正则表达式

Regular Expression, 正则表达式, ⼀种使⽤表达式的⽅式对字符串进⾏匹配的语法规则。

我们抓取到的⽹⻚源代码本质上就是⼀个超⻓的字符串, 想从⾥⾯提取内容。⽤正则再合适不过了。

正则的优点:速度快, 效率⾼, 准确性⾼ 正则的缺点:新⼿上⼿难度有点⼉⾼。

不过只要掌握了正则编写的逻辑关系, 写出⼀个提取⻚⾯内容的正则其实并不复杂

正则的语法:使⽤元字符进⾏排列组合⽤来匹配字符串 在线测试正则表达式 https://tool.oschina.net/regex/

元字符:具有固定含义的特殊符号 常⽤元字符:

. 匹配除换⾏符以外的任意字符

\w 匹配字⺟或数字或下划线

\s 匹配任意的空⽩符 \d 匹配数字

\n 匹配⼀个换⾏符

\t 匹配⼀个制表符

^ 匹配字符串的开始 $ 匹配字符串的结尾

\W 匹配⾮字⺟或数字或下划线

\D 匹配⾮数字

\S 匹配⾮空⽩符

a|b 匹配字符a或字符b

() 匹配括号内的表达式,也表示⼀个组

[…] 匹配字符组中的字符

[^…] 匹配除了字符组中字符的所有字符

量词: 控制前⾯的元字符出现的次数

* 重复零次或更多次

+ 重复⼀次或更多次

? 重复零次或⼀次

{n} 重复n次

{n,} 重复n次或更多次

{n,m} 重复n到m次

贪婪匹配和惰性匹配

.* 贪婪匹配 .*? 惰性匹配

这两个要着重的说⼀下,因为我们写爬⾍⽤的最多的就是这个惰性匹配。

先看案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
str: 玩⼉吃鸡游戏, 晚上⼀起上游戏, ⼲嘛呢? 打游戏啊
reg: 玩⼉.*?游戏
此时匹配的是: 玩⼉吃鸡游戏
reg: 玩⼉.*游戏
此时匹配的是: 玩⼉吃鸡游戏, 晚上⼀起上游戏, ⼲嘛呢? 打游戏
str: <div>胡辣汤</div>
reg: <.*>
结果: <div>胡辣汤</div>
str: <div>胡辣汤</div>
reg: <.*?>
结果:
<div>
</div>
str: <div>胡辣汤</div><span>饭团</span>
reg: <div>.*?</div>
结果:
<div>胡辣汤</div>

那么接下来的问题是, 正则我会写了, 怎么在python程序中使⽤正则呢?答案是re模块

re模块中我们只需要记住这么⼏个功能就⾜够我们使⽤了。

1. findall 查找所有,返回list

1
2
3
4
5
import re
lst = re.findall("m"," mai le for len,mai ni mei!")
print(lst) # ['m', 'm', 'm']
lst = re.findall(r"\d+","5点之前. 你要给我5000万")
print(lst) # ['5', '5000'] 匹配5开头的所有的数字

2. search 会进⾏匹配, 但是如果匹配到了第⼀个结果,就会返回这个结果。如果匹配不上search返回的则是None

1
2
ret = re.search(r"\d","5点之前. 你要给我5000万").group()
print(ret) #匹配结果为 5

3. match 只能从字符串的开头进⾏匹配

1
2
ret = re.match("a","abc").group()
print(ret) #结果为a,如果为babc 报错

4. finditer, 和findall差不多. 只不过这时返回的是迭代器(重点)

1
2
3
it = re.finditer("m"," mai le for len,mai ni mei!")
for el in it:
print(el.group()) # 不分组则返回迭代器

5. compile() 可以将⼀个⻓⻓的正则进⾏预加载. ⽅便后⾯的使⽤

1
2
3
4
5
# 将正则表达式编译成为⼀个正则表达式对象, 规则要匹配的是3个数字
obj = re.compile(r'\d{3}')
# 正则表达式对象调⽤search, 参数为待匹配的字符串
ret = obj.search('abc123eeee')
print(ret.group()) # 结果: 123

6. 正则中的内容如何单独提取?这⾥可以看到我们可以通过使⽤分组。来对正则匹配到的内容进⼀步的进⾏筛选。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
s = """
<div class='⻄游记'><span id='10010'>中国联通</span></div>
"""

obj = re.compile(r"<span id='(?P<id>\d+)'>(?P<name>\w+)</span>",re.S)
obj = re.compile(r"<span id='(?P<id>\d+)'>(?P<name>\w+)</span>",re.S)
# ?P<value>的意思就是命名一个名字为value的组,匹配规则符合后面的/d+
# 如果不使用re.S参数,则只在每一行内进行匹配,如果一行没有,就换下一行重新开始。
# 而使用re.S参数以后,正则表达式会将这个字符串作为一个整体,在整体中进行匹配。
result = obj.search(s)
# print(result.group())
print(result.group())
print(result.group("id"))
print(result.group("name"))

7.正式练习

下面一个案例,是练习用正则表达式提取豆瓣电影top250的数据并保存,一起来学一下吧。

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
# 拿到页面源代码.   requests
# 通过re来提取想要的有效信息 re
import requests
import re
import csv

url = "https://movie.douban.com/top250"
headers = {
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.192 Safari/537.36"
}
resp = requests.get(url, headers=headers)
page_content = resp.text

# 解析数据
obj = re.compile(r'<li>.*?<div class="item">.*?<span class="title">(?P<name>.*?)'
r'</span>.*?<p class="">.*?<br>(?P<year>.*?)&nbsp.*?<span '
r'class="rating_num" property="v:average">(?P<score>.*?)</span>.*?'
r'<span>(?P<num>.*?)人评价</span>', re.S)
# 开始匹配
result = obj.finditer(page_content)
f = open("data.csv", mode="w")
csvwriter = csv.writer(f)
for it in result:
# print(it.group("name"))
# print(it.group("score"))
# print(it.group("num"))
# print(it.group("year").strip())
dic = it.groupdict()
dic['year'] = dic['year'].strip()
csvwriter.writerow(dic.values())

f.close()
print("over!")