爬虫代码里,真正让人纠结的往往不是请求发不出去。
requests.get() 写完,页面拿到了,剩下的问题基本都会落到一个地方:
这一堆 HTML,我到底怎么把想要的数据抠出来?
刚开始写爬虫的时候,我也是正则一把梭。
看到页面源码:
<div class="product">
<h2>机械键盘</h2>
<span class="price">299</span>
</div>
第一反应:"这不就是几个标签嘛,正则搞一下。"
结果线上跑一晚上,第二天发现采集数量少了一半。
原因很简单,网页结构稍微变一下,正则表达式直接失效。
后来慢慢发现,爬虫里的选择器没有绝对好坏,更多是看场景。
简单文本,用正则。结构化 HTML,用 BS4。层级复杂,XPath。前端页面喜欢动态加 class,CSS 选择器反而更舒服。
下面把这几个常用方式逐一捋一下。
一、正则表达式:快,但别拿它解析整棵 DOM
正则最大的优势就是快。
比如抓接口返回的数据:
订单编号:A20260928001
金额:399
状态:已支付
这种文本,没有什么 HTML 层级,用正则非常合适。
比如需要提取订单编号:
import re
text = """
订单编号:A20260928001
金额:399
状态:已支付
"""
order_id = re.search(r'订单编号:(\w+)', text)
if order_id:
print(order_id.group(1))
输出:
A20260928001
这个场景正则很舒服。
但是很多新人喜欢这样:
html = """
<div>
<span class="name">Python教程</span>
</div>
"""
name = re.findall(
r'<span class="name">(.*?)</span>',
html
)
看起来没问题。但真实网站里经常这样:
<span class="name">
Python教程
</span>
多一个换行,凉。
再比如:
<span class="name" data-id="1">
Python教程
</span>
属性顺序变一下,又凉。
所以我的习惯是:正则负责抓规律,不负责理解 HTML。 不要让正则去干 DOM 解析的活。简单文本场景用正则就够了,真要去解析结构化页面,下面几个工具更靠谱。
二、BeautifulSoup:处理普通网页,我最常用
BS4 最大的优点就是简单。网页结构清楚的时候,它写起来很舒服。
安装:
pip install beautifulsoup4
比如抓商品名称和价格:
from bs4 import BeautifulSoup
html = """
<div class="item">
<h3>无线耳机</h3>
<span class="price">199</span>
</div>
<div class="item">
<h3>机械键盘</h3>
<span class="price">299</span>
</div>
"""
soup = BeautifulSoup(html, "html.parser")
for item in soup.find_all("div", class_="item"):
title = item.find("h3").text.strip()
price = item.find(
"span",
class_="price"
).text.strip()
print(title, price)
结果:
无线耳机 199
机械键盘 299
这个写法比较接近人的阅读方式:先找到商品节点,再进去找字段,维护起来也简单。
不过 BS4 有一个问题:当页面层级非常深的时候,代码容易套娃。
比如:
div
└── section
└── ul
└── li
└── a
└── span
你可能会写:
data.find("section")\
.find("ul")\
.find("li")\
.find("a")\
.find("span")
写到后面自己都不想看。这种时候我一般换 XPath。
三、XPath:复杂页面的利器
XPath 本质是通过路径定位节点。浏览器开发者工具里复制 XPath,很多时候可以直接用。
比如:
<div id="content">
<ul>
<li>
<a>
Python爬虫
</a>
</li>
</ul>
</div>
XPath 写法:
from lxml import etree
html = """
<div id="content">
<ul>
<li>
<a>
Python爬虫
</a>
</li>
</ul>
</div>
"""
tree = etree.HTML(html)
result = tree.xpath(
'//div[@id="content"]//a/text()'
)
print(result)
输出:
['Python爬虫']
XPath 有几个地方特别好用。
模糊匹配:
tree.xpath(
'//div[contains(@class,"product")]'
)
取属性:
tree.xpath('//img/@src')
根据文本找:
tree.xpath(
'//a[contains(text(),"Python")]'
)
实际工作中,如果遇到电商列表页、新闻列表页这种结构复杂的页面,我基本优先考虑 XPath,因为它的表达能力更强。
四、CSS 选择器:前端开发喜欢,爬虫也很好用
CSS 选择器其实和浏览器里的写法一样。如果你熟悉前端,这个上手非常快。
例如:
<div class="article">
<h1>Python爬虫入门</h1>
</div>
使用 CSS:
from bs4 import BeautifulSoup
html = """
<div class="article">
<h1>
Python爬虫入门
</h1>
</div>
"""
soup = BeautifulSoup(html, "html.parser")
title = soup.select_one(".article h1")
print(title.text.strip())
输出:
Python爬虫入门
几个常用写法:
class 选择器:
.box
id 选择器:
#main
标签后代:
div span
属性选择:
input[type="text"]
CSS 选择器最大的优势就是短。
很多时候 XPath 写一长串:
//div[@class='list']/ul/li/a/span
CSS 只需要:
.list li a span
看起来舒服很多。
不过 CSS 也有缺点。一些复杂定位,比如"找第三个 li 里面,文本包含 xxx 的节点",XPath 会更直接。
我自己项目里的选择顺序
如果只是抓接口返回的数据,直接正则。
比如:
import re
def parse_token(response):
match = re.search(
r'"token":"(.*?)"',
response
)
return match.group(1) if match else None
页面 HTML 比较规整,用 BS4:
soup.select_one(".title")
页面结构复杂,用 XPath:
tree.xpath(
'//div[contains(@class,"news")]/a/@href'
)
需要快速写,用 CSS:
soup.select(".item .name")
没有哪个选择器能吃遍所有场景。我见过不少爬虫项目,最大的问题不是不会写,而是一开始工具就选错了。拿正则硬拆 HTML,后面维护成本非常高——今天页面还能跑,过两个月换个模板,可能整个采集任务直接废掉。
最后给一个简单判断:
| 场景 |
推荐 |
| 提取固定格式文本 |
正则 |
| 普通 HTML 页面 |
BS4 |
| 页面层级复杂 |
XPath |
| 熟悉前端选择规则 |
CSS |
写爬虫,真正省时间的不是多会几个库,而是看到页面结构以后,知道什么时候该换工具。选对正则处理纯文本、用 BS4 搞定规整 HTML、遇到深层级页面换 XPath、前端动态 class 多的时候上 CSS 选择器,整个采集流程会顺畅不少。选择器只是手里的扳手,别拿螺丝刀去拧所有螺丝。更多爬虫实战技巧和项目经验,也可以到云栈社区和技术人一起交流。