Python正则表达式入门:从匹配HTML标签到网页内容抓取
·
正则表达式不是抓取网页的最佳工具,但它是理解网页结构最快的方式。掌握正则后,你能在不依赖任何第三方库的情况下,快速定位和提取页面中的关键信息。
一、先搞懂5个核心元字符
抓取网页只需要记住这几个:
| 符号 | 含义 | 网页场景举例 |
|---|---|---|
| . | 匹配任意单个字符 | 匹配任意标签内容 |
| * | 匹配前面的字符0次或多次 | 匹配可有可无的属性 |
| + | 匹配前面的字符1次或多次 | 匹配必有的内容 |
| ? | 匹配前面的字符0次或1次 | 匹配可选的斜杠 |
| () | 分组,提取内容 | 提取链接地址 |
量词补充:{n,m} 表示匹配n到m次。比如 \d{11} 就是匹配11位数字,正好用来匹配手机号。
二、匹配HTML标签:从最简单的开始
HTML标签的基本结构是 <标签名 属性="值">内容</标签名>。
匹配任意HTML标签
import re
html = '<div class="content">Hello</div><p>World</p>'
# 匹配所有标签
pattern = r'<[^>]+>'
result = re.findall(pattern, html)
print(result)
# 输出: ['<div class="content">', '</div>', '<p>', '</p>']
解释:[^>]+ 的意思是"匹配除了 > 之外的一个或多个字符"。因为HTML标签的结束标志是 >,所以这个写法能准确捕获从 < 开始到 > 结束的完整标签。
匹配指定标签
比如只想拿到所有的链接:
html = '<a href="https://example.com">点击</a><a href="/about">关于</a>'
pattern = r'<a\s+href="([^"]+)"'
links = re.findall(pattern, html)
print(links)
# 输出: ['https://example.com', '/about']
这里用了 () 括号做分组,re.findall 会自动返回括号内匹配到的内容,也就是链接地址本身,而不是整个a标签。
三、提取网页内容:三个高频场景
场景1:提取所有图片地址
html = '<img src="/static/logo.png" alt="logo"><img src="banner.jpg">'
pattern = r'<img\s+[^>]*src="([^"]+)"'
images = re.findall(pattern, html)
print(images)
# 输出: ['/static/logo.png', 'banner.jpg']
注意:[^>]* 用来跳过src前面可能存在的其他属性(比如alt、class等),这是实战中最容易踩坑的地方。很多教程只写 <img src="">,但真实网页的属性顺序是不固定的。
场景2:提取标题和正文
html = '<h1>Python正则表达式教程</h1><p>这是第一段内容。</p><p>这是第二段。</p>'
title = re.search(r'<h1>(.*?)</h1>', html).group(1)
paragraphs = re.findall(r'<p>(.*?)</p>', html)
print(title) # Python正则表达式教程
print(paragraphs) # ['这是第一段内容。', '这是第二段。']
关键点:用 .*? 而不是 .*。? 让匹配变成"非贪婪模式",即遇到第一个 </h1> 就停止,而不是一直匹配到最后一个 </h1>。
场景3:清洗HTML标签,只留文本
html = '<div>保留这个<span>还有这个</span></div><script>删除这个</script>'
# 移除所有标签
text = re.sub(r'<[^>]+>', '', html)
print(text)
# 输出: 保留这个还有这个删除这个
re.sub 是替换函数,把所有匹配到的标签替换为空字符串,剩下的就是纯文本。
四、一个完整的实战:抓取豆瓣电影Top250的电影名和评分
import re
# 假设这是从网页获取到的HTML片段
page_html = '''
<div class="item">
<span class="title">肖申克的救赎</span>
<span class="rating_num" property="v:average">9.7</span>
</div>
<div class="item">
<span class="title">霸王别姬</span>
<span class="rating_num" property="v:average">9.6</span>
</div>
'''
# 提取电影名和评分
pattern = r'<span class="title">(.*?)</span>.*?<span class="rating_num"[^>]*>(.*?)</span>'
results = re.findall(pattern, page_html, re.DOTALL)
for name, rating in results:
print(f'{name}: {rating}分')
输出:
肖申克的救赎: 9.7分
霸王别姬: 9.6分
re.DOTALL 让 . 可以匹配换行符,否则中间的换行会导致匹配失败。这是处理多行HTML时必须加的参数。
五、正则的局限性:什么时候该放弃它
正则能处理简单的HTML提取,但遇到以下情况会很痛苦:
- 标签嵌套超过2层(比如div里面套div再套div)
- 属性值里包含引号或特殊字符
- HTML格式不规范(浏览器能渲染但正则匹配不了)
这些情况下,请直接用BeautifulSoup或lxml。正则的定位是"快速处理简单场景",不是"万能解析器"。
推荐切换时机:当你发现正则写到第三层嵌套还匹配不对的时候,就是该换工具的时候。
六、速查表
| 需求 | 正则表达式 |
|---|---|
| 匹配任意标签 | <[^>]+> |
| 提取a标签链接 | <a\s+href="([^"]+)" |
| 提取img的src | <img\s+[^>]*src="([^"]+)" |
| 提取标签内文本(非贪婪) | <tag>(.*?)</tag> |
| 移除所有HTML标签 | re.sub(r'<[^>]+>', '', html) |
| 匹配11位手机号 | 1[3-9]\d{9} |
把这张表存下来,够用80%的网页抓取场景。
更多推荐
所有评论(0)