Python爬虫-正则表达式提取数据
前言
人不可以一时得志,而自夸其能
亦不可以一时失意,而自坠其志
流水不争先,争的是滔滔不绝
⚠️声明:本文所涉及的爬虫技术及代码仅用于学习、交流与技术研究目的,禁止用于任何商业用途或违反相关法律法规的行为。若因不当使用造成法律责任,概与作者无关。请尊重目标网站的
robots.txt协议及相关服务条款,共同维护良好的网络环境。
1.正则表达式
正则表达式(Regular Expression,简称正则或RegEx)是一种用于匹配字符串的模式,它可以帮助我们进行字符串的搜索、替换、分割等操作。正则表达式由普通字符和特殊字符组成,用于描述和匹配文本模式。
常见语法
| 一般字符 | 匹配规则 | 表达式 | 完整匹配的字符串 |
|---|---|---|---|
. |
匹配任意除换行符\n外的字符 |
a.c |
abc |
\ |
转义字符,使后面一个字符改变原来的意思。如果字符串有字符*需要匹配,则可以使用\* |
a\.c |
a.c |
[...] |
字符集(字符类)。对应的位置可以使字符集中任意字符。字符集中的字符可以逐个列出,也可以给定范围:[abc]或[a-c]。第一个字符如果是^则表示取反:[^abc]表示不是abc的其他字符 |
a[bcd]e |
abe、ace、ade |
\d |
数字:[0-9] |
a\dc |
a1c |
\D |
非数字:[^\d] |
a\Dc |
abc |
\s |
空白字符:[<空格>\t\r\t\f\v] |
a\sc |
a c |
\S |
非空白字符:[^\s] |
a\Sc |
abc |
\w |
单词字符:[A-Za-z0-9_] |
a\wc |
abc |
\W |
非单词字符:[^\w] |
a\Wc |
a c |
* |
匹配前一个字符0次或者无数次 | abc* |
ab、abccccc |
+ |
匹配前一个字符1次或者无数次 | abc+ |
abc、abccccc |
? |
匹配前一个字符0次或者1次 | abc? |
ab、abc |
{m} |
匹配前一个字符m次 |
ab{2}c |
abbc |
re模块的常见方法
-
pattern.match:从字符串开头找一个 -
pattern.search:在字符串任意位置找一个 -
pattern.findall:找所有- 返回一个列表,如果没有则返回一个空列表
re.findall('\d', 'zhi1zhi2')得到['1', '2']
-
pattern.sub:替换re.sub('\d', '_', 'zhi1zhi2')得到['zhi_zhi_']
-
re.compile:编译-
返回一个模型
P,具有和re一样的方法,但是传递的参数不同 -
匹配模式需要传到
compile中
-
实例
r'':r 表示原始字符串(raw string)。在原始字符串中,反斜杠(\)不会被转义,因此正则表达式中的反斜杠无需再用 \\ 来表示。例如,r'\d' 等价于 \\d(匹配数字)。
[]:方括号内表示字符集合,用于匹配其中任意一个字符。
\d:表示数字(0-9)。
.:表示小数点符号。
[\\d.]:表示匹配数字(0-9)或小数点(.)。
+:表示前面的字符集合(即数字或小数点)可以出现一次或多次,至少要有一个字符。这里它用于匹配工资金额,如 2 或 2.5。
万:匹配字符 万,用于标识单位“万”。
/:匹配字符 /,在这个正则表达式中是固定的字符。
每{0,1}:每:匹配字符 每,它可能出现,也可能不出现。{0,1} 表示 每 字符可以出现零次或一次,即允许匹配 /月 或 /每月。
月:匹配字符 月,用于表示单位“每月”。
import re
content = '''
Python3 高级开发工程师 上海互教教育科技有限公司上海-浦东新区2万/月02-18满员
测试开发工程师(C++/python) 上海墨鹍数码科技有限公司上海-浦东新区2.5万/每月02-18未满员
Python3 开发工程师 上海德拓信息技术股份有限公司上海-徐汇区1.3万/每月02-18剩余11人
测试开发工程师(Python) 赫里普(上海)信息科技有限公司上海-浦东新区1.1万/每月02-18剩余5人
Python高级开发工程师 上海行动教育科技股份有限公司上海-闵行区2.8万/月02-18剩余255人
python开发工程师 上海优似腾软件开发有限公司上海-浦东新区2.5万/每月02-18满员
'''
for temp in re.findall(r'([\d.]+)万/每{0,1}月', content):
print(temp)

