0%

正则表达式的进阶用法——预查与分组

@[toc]

昨天刚发现正则表达式的分组用法,故在此记录。


预查

正向预查:?=, ?!

?=: 检测包含此结尾的内容,但不捕获。

例:w+(?=\.com),检测以.com结尾的字符串,但返回结果中不包含.com.

*注:需引入re库,后面不再赘述。

1
print(re.findall(r'\w+(?=\.com)', 'baidu.com google.com csdn.net'))

输入结果:

1
['baidu', 'google']

?!: 检测不包含此结尾的内容,但不捕获。

例:Windows(?!95|98),检测不以95和98结尾的"Windows",返回结果中不包含Windows之后的内容。

1
2
print(re.match(r'Windows(?!95|98|NT|2000)', 'Windows95'))
print(re.match(r'Windows(?!95|98|NT|2000)', 'Windows10').group)

输出:

1
2
None
Windows

负向预查:?<=, ?<!/?<!=

?<=: 检测包含此开头的内容,但不捕获。

例:(?<=www\.)\w+,检测以www.开头的字符串,但返回结果中不包含开头。

1
print(re.findall(r'(?<=www\.)\w+', 'www.github.com cn.vuejs.org www.baidu.com'))

输出结果:

1
['github', 'baidu']

?<!?<!=: 等价,检测不包含此开头的内容,但不捕获。此处不再举例。

练习:网页小爬虫

需求:爬取douban.com中所有用div标签包裹的内容

1
2
3
4
5
6
7
8
9
10
import requests
import re

headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36'}
url = 'https://www.douban.com/'
http = requests.get(url, headers=headers)
reg = r'(?<=<div[^>]*>)\n*\w+\n*(?=</div>)'
result_list = re.findall(reg, http.text)
for result in result_list:
print(result)

我们自然而然的想到左右用<?<=div...(任意字符)>...<?=/div>,然而结果却是:

1
re.error: look-behind requires fixed-width pattern

原来负向预查并不支持不定长字符串,我们需要找到更好的办法,不过现在我们可以先尝试不排除div标签,直接打印:

修改表达式为:reg = r'<div[^>]*>[^<]*</div>'

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<div id="dale_anonymous_homepage_top_for_crazy_ad"></div>
<div id="dale_anonymous_homepage_right_top"></div>
<div id="dale_homepage_online_activity_promo_1"></div>
<div id="dale_anonymous_homepage_doublemint"></div>
<div class="side"></div>
<div id="dale_anonymous_homepage_movie_bottom" class="extra"></div>
<div class="author">〔日〕多利安助...</div>
<div class="author">〔日〕伊坂幸太...</div>
<div class="author">〔日〕池井户润...</div>
<div class="author">吴沚默</div>
<div class="author"></div>
...
<div class="title">你听过《东京爱情故事》吗?</div>
<div id="dale_anonymous_home_page_middle_2" class="extra"></div>
<div class="market-topic-pic"
style="background-image:url(https://img3.doubanio.com/img/files/file-1513305186-3.jpg)">
</div>
<div class="market-spu-pic"
style="background-image: url(https://img3.doubanio.com/img/files/file-1546855945-0.jpg)">
</div>
<div class="market-spu-pic"
style="background-image: url(https://img3.doubanio.com/img/files/file-1545819571-0.jpg)">
</div>
<div class="market-spu-pic"
style="background-image: url(https://img9.doubanio.com/img/files/file-1513305186-4.jpg)">
</div>
<div class="follow">
3人关注
</div>
<div class="datetime">
44日 周六 19:30 - 21:30
</div>
<div class="follow">
1人关注
</div>
<div class="datetime">
1221日 周六 - 412日 周日
</div>
<div class="follow">
5人关注
</div>
<div id="dale_anonymous_home_page_bottom" class="extra"></div>

结果返回但非常杂乱,这就是我们之后需要改进的。


分组

普通分组

使用圆括号()表示分组。

这里介绍一下re.match()函数,这个函数会根据正则表达式从开头向后匹配,返回第一个符合的结果 (开头必须符合) 。它的返回值是一个object,object有group()方法和groups()方法。

  • group(): 返回正常匹配结果
  • groups(): 返回分组内容
1
2
print(re.match('(\w+)\.\w+\.(\w+)', 'www.baidu.com').group())
print(re.match('(\w+)\.\w+\.(\w+)', 'www.baidu.com').groups())

这里使用圆括号将网页URL的前缀作为一组,后缀作为一组,因此groups()会返回网页前后缀字符串。

输出结果:

1
2
www.baidu.com
('www', 'com')

命名分组

命名分组,顾名思义,可以给每个分组命名,返回值将以字典形式表示。

语法:?P<分组名>

例:

1
2
result = re.match('(?P<head>\w+)\.\w+\.(?P<tail>\w+)', 'www.baidu.com')
print(result['head'], result['tail'])
用head,tail命名网页前后缀,最终可通过访问字典的方式访问match返回值。

练习完善

之前的爬虫得到结果,却能杂乱,有个分组的知识我们就可以做出改进。

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests
import re

headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36'}
url = 'https://www.douban.com/'
http = requests.get(url, headers=headers)
reg = r'<div[^>]*>([^<]*)</div>'
result_list = re.findall(reg, http.text)
index = 1
for result in result_list:
if str(result).strip():
print(str(index) + ". " + str(result).strip())
index += 1

只做了少许修改:

  • 为reg标签包裹的内容加上括号(findall函数会以列表形式返回分组的所有内容,如果没有分组就返回匹配结果)
  • 对匹配结果使用strip()方法去除多余的空字符
  • 排除全空的结果,并为结果加上索引

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1. 吃屎不忘拉屎人的日记
2. 〔日〕多利安助...
3. 〔日〕伊坂幸太...
4. 〔日〕池井户润...
5. 吴沚默
6. 免费
7. 免费
8. 免费
9. 免费
10. 「旅行」我想去精灵旅社度个假
11. 欧美丨在Uptown听Funk修个椰子皮
12. 日本民谣:我的歌,是用时间...
13. 「复古」音乐和情绪都不会过时
14. 给我一段音乐,推开看得见风...
15. 你听过《东京爱情故事》吗?
16. 3月25日 周三 19:30 - 21:30
17. 3人关注
18. 3月18日 周三 19:30 - 21:30
19. 3人关注
20. 4月4日 周六 19:30 - 21:30
21. 1人关注
22. 12月21日 周六 - 4月12日 周日
23. 5人关注

这次返回的结果就非常成功,简单利索。