Python爬虫-正则表达式提取数据

前言

人不可以一时得志,而自夸其能

亦不可以一时失意,而自坠其志

流水不争先,争的是滔滔不绝

⚠️声明:本文所涉及的爬虫技术及代码仅用于学习、交流与技术研究目的,禁止用于任何商业用途或违反相关法律法规的行为。若因不当使用造成法律责任,概与作者无关。请尊重目标网站的robots.txt协议及相关服务条款,共同维护良好的网络环境。

1.正则表达式

正则表达式(Regular Expression,简称正则或RegEx)是一种用于匹配字符串的模式,它可以帮助我们进行字符串的搜索、替换、分割等操作。正则表达式由普通字符和特殊字符组成,用于描述和匹配文本模式。

常见语法

一般字符 匹配规则 表达式 完整匹配的字符串
. 匹配任意除换行符\n外的字符 a.c abc
\ 转义字符,使后面一个字符改变原来的意思。如果字符串有字符*需要匹配,则可以使用\* a\.c a.c
[...] 字符集(字符类)。对应的位置可以使字符集中任意字符。字符集中的字符可以逐个列出,也可以给定范围:[abc][a-c]。第一个字符如果是^则表示取反:[^abc]表示不是abc的其他字符 a[bcd]e abeaceade
\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* ababccccc
+ 匹配前一个字符1次或者无数次 abc+ abcabccccc
? 匹配前一个字符0次或者1次 abc? ababc
{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)或小数点(.)。

+:表示前面的字符集合(即数字或小数点)可以出现一次或多次,至少要有一个字符。这里它用于匹配工资金额,如 22.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)

image-20250121211022374

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

地址:https://regexr-cn.com/

image-20250121211509464

3.常见语法

3.1普通字符匹配

比如匹配Python

image-20250121211639561

元字符不能直接匹配,需要转义

. * + ? \ [] ^ $ {} | ()

3.2通配符-单个字符(.)

.(点号):表示匹配 任意单个字符(除了换行符)。

匹配颜色

/.色/g

苹果是绿色的
橙子是橙色的
香蕉是黄色的
乌鸦是黑色的

image-20250121211905889

代码

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

image-20250121212336380

3.3通配符-0次或多次(*)

*(星号):表示前面的元素可以出现 0次或多次。它是最常用的通配符之一。

/,.*/g

苹果,是绿色的
橙子,是橙色的
香蕉,是黄色的
乌鸦,是黑色的
枫叶,是红色的

image-20250121212611469

代码

import re

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

image-20250121212812669

3.4通配符-1次或多次(+)

+(加号):表示前面的元素可以出现 1次或多次。它要求至少匹配一次前面的字符。

/,.+/g

苹果,是绿色的
橙子,是橙色的
香蕉,是黄色的
乌鸦,是黑色的
枫叶,是红色的
大海,

image-20250121213020855

代码

import re

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

image-20250121213223193

3.5通配符-0次或1次(?)

?(问号):表示前面的元素可以出现 0次或1次。即前面的字符是可选的。

/,.?/g

苹果,是绿色的
橙子,是橙色的
香蕉,是黄色的
乌鸦,是黑色的
枫叶,是红色的
大海,

image-20250121222151336

3.6执行次数({})

{}(花括号) 是一个量词,用于指定前面的元素 重复的次数

/油{3,4}/g:匹配 连续3到4个 字符

/油{3}/g::匹配 恰好3个连续的 字符

/油{3,4}/g

红彤彤,绿油油,黑乎乎,绿油油油油

image-20250121221116472

代码

import re

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

image-20250121221319732

3.7贪婪模式和非贪婪模式

3.7.1贪婪模式(.*)

贪婪模式是正则表达式的默认行为,它会尽可能匹配更多的字符,直到满足整个模式的条件为止。也就是说,贪婪模式会尽量“吃掉”所有可能匹配的字符,以便匹配整个表达式。

.* 代表匹配零个或多个字符(任意字符),贪婪模式会匹配尽可能多的字符。

import re
content = '<html><head><title>Title</title>'
for temp in re.findall(r'<.*>', content):
    print(temp)

image-20250121221455255

3.7.2非贪婪模式(.*?)

非贪婪模式则与贪婪模式相反,它尽可能少地匹配字符。它会在找到第一个匹配后尽快停止匹配。

.*? 中的 ? 使得 * 变成了非贪婪模式,表示尽可能少的匹配字符。

import re
content = '<html><head><title>Title</title>'
for temp in re.findall(r'<.*?>', content):
    print(temp)

image-20250121221718524

3.8元字符转义(\)

在正则表达式中,元字符是具有特殊含义的字符,它们用于构建复杂的模式。为了匹配这些字符的字面值(即它们本身),我们需要使用转义字符(通常是反斜杠 \)来对这些元字符进行转义。

.\*:表示匹配任意字符(除换行符外),0个或多个,这是贪婪模式的行为,尽可能匹配更多的字符。

\.:表示字面量的点号(.),因为点号在正则表达式中是一个特殊字符,表示匹配任意字符。通过使用反斜杠 \ 来转义,表示字面量的点。

import re
content = '''
苹果.是绿色的
橙子.是橙色的
香蕉.是黄色的
'''
for temp in re.findall(r'.*\.', content):
    print(temp)

image-20250121222526499

3.9匹配字符类型

1.匹配字母和数字

  • [a-z]:匹配小写字母 az
  • [A-Z]:匹配大写字母 AZ
  • [a-zA-Z]:匹配所有字母(大小写不限)。
  • [0-9]:匹配数字 09
  • [^a-z]:匹配除小写字母以外的任意字符(^ 表示取反)。

2.匹配任意单个字符

  • .:匹配任意字符(除了换行符\n)。例如,a.b 可以匹配 acba1b 等,但不会匹配 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]:匹配字符 abc 中的任意一个字符。
  • [a-z]:匹配小写字母范围 az 中的任意一个字符。
  • [A-Z]:匹配大写字母范围 AZ 中的任意一个字符。
  • [0-9]:匹配数字范围 09 中的任意一个字符。

代码

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)

image-20250121234921238

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)

image-20250121235338403

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)

image-20250121235911504

3.12匹配指定多个字符中的其中之一(|)

| 是一个逻辑或运算符,也称为“选择符”或“管道符”。它允许匹配多个不同的模式中的任何一个,类似于逻辑运算中的“或”操作。

/dog|cat/g

I have a cat
I have a dog
I have a bird

image-20250122000417255

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'))

image-20250122001042823

3.14允许点号匹配所有字符(DOTALL)

正则表达式class=\"t1\">.*?<a>(.*?)</a>

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)

image-20250122002405596

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)

image-20250122142555003

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)

image-20250122143328762

6.实例-爬取36kr新闻首页

地址:https://m.36kr.com/

使用浏览器切换到移动端

image-20250122143711339

分析页面,需要爬取的是这个<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>

image-20250122144018566

代码

<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])

image-20250122144823088

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

wxzf
posted @ 2025-04-08 22:14  peng_boke  阅读(179)  评论(0)    收藏  举报