2.正则表达式在线验证工具

3.常见语法
3.1普通字符匹配
比如匹配Python

元字符不能直接匹配,需要转义
. * + ? \ [] ^ $ {} | ()
3.2通配符-单个字符(.)
.(点号):表示匹配 任意单个字符(除了换行符)。
匹配颜色
/.色/g
苹果是绿色的
橙子是橙色的
香蕉是黄色的
乌鸦是黑色的

代码
for temp in re.findall(r'.色', content):
print(temp)

3.3通配符-0次或多次(*)
*(星号):表示前面的元素可以出现 0次或多次。它是最常用的通配符之一。
/,.*/g
苹果,是绿色的
橙子,是橙色的
香蕉,是黄色的
乌鸦,是黑色的
枫叶,是红色的

代码
import re
content = '''
苹果,是绿色的
橙子,是橙色的
香蕉,是黄色的
乌鸦,是黑色的
枫叶,是红色的
'''
for temp in re.findall(r',.*', content):
print(temp)

3.4通配符-1次或多次(+)
+(加号):表示前面的元素可以出现 1次或多次。它要求至少匹配一次前面的字符。
/,.+/g
苹果,是绿色的
橙子,是橙色的
香蕉,是黄色的
乌鸦,是黑色的
枫叶,是红色的
大海,

代码
import re
content = '''
苹果,是绿色的
橙子,是橙色的
香蕉,是黄色的
乌鸦,是黑色的
枫叶,是红色的
大海,
'''
for temp in re.findall(r',.+', content):
print(temp)

3.5通配符-0次或1次(?)
?(问号):表示前面的元素可以出现 0次或1次。即前面的字符是可选的。
/,.?/g
苹果,是绿色的
橙子,是橙色的
香蕉,是黄色的
乌鸦,是黑色的
枫叶,是红色的
大海,

3.6执行次数({})
{}(花括号) 是一个量词,用于指定前面的元素 重复的次数。
/油{3,4}/g:匹配 连续3到4个 油 字符。
/油{3}/g::匹配 恰好3个连续的 油 字符。
/油{3,4}/g
红彤彤,绿油油,黑乎乎,绿油油油油

代码
import re
content = '''
红彤彤,绿油油,黑乎乎,绿油油油油
'''
for temp in re.findall(r'油{3,4}', content):
print(temp)

3.7贪婪模式和非贪婪模式
3.7.1贪婪模式(.*)
贪婪模式是正则表达式的默认行为,它会尽可能匹配更多的字符,直到满足整个模式的条件为止。也就是说,贪婪模式会尽量“吃掉”所有可能匹配的字符,以便匹配整个表达式。
.* 代表匹配零个或多个字符(任意字符),贪婪模式会匹配尽可能多的字符。
import re
content = '<html><head><title>Title</title>'
for temp in re.findall(r'<.*>', content):
print(temp)

3.7.2非贪婪模式(.*?)
非贪婪模式则与贪婪模式相反,它尽可能少地匹配字符。它会在找到第一个匹配后尽快停止匹配。
.*? 中的 ? 使得 * 变成了非贪婪模式,表示尽可能少的匹配字符。
import re
content = '<html><head><title>Title</title>'
for temp in re.findall(r'<.*?>', content):
print(temp)

