2.3 Selectors:到站后的拆包动作


文档摘要

2.3 Selectors:到站后的拆包动作 本节摘要:选择器是从响应里拆出数据的唯一工具,Scrapy 基于 parsel 提供 CSS 与 XPath 两套语法。本节讲两套语法的舒适区分工、get 与 getall 的取值纪律,以及"先在 shell 里调通、再写回 Spider"的标准工作流——这是到站拆包环节效率最高的做法。 车票到站,包裹堆在月台上。这一节解决第 2 章第三个问题:怎么把网页拆成结构化数据。拆包动作看似琐碎,实则有一套可以少走弯路的工作方法——先选语法、再定取值口径、最后用试验台验证。 两套语法,两个舒适区 CSS 选择器写起来短,适合按类名、标签、层级取节点;XPath 表达能力强,能沿轴反向查找、按文本定位、取属性。

2.3 Selectors:到站后的拆包动作

本节摘要:选择器是从响应里拆出数据的唯一工具,Scrapy 基于 parsel 提供 CSS 与 XPath 两套语法。本节讲两套语法的舒适区分工、get 与 getall 的取值纪律,以及"先在 shell 里调通、再写回 Spider"的标准工作流——这是到站拆包环节效率最高的做法。

车票到站,包裹堆在月台上。这一节解决第 2 章第三个问题:怎么把网页拆成结构化数据。拆包动作看似琐碎,实则有一套可以少走弯路的工作方法——先选语法、再定取值口径、最后用试验台验证。

两套语法,两个舒适区

CSS 选择器写起来短,适合按类名、标签、层级取节点;XPath 表达能力强,能沿轴反向查找、按文本定位、取属性。我的习惯是:结构定位用 CSS,一旦需要"根据文本找节点"或"向上找父级",立刻切 XPath。

# CSS 舒适区:结构定位 response.css("div.quote") # 所有引用块 response.css("div.quote span.text::text") # 进入文本节点:::text response.css("a::attr(href)") # 取属性:::attr(名字) # XPath 舒适区:文本定位与轴查找 response.xpath('//span[@class="text"]/text()') response.xpath('//a[contains(text(), "下一页")]/@href') # 按文本找链接 response.xpath('//div[h3="书名"]/parent::div') # 由子及父

两套语法可以混用,中间用 xpath 或 css 方法接力:

# CSS 定位区块,XPath 在区块内按文本精确定位 for card in response.css("div.product"): name = card.xpath('.//h3[text()="书名"]/following-sibling::dd[1]/text()').get()

get 与 getall:取值口径的纪律

取值方法的口径必须在写代码前定好:get 取第一条、没有就 None;getall 取全部、没有就空列表。口径混乱是选择器代码最常见的 bug 来源——对列表用 get 只拿到一条,对唯一元素用 getall 又得到单元素列表,下游管道处理两种形状的数据时必然出错。

titles = response.css("h3.book-title") # SelectorList,先圈后取 first_title = titles.get() # 第一条或 None all_titles = titles.getall() # 全部,或空列表 # 直接在结果上链式取值也可以,但别混淆两种返回形状 price = response.css("p.price::text").get() # '£23.19' 或 None prices = response.css("p.price::text").getall() # ['£23.19'] 或 []

工程上的纪律:列表型字段一律 getall 加循环,标量字段一律 get 加判空。字段口径在 Items(2.4 节)里固化成模型后,这个问题会自然消失。

标准工作流:shell 试验台先行

别在 Spider 里盲写选择器然后整程重跑。正确动作是先进交互终端,把选择器调到能出数据,再搬回代码:

$ scrapy shell "https://books.example.com/list" >>> response.status 200 >>> response.css("article.product_pod h3 a::attr(title)").getall()[:3] ['A Light in the Attic', 'Tipping the Velvet', 'Soumission'] >>> response.css("p.price::text").getall()[:2] ['£51.77', '£23.19'] >>> response.xpath('//a[text()="next"]/@href').get() 'catalogue/page-2.html'

试验台上还常用一个技巧:页面存到本地反复试验,避免对目标站点重复请求:

>>> fetch("https://books.example.com/list") # 拉一次 >>> import scrapy; open("page.html","wb").write(response.body) 50234 # 之后可用 scrapy shell page.html 反复试验,不碰远端

💡 关键直觉:选择器调不通的第一反应不该是改语法,而是先确认"数据在不在原始 HTML 里"。如果浏览器看得到、shell 里看不到,那是动态渲染问题,属于第 5 章的范畴——选择器再怎么改都没用。

完整拆包示例:从选择器到字典

把上面验证过的选择器组装成解析回调,这是到站拆包的完整形态:

def parse(self, response): for card in response.css("article.product_pod"): yield { "title": card.css("h3 a::attr(title)").get(default="").strip(), "price": card.css("p.price::text").get(default="").replace("£", ""), "stock": card.css(".instock.availability::text").get(default="").strip(), "detail_url": card.css("h3 a::attr(href)").get(), } next_page = response.xpath('//a[text()="next"]/@href').get() if next_page: yield response.follow(next_page, callback=self.parse)

运行统计里,item_scraped_count 的数字就是拆包产出。若它远小于预期而日志无报错,第一件事是回 shell 检查选择器是否被页面改版击穿——拆包代码是全工程最依赖站点现状的部分,改版即失效,这也是第 5 章"站点分析"要建立监控意识的原因。

本节要点回顾

  • 两套语法两个舒适区:CSS 管结构定位,XPath 管文本定位与轴查找,可接力混用;
  • 取值口径先行:列表用 getall、标量用 get,返回形状在源头定死;
  • shell 先行:选择器在试验台调通再进 Spider,必要时存页面离线反复试验;
  • 拆包失效先查数据在不在:渲染问题不是选择器问题。

拆出来的数据现在是裸字典。下一节给它们上户口——Item 数据模型,让整个下游对字段口径有据可依。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U