爬虫基础系列-正则表达式

正则表达式

在上一节中已经知道如何通过requests获取网页的源代码,得到HTML代码,但是我们需要的数据都存在于HTML代码中,那么如何从HTML中获取目标数据呢?正则表达式是其中的一种方法,并且我个人还是比较建议使用正则表达式获取数据会更加的方便。

通用的匹配规则

模式 描述
w 匹配数字、字母及下划线
W 匹配非数字、字母及下划线的字符
s 匹配任意的空白字符(空格)
S 匹配任意的非空字符
d 匹配任意数字,等价于[0-9]
D 匹配任意非数字的字符
A 匹配字符串开头
Z 匹配字符串结尾。如果存在换行,只匹配到换行前的结束字符串
z 匹配字符串结尾。如果存在换行,同时还会匹配换行符
G 匹配最后匹配完成的位置
n 匹配一个换行符
t 匹配一个制表符
^ 匹配一行字符串开头
$ 匹配一行字符串结尾
. 匹配任意字符,除了换行符,当re.DOTALL标记被指定时,可以匹配包括换行符的任意字符
[...] 用来表示一组字符,单独列出,例如[amk]
[^...] 匹配不在[]中的字符,例如匹配除了a、b、c之外的字符
* 匹配0个或多个表达式
+ 匹配1个或多个表达式
? 匹配0个或1个前面的正则表达式定义的片段,非贪婪方式
{n} 精准匹配n个前面的表达式
{n, m} 匹配n到m次由前面正则表达式定义的片段,贪婪方式
a|b 匹配a或b
() 匹配括号内的表达式,也表示一个组

看完这个表格之后应该会头脑炸裂吧,不用担心,接下来会讲解关于正则表达式的用法。

大家要注意,正则表达式并不是只有python才有,它也可以在其他编程语言中使用。python的re库提供了整个表达式的实现,利用这个库,可以在python中方便的使用正则表达式。

match

match是一种常用的匹配方式,向它传入正则表达式以及需要匹配的字符串,就可以匹配这个表达式和字符串是否相匹配。它是从开头开始匹配的方法

如果匹配成功,则返回匹配成功的结果,如果失败则返回None。

示例如下:

import re

content = 'Hello 123 4567 Word_This is a Regex Dome'
print(len(content))
result = re.match('^Hellosdddsd{4}sw{9}', content)
print(result)
print(result.group())
print(result.span())

运行结果:

40
<re.Match object; span=(0, 24), match='Hello 123 4567 Word_This'>
Hello 123 4567 Word_This
(0, 24)

首先声明了一个字符串包含字母、数字、下划线和空白符。接着写了一个正则表达式:

^Hellosdddsd{4}sw{9}

开头的^表示匹配开头的字符串,也就是以hello开头,然后/s表示匹配空白字符,用于匹配hello后面的空格;d表示匹配数字,3个d表示匹配3个数字;紧接着一个s表示空格,紧接着后面有4数字,虽然可以连续写4个d表示4个数字,但是比较繁琐,所以可以在d的后面跟着{4}代表匹配4个数字。最后的w{9}代表匹配9个字母及下划线。

将结果打印出来,可以看到re.Match object对象,证明匹配成功。该对象包含两个方法;group()方法可以输出匹配的内容,结果是Hello 123 4567 Word_This,这恰好是正则表达式匹配的内容;span表示匹配的范围,结果是(0, 24)。

  • 匹配目标

用match方法可以实现匹配,如果想从字符串中提取一部分内容,那么应该如何实现呢?

可以使用()将先要提取的字符串括起来。()实际标记了一个子表达式开始和结束的位置,被标记的每个子表示式起始位置和结束位置,被标记的子表达式依次对应每个分组,调用group方法传入分组的索引即可提取结果。

示例代码:

import re

content = 'Hello 1234567 Word_This is a Regex Dome'
pattern = '^Hellos(d+)sWord'
result = re.match(pattern, content)
print(result)
print(result.group())
print(result.group(1))
print(result.span())

