正则表达式

Regular Expression, 正则表达式, ⼀种使⽤表达式的⽅式对字符串进⾏匹配的语法规则 我们抓取到的⽹⻚源代码本质上就是⼀个超⻓的字符串, 想从⾥⾯提取内容.⽤正则再合适不过了. 正则的优点: 速度快, 效率⾼, 准确性⾼ 正则的缺点: 新⼿上⼿难度有点⼉⾼. 不过只要掌握了正则编写的逻辑关系, 写出⼀个提取⻚⾯内容的正则其实并不复杂 正则的语法: 使⽤元字符进⾏排列组合⽤来匹配字符串 在线测试正则 表达式https://tool.oschina.net/regex/

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

列名列名
.匹配除换⾏符以外的任意字符, 未来在python的re模块中是⼀个坑
\w匹配字⺟或数字或下划线
\s匹配任意的空⽩符
\d匹配数字
\n匹配⼀个换⾏符
\t匹配⼀个制表符
^匹配字符串的开始
$匹配字符串的结尾
\W匹配⾮字⺟或数字或下划线
\D匹配⾮数字
\S匹配⾮空⽩符
a|b匹配字符a或字符b
()匹配括号内的表达式,也表示⼀个组
[...]匹配字符组中的字符
[^...]匹配除了字符组中字符的所有字符
\A匹配字符串开始
\Z匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。
\z匹配字符串结束
\G匹配最后匹配完成的位置。
\b匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。
\B匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。
\n, \t, 等.匹配一个换行符。匹配一个制表符。等
\1...\9匹配第n个分组的内容。
\10匹配第n个分组的内容,如果它经匹配。否则指的是八进制字符码的表达式。

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

列名列名
*匹配0个或多个的表达式。
+匹配1个或多个的表达式。
?匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式
{n}精确匹配 n 个前面表达式。例如, o{2} 不能匹配 "Bob" 中的 "o",但是能匹配 "food" 中的两个 o。
{n,}匹配 n 个前面表达式。例如, o{2,} 不能匹配"Bob"中的"o",但能匹配 "foooood"中的所有 o。"o{1,}" 等价于 "o+"。"o{0,}" 则等价于 "o*"。
{n,m}匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式

贪婪匹配和惰性匹配

列名列名
.*贪婪匹配, 尽可能多的去匹配结果
.*?惰性匹配, 尽可能少的去匹配结果 -> 回溯
(?imx)正则表达式包含三种可选标志:i, m, 或 x 。只影响括号中的区域。
(?-imx)正则表达式关闭 i, m, 或 x 可选标志。只影响括号中的区域。
(?: re)类似 (...), 但是不表示一个组
(?imx: re)在括号中使用i, m, 或 x 可选标志
(?-imx: re)在括号中不使用i, m, 或 x 可选标志
(?#...)在括号中不使用i, m, 或 x 可选标志
(?= re)前向肯定界定符。如果所含正则表达式,以 ... 表示,在当前位置成功匹配时成功,否则失败。但一旦所含表达式已经尝试,匹配引擎根本没有提高;模式的剩余部分还要尝试界定符的右边。
(?! re)前向否定界定符。与肯定界定符相反;当所含表达式不能在字符串当前位置匹配时成功
(?> re)匹配的独立模式,省去回溯。

这两个要着重的说⼀下. 因为我们写爬⾍⽤的最多的就是这个惰性匹配. 先看案例

str: 玩⼉吃鸡游戏, 晚上⼀起上游戏, ⼲嘛呢? 打游戏啊
reg: 玩⼉.*?游戏
此时匹配的是: 玩⼉吃鸡游戏
reg: 玩⼉.*游戏 
此时匹配的是: 玩⼉吃鸡游戏, 晚上⼀起上游戏, ⼲嘛呢? 打游
戏 
 
 
str: <div>胡辣汤</div>
reg: <.*>
结果: <div>胡辣汤</div>
str: <div>胡辣汤</div>
reg: <.*?>
结果:
 <div>
 </div>
str: <div class="abc"><div>胡辣汤</div><div>饭团
</div></div>
reg: <div>.*?</div>
结果:
 <div>胡辣汤</div>
 <div>饭团</div>

所以我们能发现这样⼀个规律: .*? 表示尽可能少的匹配, .*表示尽可 能多的匹配, 暂时先记住这个规律. 后⾯写爬⾍会⽤到的哦

re模块

怎么在python程序中使⽤正则呢? 答案是re模块

findall

findall 查找所有. 返回list

lst = re.findall("m", "mai le fo len, mai ni
mei!")
print(lst) # ['m', 'm', 'm']
lst = re.findall(r"\d+", "5点之前. 你要给我5000
万")
print(lst) # ['5', '5000']

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

ret = re.search(r'\d', '5点之前. 你要给我5000
万').group()
print(ret) # 5

match

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

ret = re.match('a', 'abc').group() 
print(ret) # a

finditer

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

it = re.finditer("m", "mai le fo len, mai ni
mei!")
for el in it:
	print(el.group()) # 依然需要分组

compile() 预加载

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

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

正则中的内容如何单独提取? 单独获取到正则中的具体内容可以给分组起名字

s = """
<div class='⻄游记'><span id='10010'>中国联通
</span></div>
"""
obj = re.compile(r"<span id='(?P<id>\d+)'>(?P<name>\w+)</span>", re.S)
result = obj.search(s)
print(result.group()) # 结果: <span id='10010'>中国联通</span>
print(result.group("id")) # 结果: 10010 # 获取id组的内容
print(result.group("name")) # 结果: 中国联通 #获取name组的内容

这⾥可以看到我们可以通过使⽤分组. 来对正则匹配到的内容进⼀步的进⾏筛选.