3.8元字符转义(\)
在正则表达式中,元字符是具有特殊含义的字符,它们用于构建复杂的模式。为了匹配这些字符的字面值(即它们本身),我们需要使用转义字符(通常是反斜杠 \)来对这些元字符进行转义。
.\*:表示匹配任意字符(除换行符外),0个或多个,这是贪婪模式的行为,尽可能匹配更多的字符。
\.:表示字面量的点号(.),因为点号在正则表达式中是一个特殊字符,表示匹配任意字符。通过使用反斜杠 \ 来转义,表示字面量的点。
import re
content = '''
苹果.是绿色的
橙子.是橙色的
香蕉.是黄色的
'''
for temp in re.findall(r'.*\.', content):
print(temp)

3.9匹配字符类型
1.匹配字母和数字
[a-z]:匹配小写字母a到z。[A-Z]:匹配大写字母A到Z。[a-zA-Z]:匹配所有字母(大小写不限)。[0-9]:匹配数字0到9。[^a-z]:匹配除小写字母以外的任意字符(^表示取反)。
2.匹配任意单个字符
.:匹配任意字符(除了换行符\n)。例如,a.b可以匹配acb、a1b等,但不会匹配ab(没有中间字符)。
3.匹配字母、数字和下划线
\w:匹配字母、数字或下划线(等价于[a-zA-Z0-9_])。\W:匹配非字母、非数字、非下划线的字符(与\w相反)。
4.匹配空白字符
\s:匹配任何空白字符,包括空格、制表符(tab)、换行符、回车符等。\S:匹配非空白字符。
5.匹配数字
\d:匹配任何数字(等价于[0-9])。\D:匹配任何非数字字符。
6.匹配单词边界
\b:匹配一个单词的边界,例如,在一个单词的开头或结尾。常用于匹配完整的单词。\B:匹配非单词边界。
7.匹配数字、字母或其他字符
\d{n}:匹配正好n个数字。\w{n,}:匹配至少n个字母、数字或下划线。\s+:匹配一个或多个空白字符。
3.10匹配指定字符范围([])
在正则表达式中,方括号 [] 用于指定字符范围或字符集。它允许你匹配一个集合中的任意单个字符。通过方括号,你可以定义特定范围的字符或选择要匹配的字符。
基本语法:
[abc]:匹配字符a、b或c中的任意一个字符。[a-z]:匹配小写字母范围a到z中的任意一个字符。[A-Z]:匹配大写字母范围A到Z中的任意一个字符。[0-9]:匹配数字范围0到9中的任意一个字符。
代码
import re
content = 'a1b2c3d4e5'
# 非数字
print("***********非数字***********")
for temp in re.findall(r'[^\d]', content):
print(temp)
print("***********数字***********")
for temp in re.findall(r'[\d]', content):
print(temp)
print("***********小写字符***********")
for temp in re.findall(r'[a-z]', content):
print(temp)