通过上面的示例,把字符串中的123456提取出来了,可以看到数字部分的正则表达式被括起来,然后调用group(1)获取匹配结果。

运行结果如下:

<re.Match object; span=(0, 18), match='Hello 1234567 Word'>
Hello 1234567 Word
1234567
(0, 18)

group(1)代表匹配到第一个被括起来的内容,如果后面还有括号,则通过group(2)或group(3)提取结果。

  • 通用匹配

刚刚所写的正则表达式还是比较复杂的,只要出现空白字符就要通过s匹配,出现的数字通过d匹配。为了减少工作量,柘林有一个万能的匹配方式就是.*。其中.可以匹配任意字符(除了换行符);*代表匹配前面的字符无限次,所以它们组合在一起就可以匹配任意字符。

优化代码示例:

import re

content = 'Hello 123 4567 Word_This is a Regex Dome'
pattern = '^Hello.*Dome$'
result = re.match(pattern, content)
print(result)
print(result.group())
print(result.span())

这里,我们直接省略了中间部分,用.*来代替,最后加了一个结尾字符串。

运行结果如下:

<re.Match object; span=(0, 40), match='Hello 123 4567 Word_This is a Regex Dome'>
Hello 123 4567 Word_This is a Regex Dome
(0, 40)
  • 贪婪匹配与非贪婪匹配

使用通用匹配.*获取的内容有时并非我们想要的结果。看下面的示例:

import re

content = 'Hello 1234567 Word_This is a Regex Dome'
pattern = '^Hello.*(d+).*Dome$'
result = re.match(pattern, content)
print(result)
print(result.group())
print(result.group(1))
print(result.span())

现在依然想要获取中间的那段数字,来看看运行结果

<re.Match object; span=(0, 39), match='Hello 1234567 Word_This is a Regex Dome'>
Hello 1234567 Word_This is a Regex Dome
7
(0, 39)

神奇的事情是,只得到了7这个数字。这是怎么回事呢?

这里就涉及到了贪婪和非贪婪匹配,在贪婪匹配下.*会匹配更多的字符。而d+也就是至少能匹配1个数字。而且没有具体指定具体数字,因此.*会把123456都匹配了,只给d+留下一个可以满足条件的数字7,因此最后得到的内容就只有数字7.

这种情况明显会给自己带来不便。有时候,匹配结果会莫名少一部分内容。其实,这里只需要要使用非贪婪匹配即可。非贪婪匹配的写法是.*?

import re

content = 'Hello 1234567 Word_This is a Regex Dome'
pattern = '^Hello.*?(d+).*Dome$'
result = re.match(pattern, content)
print(result)
print(result.group())
print(result.group(1))
print(result.span())

运行结果如下:

<re.Match object; span=(0, 39), match='Hello 1234567 Word_This is a Regex Dome'>
Hello 1234567 Word_This is a Regex Dome
1234567
(0, 39)

非贪婪匹配就是尽可能匹配较少的字符。当.*?匹配到Hello后面的空白字符时,再往后的字符是数字了,而d+恰好可以匹配,于是这里的.*?就不再进行匹配了。

但是有一点需要注意,如果匹配的内容在结尾处,那使用.*?可能会匹配不到内容。

示例如下:

import re

content = 'http://weibo.com/comment/kEraCN'
result1 = re.match('http.*?comment/(.*)', content)
result2 = re.match('http.*?comment/(.*?)', content)
print('result1: ', result1.group(1))
print('result2: ', result2.group(1))

运行结果如下:

