正则表达式
在上一节中已经知道如何通过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 >
1234567findall
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