3.11起始结尾位置与单行多行模式
3.11.1起始位置(^)
^ 元字符用于匹配输入字符串的开始。它确保正则表达式从字符串的开始处进行匹配。
在正则表达式中可以设置单行模式与多行模式。
- 单行模式:表示匹配整个文本的开头位置
- 多行模式:表示匹配文本每行的开头位置
代码
^\d+:正则表达式中的 ^ 表示匹配行的开头,\d+ 匹配一个或多个数字。
re.M:这个标志告诉正则表达式引擎将输入字符串视为多行文本,这样 ^ 会匹配每一行的开头,而不仅仅是字符串的开始。
如果不加re.M,则匹配不到,因为001前还有\n,除非001不换行,紧跟'''
import re
content = '''
001-苹果-60
002-橙子-70
003-香蕉-80
'''
for temp in re.findall(r'^\d+', content, re.M):
print(temp)

3.11.2结尾位置($)
$ 元字符用于匹配字符串的结尾位置。它确保正则表达式只会匹配字符串的最后部分。
代码
\d+:匹配一个或多个数字。
$:匹配行的结尾。
re.M:多行模式标志,这样 ^ 和 $ 就会匹配每一行的开头和结尾,而不仅仅是整个文本的开始和结束。
import re
content = '''
001-苹果-60
002-橙子-70
003-香蕉-80
'''
for temp in re.findall(r'\d+$', content, re.M):
print(temp)

3.12匹配指定多个字符中的其中之一(|)
| 是一个逻辑或运算符,也称为“选择符”或“管道符”。它允许匹配多个不同的模式中的任何一个,类似于逻辑运算中的“或”操作。
/dog|cat/g
I have a cat
I have a dog
I have a bird

3.13分组(())
分组(Grouping)是将多个表达式组合在一起,使得它们作为一个单独的单元进行处理。分组的基本作用是:
- 提取匹配的子字符串。
- 控制模式的优先级。
我们可以在正则表达式中标记多个组。
代码
^:匹配每行的开始。
(?P<user_name>.+):匹配并命名为 user_name 的用户名部分(即 张三、李四 等)。.+ 表示匹配一个或多个字符,直到遇到分隔符 ,。
,:匹配中文逗号。
.+:匹配并忽略手机号码前面的文本(即“手机号码”)。
(?P<mobile>\d{11}):匹配并命名为 mobile 的手机号码部分(即 11 位数字)。
re.M:启用多行匹配模式,使得 ^ 和 $ 能够分别匹配每行的开头和结尾。
re.finditer():与 re.findall() 不同,re.finditer() 返回一个迭代器,每次返回一个 Match 对象,允许我们使用 group() 方法访问各个分组的匹配内容。
temp.group('user_name'):提取命名分组 user_name 的内容。
temp.group('mobile'):提取命名分组 mobile 的内容。
import re
# content = '''苹果,苹果是绿色的
# 橙子,橙子是橙色的
# 香蕉,香蕉是黄色的'''
#
# for temp in re.findall(r'^(.*),', content, re.M):
# print(temp)
import re
content = '''张三,手机号码15945678901
李四,手机号码13945677701
王二,手机号码13845666901'''
# for temp in re.findall(r'^(.+),.+(\d{11})', content, re.M):
# print(temp)
for temp in re.finditer(r'^(?P<user_name>.+),.+(?P<mobile>\d{11})', content, re.M):
print(temp.group('user_name'), temp.group('mobile'))

3.14允许点号匹配所有字符(DOTALL)
正则表达式:class=\"t1\">.*?<a>(.*?)</a>
-
class=\"t1\":匹配class="t1",用于定位包含a标签的部分。 -
>:匹配>符号,紧跟在class="t1"后面,表示该元素的开始。 -
.\*?:匹配零个或多个任意字符(使用非贪婪模式?)。非贪婪模式会让匹配尽可能短,以确保匹配到<a>标签前的部分。 -
<a>(.*?)</a>:匹配 标签中的内容,使用 (.*?) 表示非贪婪模式的匹配,可以提取 标签中的文本。-
.*?表示匹配零个或多个任意字符(非贪婪模式),确保只匹配<a>标签内的内容。 -
(.*?)捕获分组,表示提取<a>标签中的文本。
-
-
re.DOTALL:re.DOTALL使得.可以匹配包括换行符在内的任意字符,否则.默认不会匹配换行符。这个标志允许跨行匹配 HTML 标签中的内容。
import re
content = '''
<div class="el">
<p class="t1">
<span>
<a>Python开发工程师</a>
</span>
</p>
<span class="t2">南京</span>
<span class="t3">1.5-2万/月</span>
</div>
<div class="el">
<p class="t1">
<span>
<a>java开发工程师</a>
</span>
</p>
<span class="t2">苏州</span>
<span class="t3">1.5-2/月</span>
</div>
'''
for temp in re.findall(r'class=\"t1\">.*?<a>(.*?)</a>', content, re.DOTALL):
print(temp)
4.常用字符串处理方式
4.1正则字符串切割
正则表达式:[;,\s]\s*
-
[;,\s]:-
;:匹配分号;。 -
,:匹配逗号,。 -
\s:匹配空格字符(包括空格、制表符、换行符等)。
-
这个部分表示匹配分号、逗号或空白字符(空格、制表符等)。
\s\*:匹配零个或多个空白字符(如空格或制表符)。这样可以处理分隔符后面的空格。
re.split() 会根据正则表达式中的模式在 names 字符串上进行拆分。这里,[;,\s]\s* 会匹配一个分号、逗号或者空白字符(包括后面的空格),并将其作为分隔符来拆分字符串。
import re
names = '关羽; 张飞, 赵云, 马超, 黄忠 李逵'
name_list = re.split(r'[;,\s]\s*', names)
print(name_list)