result1:  kEraCN
result2:  
  • 修饰符

    在正则表达式中,可以用一些可选的标志修饰符来控制匹配的模式。修饰符被定义为一个可选的标志。

    import re

    content = '''Hello 1234567 World_this
    is a Regex Dome
    '''

    result = re.match('^He.*?(d+).*?Dome$', content)
    print(result.group(1))

    和上面的例子一样,只是在字符串中添加了换行符,正则表达式还是一样的。

    运行结果:

    AttributeError: 'NoneType' object has no attribute 'group'

    运行报错,也就是说正则表达式没有匹配到字符串,返回的结果为None。

    因为.*.*?所匹配的内容是除换行符之外的任意字符,所以遇到换行符后就会导致匹配失败。

    修饰符

    修饰符 描述
    re.I 使匹配对大小写不敏感
    re.L 实现本地化识别(locale-aware)匹配
    re.M 多行匹配,影响^和$
    re.S 使匹配内容包括换行符在内的所有字符
    re.U 根据Unicode字符集解析字符,这个标志会影响w、W、b和B
    re.X 该标志能给予你更灵活的格式,以便将正则表达式书写的更易于理解

    将上方代码作如下修改即可。

    result = re.match('^He.*?(d+).*?Dome$', content, re.S)

    search

    match方法是从字符串的开头开始匹配的,意味着开头不匹配,整个匹配就会失败。

    代码示例:

    import re

    content = 'Extra string Hello 1234567 World_This is a regex Dome Extra   string'
    pattern = 'Hello.*?(d+).*string'
    result = re.match(pattern, content)
    print(result)

    运行结果

    None

    这里的字符串是以Extra开头,而正则表达式却以Hello开头,因此整个正则表达式的匹配是失败的。

    这里介绍另外一种方法search,它在匹配时会扫描整个字符串,然后返回第一个匹配成功的结果,也就是说正则表达式可以是字符串的一部分,在匹配时,search方法会依次以每个字符串作为开头进行扫描,直到找到第一个符合要求的字符串,然后返回匹配的内容。如果没有匹配成功则返回None.

    import re

    content = 'Extra string Hello 1234567 World_This is a regex Dome Extra   string'
    pattern = 'Hello.*?(d+).*string'
    result = re.search(pattern, content)
    print(result)
    print(result.group(1))

    运行结果

    <re.Match object; span=(13, 68), match='Hello 1234567 World_This is a regex Dome Extra   >
    1234567

    findall

    search方法可以返回与正则表达式匹配的第一个字符串。如果想要获取与正则表达式相匹配的所有字符串,那就可以借助findall方法。

    示例代码:

    html = '''<li class=" " title="檀健次 - 蒙娜丽莎"  ></li>
              <li class=" " title="尚辰 - 你是我捡到的光"  ></li>
              <li class=" " title="GALI、万妮达Vinida Weng - 狂恋"  ></li>'''


    import re

    pattern = '<li.*?title="(.*?)".*?'
    results = re.findall(pattern, html, re.S)
    print(results)
    for result in results:
        print(result)

    运行结果

    [('檀健次 - 蒙娜丽莎''1'), ('尚辰 - 你是我捡到的光''2'), ('GALI、万妮达Vinida Weng - 狂恋''3')]
    ('檀健次 - 蒙娜丽莎''1')
    ('尚辰 - 你是我捡到的光''2')
    ('GALI、万妮达Vinida Weng - 狂恋''3')

    compile

    前面所讲的方法都是处理字符串的方法,现在介绍一下compile方法,这个方法可以将正则字符串编译成正则表达式对象,以便在后面的匹配中复用。

    示例代码:

    import re

    content1 = '2019-12-15 12:00'
    content2 = '2019-12-16 17:29'
    content3 = '2019-12-31 15:25'
    pattern = re.compile('d{2}:d{2}')
    result1 = re.sub(pattern, '', content1)
    result2 = re.sub(pattern, '', content2)
    result3 = re.sub(pattern, '', content3)
    print(result1, result2, result3)

    这里利用了sub方法将时间去掉

    运行结果

    2019-12-15  2019-12-16  2019-12-31 


原文始发于微信公众号(小志Codings):爬虫基础系列-正则表达式

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/236542.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!