正则表达式不是抓取网页的最佳工具,但它是理解网页结构最快的方式。掌握正则后,你能在不依赖任何第三方库的情况下,快速定位和提取页面中的关键信息。


一、先搞懂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%的网页抓取场景。

更多推荐