5月31日 01:07

Scrapy 如何用选择器解析网页内容?

Scrapy 解析网页内容主要靠 Selector,它把响应内容包装成可以用 CSS、XPath 和正则提取的对象。选择器写得好,爬虫会很稳定;选择器写得太依赖页面样式,前端一改 class 名就会全线失效。实际开发里不要纠结“CSS 一定比 XPath 简单”或者“XPath 一定更强”,更重要的是看页面结构、字段稳定性和后续维护成本。写选择器前先在 Scrapy shell 里试几条真实页面,比直接在代码里盲改更省时间,也能提前发现编码、重定向和空页面问题。列表页、详情页、分页、隐藏字段都可能需要不同写法,边界是:如果数据来自异步接口而不是 HTML,优先抓接口,别硬从渲染后的 DOM 里抠。

追问

CSS 选择器和 XPath 应该怎么选?

CSS 选择器写起来更直观,适合按 class、id、标签层级提取内容,团队里前端背景的人也容易维护。XPath 的优势是表达能力更强,能按文本、位置、祖先节点、兄弟节点做更精确的定位。取舍上,结构简单就用 CSS,遇到“找到包含某段文字的标题,再取它后面的价格”这类需求,用 XPath 会少绕很多弯。踩坑点是过度依赖层级路径,例如 div > div > div:nth-child(2),页面插一个广告位就会错位。更稳的方式是先找业务语义明显的容器,再在容器内取标题、链接和价格,避免整页范围内抓到推荐位或页脚里的相似元素。

python
def parse(self, response): for card in response.css("div.product-card"): yield { "title": card.css("h2::text").get(default="").strip(), "price": card.xpath(".//span[contains(@class, 'price')]/text()").get(), "url": response.urljoin(card.css("a::attr(href)").get()) }

.get().getall().extract() 有什么区别?

现在更推荐用 .get().getall(),它们语义更清楚:前者拿第一个结果,后者拿全部结果。.extract() 是旧写法,仍然能用,但新代码里没有必要继续混用,团队维护时容易让人误判返回类型。边界在于 .get() 没匹配到会返回 None,后续直接 .strip() 就会报错,所以要加默认值或单独判断。很多解析 bug 不是选择器错了,而是默认值没处理好,导致某一条脏数据让整个任务中断。字段进入 Item 前最好统一做清洗,例如去空白、补全 URL、规范日期格式,这些步骤比事后在数据库里修数据可靠得多。

python
title = response.css("h1::text").get(default="").strip() tags = [x.strip() for x in response.css(".tag::text").getall() if x.strip()]

如何让选择器更抗页面改版?

优先选择语义稳定的属性,比如 data-id、itemprop、aria-label、固定 URL 结构,而不是只看样式 class。很多网站的 class 是构建工具生成的,今天叫 css-a1b2,下次发布就变成另一个值,用它做选择器风险很高。取舍上,选择器写得宽一点能抗改版,但可能误抓广告、推荐位和隐藏模板;写得窄一点更准确,却更容易被结构变动打断。实战里可以先定位稳定容器,再在容器内做相对选择,避免全局搜索抓到重复字段。

python
for item in response.css("article[data-id]"): title = item.css("[itemprop='headline']::text, h2::text").get(default="").strip() if not title: continue yield {"title": title}

正则选择器适合用在哪些地方?

正则适合从一段文本里提取格式明确的值,比如脚本里的 JSON 字段、价格数字、日期、页面内嵌 ID。它不适合替代 HTML 解析,因为用正则匹配嵌套标签通常会变得脆弱又难读。边界是正则要尽量约束上下文,别写一个过宽的 .* 去吞整页内容,否则页面稍微变大就可能性能变差。踩坑最多的是拿到转义后的 JSON 字符串却没有反转义,或者把多个相似字段里的第一个误当成目标字段。

python
import json raw = response.xpath("//script[contains(., 'window.__DATA__')]/text()").re_first(r"window\.__DATA__\s*=\s*(\{.*\})") if raw: data = json.loads(raw)

解析结果怎么做质量检查?

选择器写完后,至少要检查必填字段是否为空、列表数量是否异常、URL 是否能补全、价格或日期格式是否符合预期。不要等数据入库后才发现标题全是空字符串,那时排查成本会高很多。取舍上,严格校验能尽早发现页面变动,但也可能丢掉少量字段不完整的正常数据;宽松校验吞吐高,却容易把坏数据带到下游。常见做法是必填字段缺失就丢弃并打日志,非核心字段允许为空,但要在统计里看到缺失比例。如果某次发布后缺失率突然升高,就应该暂停入库或降级为抽样抓取,先确认页面结构是否变化。

结论

Scrapy 选择器的核心不是背语法,而是写出稳定、可读、可验证的提取逻辑。CSS、XPath、正则各有位置:CSS 负责常规结构,XPath 处理复杂关系,正则只提取文本里的明确模式。解析代码一旦进入生产,就要配合默认值、校验和日志,否则页面一次小改版就可能让数据悄悄变脏。

标签:Scrapy