爬虫2-正则表达式
1. 正则作用
正则表达式是一种可以让复杂的字符串问题变简单的工具
写正则表达式的时候就是用正则表达式来描述字符串规则
2. 正则表达式的语法
2.1 re模块
- 提供了python中所有和正则相关的函数
from re import fullmatch, findall, search
-
fullmatch(正则表达式, 字符串) - 判断指定字符串是否满足正则表达式所描述的规
-
findall(正则表达式, 字符串) - 提取正则表达式所满足正则表达式的字串
-
search(正则表达式, 字符串) - 匹配字符串中第一个满足正则表达式的字串
注意:pythhon中表达式一个正则表达式一般使用r字符串
2.2 正则符号
第一类符号:匹配字符符号
1)普通符号 - 在正则表达式中表示本身的符号
result = fullmatch(r'abc', 'abcc')
print(result) # None, 只有'
2). - 匹配任意一个字符
result = fullmatch(r'.bc', '*bc')
print(result) # <re.Match object; span=(0, 3), match='*bc'>
result = fullmatch(r'.bc.', 'hbc*') # 前后任意字符,中间必须是bc
print(result) # <re.Match object; span=(0, 3), match='*bc'>
3)\d - 匹配任意一个数字字符
result = fullmatch(r'\d\dabc', '12abc')
result1 = fullmatch(r'\d\dabc', '1*abc')
print(result)
print(result1) # None
4)\s - 匹配任意一个空白字符
- 空白字符:空格(’ ‘)、换行(’\n’)、水平制表符(‘\t’)
- 手动按tab表示4个空格(4个字符,但\s是一个字符)
result = fullmatch(r'123\sabc', '123 abc')
print(result)
result = fullmatch(r'123\sabc', '123 abc')
print(result) # None 手动按tab表示4个空格(4个字符,但\s是一个字符)
result = fullmatch(r'123\sabc', '123\tabc')
print(result)
5)\w - 匹配任意一个字母、数字、下划线或者中文
result = fullmatch(r'123\wabc', '123好abc')
print(result)
result = fullmatch(r'123\wabc', '123*abc')
print(result) # None
6)\D、\S、\W - 分别和\d、\s、\w功能相反
- \D 不能匹配数字
result = fullmatch(r'123\Dabc', '1237abc')
print(result) # None
- \S 不能匹配空格
result = fullmatch(r'123\Sabc', '123 abc')
print(result) # None
- \W 不能匹配字母、数字、下划线或者中文
result = fullmatch(r'123\Wabc', '123+abc')
print(result) # <re.Match object; span=(0, 7), match='123+abc'>
result = fullmatch(r'123\Wabc', '123_abc')
print(result) # None
7)[字符集] - 匹配在字符集中的任意一个字符
- [abc] - 匹配a或者b或者c
- [abc\d] - 匹配a或者b或者c或者任意数字:[abc0123456789]
- [0-9] - 匹配字符0到字符9的任意字符
- [a-z] - 匹配任意一个小写字母
- [A-Z] - 匹配任意一个大写字母
- [a-zA-Z] - 匹配任意一个字母
- [a-zA-Z\d] - 匹配任意一个字母或者数字
- [a-z=%] - 匹配任意一个小写字母或者‘=’或者‘%’
- [\u4e00-\u9fa5] - 匹配任意一个中文
# 在123和abc中间只能是M、9、你
result = fullmatch(r'123[M9你]abc', '123你abc')
print(result)
# 在123和abc中间匹配任意一个中文
result = fullmatch(r'123[\u4e00-\u9fa5]abc', '123你abc')
print(result)
8)[^字符集] - 匹配不在字符集中的任意字符
# 在123和abc中间匹配任意一个非小写字母
result = fullmatch(r'123[^a-z]abc', '123babc')
print(result) # None
result = fullmatch(r'123[^a-z]abc', '123你abc')
print(result) # <re.Match object; span=(0, 7), match='123你abc'>
第二类符号:匹配次数符号
1)* —— 任意次数(0次或者次数)
- a* —— a出现任意多次
- \d* —— 任意多个数字
- [abc]* —— 多个abc中的字母
result = fullmatch(r'1[abc]*2', '1bbababbcc2')
print(result)
result = fullmatch(r'M\d*N', 'M459854420N')
print(result)
result = fullmatch(r'M[3-9]*N', 'M3893N')
print(result)
2)+ - 一次或者多次(至少一次)
result = fullmatch(r'Ma+N', 'MaaN')
print(result)
3) ? - 0次或者1次
result = fullmatch(r'Ma?N', 'MaaN')
print(result) # None
4){}
- {N} - N次
- {M,N} - M到N次
- {M,} - 至少M次
- {,N} - 最多N次
# M和N之间只能是三个a
result = fullmatch(r'Ma{3}N', 'MaaN')
print(result) # None
# M和N之间任意数字出现至少三次
result = fullmatch(r'M\d{3,}N', 'M23N')
print(result) # None
练习:写一个正则表达式,可以匹配任意一个除了0的整数
# 合法:233、+345、-4561、
# 不合法:0、00034、2.23、+-345
result = fullmatch(r'[+-]?[1-9]\d*', '101')
print(result)
5) 贪婪和非贪婪
- 在匹配次数不确定的时候,如果有多种次数匹配成功,贪婪取最多的次数,非贪婪取最少次数(默认是贪婪模式)
- 贪婪模式:+、?
- {M,N}、{M,}、{,N}、*
非贪婪模式:+?、??、*?、{M,N}?、{M,}?、{,N}?
# 'aknb'、'aknbhuob'、'aknbhuobjfb'
# 默认贪婪模式,取最大次数
result = search(r'a.+b', '首农dgaknbhuobjfb')
print(result) # <re.Match object; span=(4, 15), match='aknbhuobjfb'>
# 非贪婪模式,取最小次数
result = search(r'a.+?b', '首农dgaknbhuobjfb')
print(result) # <re.Match object; span=(4, 8), match='aknb'>
第三类符号:分组和分支
1)分组 - ()
正则表达式中可以用()将部分内容表示一个整体;括号括起来的部分既是一个分组
- 整体操作的时候需要分组
- 重复匹配 - 正则中可以通过\M来重复它前面第M个分组来匹配的结果
- 捕获 - 提取分组捕获到的结果(捕获分为自动捕获(findall)和手动捕获)
# '23M'、/89K10L'、'09H23Y67G90W'
result = fullmatch(r'(\d\d[A-Z])+', '09H23Y67G90W')
print(result)
# '23M23'、'90K90'
result = fullmatch(r'(\d\d)[A-Z]\1', '90K90')
print(result)
result = fullmatch(r'(\d{3})([a-z]{2})!=\2\1{2}', '234hj!=hj234234')
print(result)
- findall在正则表达式找那个有分组的时候,会自动提取正则匹配结果中分组匹配到的内容
# 案例:提取中文后的数字
message = '单位房203jgi频数op09囧345=23jp-4vs'
result = findall(r'[\u4e00-\u9fa5](\d+)', message)
print(result) # ['203', '345']
- seach不能自动捕获,只能手动捕获
匹配结果.group(N) - 获取匹配结果中指定分组匹配到的内容
# 提取身高、体重
message = '我是小明明,今年12岁,身高180厘米,体重70kg'
result = search(r'身高(\d+)厘米,体重(\d+)kg', message)
print(result) # <re.Match object; span=(12, 26), match='身高180厘米,体重70kg'>
print(result.group()) # 身高180厘米,体重70kg
print(result.group(1), result.group(2)) # 180 70
2)分支 - |
正则1|正则2|正则3|… - 先用正则1进行匹配,匹配成功直接成功;匹配失败用正则2进行匹配…
result = findall(r'\d{3}|[a-z]{2}', 'h34ugh4567uy')
print(result) # ['ug', '456', 'uy']
# 'abc34'、'abcKP'、'abc56'、'abcHG'
# abc后是两个数字或者两个大写字母
result = fullmatch(r'abc(\d\d|[A-Z]{2})', 'abcKP')
print(result)
第四类:检测类符号
指匹配成功的时候检测所在位置是否符合要求
1)\b:检测是否是单词边界(任何可以将不同单词区分的符号:空白符号、标点符号、字符串开头、结尾)
print(fullmatch(r'abc\b123', 'abc123')) # None
print(fullmatch(r'abc\b123', 'abc 123')) # None
print(fullmatch(r'ab3\b', 'ab3')) # <re.Match object; span=(0, 3), match='ab3'>
print(fullmatch(r'abc\s\b123', 'abc 123')) # <re.Match object; span=(0, 7), match='abc 123'>
msg = '23红色多5弄了209,jsilh阿杰,欧6.7,派=p=q34f哦!66'
result = findall(r'\d+', msg)
print(result) # ['23', '5', '209', '6', '7', '34', '66']
result = findall(r'\d+\b', msg)
print(result) # ['209', '6', '7', '66']
result = findall(r'\b\d+\b', msg)
print(result) # ['7', '66']
2)^ - 检测是否是字符串开头
print(findall(r'^\d+', msg)) # ['23']
3)$ - 检测是否是字符串结尾
print(findall(r'\d+$', msg)) # ['66']
转义符号
转义符号:在本身具有特殊功能的符号或者特殊意义的符号前加 \ ,让特殊符号变成普通符号
案例:匹配整数部分和小数部分都是两位数的小数
# '\.'表示的就是'.'
result = fullmatch(r'[1-9]\d\.\d\d', '34.09')
print(result) # <re.Match object; span=(0, 5), match='34.09'>
# 任意两个数相加
result = fullmatch(r'\d+\+\d+', '3+09')
print(result) # <re.Match object; span=(0, 4), match='3+09'>
# '(amd)'
result = fullmatch(r'\([a-z]{3}\)', '(amd)')
print(result) # <re.Match object; span=(0, 5), match='(amd)'>
注意:单独存在有特殊意义的符号,在中括号中它的功能会自动消失
比如:.、*、+、?、()、$、^
组合符号不会消失:/s、/w、/d、/W、…
[]里需要有[、],需要用\
3. re模块
from re import fullmatch,findall,search,split, sub, finditer,match
1)fullmatch(正则, 字符串) - 用整个字符串和正则,匹配成功返回匹配对象,匹配不成功返回None
2)findall(正则, 字符串) - 获取字符串所有满足正则的子串,默认返回一个列表,列表中元素是所有匹配的子串(存在自动捕获现象)
3)seach(正则, 字符串) - 匹配第一个满足正则的子串,匹配成功返回匹配对象,匹配不成功返回None
4)split(正则, 字符串, N) - 将字符串中前N个满足正则的子串作为切割点进行切割
5)sub(正则, 字符串1, 字符串2, N) - 将字符串2中前N个满足正则的子串替换成字符串1
6)finditer(正则, 字符串) - 提取字符串中所有满足正则的子串,返回以一个迭代器,迭代器的元素是匹配对象
7)match(正则, 字符串) - 匹配字符串开头
str1 = '鸡7皮肤呢934u9njk4r流口水'
print(split(r'\d+', str1, 3)) # ['鸡', '皮肤呢', 'u', 'njk4r流口水']
str1 = '鸡7皮肤呢934u9njk4r流口水'
print(sub(r'\d', '*', str1)) # 鸡*皮肤呢***u*njk*r流口水
message = '我草,去你妈的,快点!sb,艹!SB'
print(sub(r'(?i)我草|妈的|sb|艹|f\s*u\s*c\s*k', '*', message))
str1 = '鸡7皮肤呢934u9njk4r流口水'
result = finditer(r'\d+', str1)
print(list(result))
result = match(r'\d{3}', '345加囧诶哦jod')
print(result)
- 补充:
1)忽略大小写: (?i)
print(fullmatch('(?i)abc', 'AbC'))
2)单行匹配:(?s)
-
多行匹配(默认):.不能和换行符进行匹配
-
单行匹配:可以和换行符进行匹配
print(fullmatch(r'abc.123', 'abc\n123')) # None
print(fullmatch(r'(?s)abc.123', 'abc\n123')) # <re.Match object; span=(0, 7), match='abc\n123'>
练习:爬取豆瓣电影TOP250的电影名和详情页网址
import requests
from re import findall
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36',
}
response = requests.get('https://movie.douban.com/top250', headers=headers)
result = response.text
# print(result)
name = findall(r'<img width="100" alt="(.+?)"', result)
print(name)
ip = findall(r'<a href="(.+?)" class="">', result)
print(ip)