4.2字符串替换
r'/av\d+/':
/匹配斜杠。av匹配固定字符串av。\d+匹配一个或多个数字。/再次匹配斜杠。
re.sub:在字符串中用指定内容替换匹配的部分。
import re
html_obj = '''
下面是这学期要学习的课程:
<a href='https://www.bilibili.com/video/av66771949/?p=1' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a>
这节讲的是牛顿第2运动定律
<a href='https://www.bilibili.com/video/av46349552/?p=125' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a>
这节讲的是毕达哥拉斯公式
<a href='https://www.bilibili.com/video/av90571967/?p=33' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a>
这节讲的是切割磁力线
'''
data = re.sub(r'/av\d+/', '/cn34567/', html_obj)
print(data)

5.匹配中文
re.compile(r'[\u4e00-\u9fa5]+')
[\u4e00-\u9fa5]:匹配 Unicode 编码范围在\u4e00到\u9fa5的字符。这是中文汉字的范围,覆盖了常见的简体和繁体中文字符。+:匹配前面的规则一次或多次。意味着可以提取连续的中文字符。re.compile:预编译正则表达式,提高匹配效率。
import re
title = '你好,hello,世界, world'
pattern = re.compile(r'[\u4e00-\u9fa5]+')
result = pattern.findall(title)
print(result)

6.实例-爬取36kr新闻首页
使用浏览器切换到移动端

分析页面,需要爬取的是这个<a>标签
<a class="item-info clearfloat" href="/p/3133516613320194" target="_blank" rel="noopener noreferrer"><span class="item-title weight-bold ellipsis-2">瑞士工程科技公司Apheros研发新型金属泡沫,可将数据中心冷却效率提升90% | 瑞士创新100强</span><div class="item-bar"><span class="bar-author" rel="noopener noreferrer">以明科技</span><span class="bar-time"><i class="icon"></i>3分钟前</span></div></a>

代码
<a class="item-info clearfloat" href="([^"]*).*?ellipsis-2">(.*?)</span>.*?</a>
<a class="item-info clearfloat" href="/p/3133516613320194" target="_blank" rel="noopener noreferrer"><span class="item-title weight-bold ellipsis-2">瑞士工程科技公司Apheros研发新型金属泡沫,可将数据中心冷却效率提升90% | 瑞士创新100强</span><div class="item-bar"><span class="bar-author" rel="noopener noreferrer">以明科技</span><span class="bar-time"><i class="icon"></i>3分钟前</span></div></a>
<a class="item-info clearfloat" href="([^"]*):
- 匹配
<a>标签中class="item-info clearfloat"的部分。 - 提取
href="..."中的链接,([^"]*)表示匹配引号内的任意非引号字符,并捕获到第一个分组。
.\*?ellipsis-2">(.\*?)</span>:
- 匹配包含
ellipsis-2的<span>标签中的内容。 .*?是非贪婪匹配,确保匹配到ellipsis-2之后的最近一个>。(.*?)捕获<span>标签中内容的最小匹配。
.\*?</a>:
- 匹配到整个
</a>标签结束。
import requests
import re
url = 'https://m.36kr.com/'
headers = {
"User-Agent":
"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Mobile Safari/537.36"
}
r = requests.get(url, headers=headers).text
item_list = re.findall(r'<a class="item-info clearfloat" href="([^"]*).*?ellipsis-2">(.*?)</span>.*?</a>', r)
for temp in item_list:
print(url + temp[0], temp[1])

📌 创作不易,感谢支持!
每一篇内容都凝聚了心血与热情,如果我的内容对您有帮助,欢迎请我喝杯咖啡☕,您的支持是我持续分享的最大动力!

浙公网安备 33010602011771号