爬虫网页抓取:Python中的CSS和XPath选择器
Senior Web Scraping Engineer
摘要:
- parsel 使用 CSS 和 XPath 从 HTML 中选择数据,在同一个文档上 — 它是 Scrapy 使用的选择器引擎,作为独立库可用。
- parsel 不执行抓取。 它没有 HTTP 客户端,也不运行 JavaScript;您提供标记,它构建一个可查询的树。
- 这个差距出现在一个脚本中。 对于一个 JavaScript 渲染的演示页面,普通的 GET 请求给 parsel 0 个引用节点;通过 Scrapeless Universal Scraping API 使用
js_render抓取同一 URL 则得到所有 10 个。 - CSS 和 XPath,齐头并进。 实时运行通过两种方式提取相同的 10 个作者 —
sel.css("small.author::text")和sel.xpath("//small[@class='author']/text()")— 从渲染后的 HTML 中。 - 这两层保持分开。 Scrapeless 负责抓取和渲染;parsel 进行选择。两者之间没有交集。
- 可自由开始抓取一侧。 在 app.scrapeless.com 创建您的 Scrapeless API 密钥。
parsel 是什么,以及它不是
parsel 是一个用于从 HTML 和 XML 中提取数据的 Python 库,使用 CSS 选择器和 XPath 表达式。它是构建 Scrapy 的选择器层,打包使您可以在任何地方使用,它在底层封装了 lxml,因此这两种选择器语言都会在相同的快速解析树上运行。选择它而不是仅 CSS 的解析器的原因是 XPath:当一个字段是通过其位置、文本或与邻居的关系而非类定义时,XPath 可以表达,而 CSS 不能。
它是一个选择器库,仅此而已。parsel 没有 HTTP 客户端,不持有会话,也不运行 JavaScript。给它一个字符串,它构建一个您可以查询的 Selector;让它去抓取一个 URL 时,没有相应的方法。因此,每个“parsel 网络抓取”设置是两层:返回真实 HTML 的内容和从中进行选择的 parsel。本指南使用 Scrapeless Universal Scraping API 作为第一层,因为普通的 HTTP 客户端在任何通过 JavaScript 构建内容的页面上返回的是预渲染的标记。更广泛的 Python 网络抓取教程 涵盖了周边生态。
安装
parsel 和 requests 是整个工具链。本指南是基于 parsel 1.11.0 编写的:
bash
pip install "parsel==1.11.0" requests
将您的密钥保存在环境中,绝对不要在源代码中:
bash
export SCRAPELESS_API_KEY="sk_your_scrapeless_key"
获取值得解析的 HTML
选择质量受抓取保真度的限制,因此从这里开始。在一个 JavaScript 渲染的页面上,普通 HTTP 客户端收到的标记不是读者所看到的标记:内容仅在脚本构建 DOM 之后到达,这个生命周期由 HTML 脚本规范 定义。一个脚本计算通过 parsel 自身的差异:
python
# fidelity.py — parsel 看到的内容:普通 GET 与服务器端渲染
import os
import requests
from parsel import Selector
URL = "https://quotes.toscrape.com/js/"
plain = requests.get(URL, timeout=60).text
print("plain GET chars:", len(plain), "| quote nodes:", len(Selector(text=plain).css("div.quote")))
resp = requests.post(
"https://api.scrapeless.com/api/v2/unlocker/request",
headers={"Content-Type": "application/json", "x-api-token": os.environ["SCRAPELESS_API_KEY"]},
json={"actor": "unlocker.webunlocker", "input": {"url": URL, "js_render": True}},
timeout=120,
)
resp.raise_for_status()
rendered = resp.json().get("data", "")
print("rendered chars:", len(rendered), "| quote nodes:", len(Selector(text=rendered).css("div.quote")))
运行结果显示普通抓取的引用节点为 0,而渲染后的为 10:
text
plain GET chars: 5806 | quote nodes: 0
rendered chars: 8940 | quote nodes: 10
渲染、解锁和代理路由都在服务器端的那一个 POST 中进行 — Universal Scraping API 是抓取层,而渲染后的标记是 parsel 应当选择的内容。
用 CSS 和 XPath 提取
通过真实的 HTML,parsel 完成提取。css 和 xpath 都返回一个 SelectorList; get 返回第一个匹配项,getall 返回每个匹配项。::text 伪元素和 text() 节点测试都可以提取文本,因此您可以通过两种方式读取相同字段 — CSS 遵循 W3C 选择器规范,而 XPath 遵循 W3C XPath 规范:
python
# extract.py — 抓取渲染后的页面,然后使用 CSS 和 XPath 选择
import os
import requests
从parsel导入选择器
响应 = requests.post(
"https://api.scrapeless.com/api/v2/unlocker/request",
headers={"Content-Type": "application/json", "x-api-token": os.environ["SCRAPELESS_API_KEY"]},
json={"actor": "unlocker.webunlocker", "input": {"url": "https://quotes.toscrape.com/js/", "js_render": True}},
timeout=120,
)
响应.raise_for_status()
sel = 选择器(文本=响应.json().get("data", ""))
css_nodes = sel.css("div.quote")
xpath_authors = sel.xpath("//div[@class='quote']//small[@class='author']/text()").getall()
print("css 引用节点:", len(css_nodes))
print("xpath 作者:", len(xpath_authors))
记录 = []
对于引用中的 css_nodes:
记录.append({
"text": 引用.css("span.text::text").get(),
"author": 引用.xpath(".//small[@class='author']/text()").get(),
"tags": 引用.css("a.tag::text").getall(),
})
print("第一位作者:", 记录[0]["author"])
print("第一位标签:", 记录[0]["tags"])
上述程序的实际运行选择了全部10条记录,CSS和XPath返回相同的作者:
text
css 引用节点: 10
xpath 作者: 10
第一位作者: 阿尔伯特·爱因斯坦
第一位标签: ['变化', '深思', '思考', '世界']
这就是整个爬虫:一次POST请求以获取并渲染,一次选择器进行查询。对于简单字段使用CSS,对于位置字段使用XPath — quote.xpath("...")是相对于每个节点运行的 — 这种模式使得爬虫在页面变得复杂时仍然可读。
在免费计划中获取您的API密钥:app.scrapeless.com
高级模式
- 当CSS不够时,选择XPath。 通过文本选择(
//a[contains(text(), "Next")])、按位置选择((//tr)[2])或通过轴选择(following-sibling::td)都是XPath的领域;CSS没有等效的方式。 - 相对于节点链选择器。
quote.css(...)和quote.xpath(".//...")都是针对该节点的范围 — XPath中的前导.保持相对,这是“此引用中的作者”和“页面上的所有作者”之间的区别。 - 使用
get(default="")来避免None。sel.css("span.missing::text").get(default="")返回一个空字符串而不是None,这防止了在不均匀记录的循环中因为奇特页面而崩溃。 - 使用
::attr()或@来提取属性。sel.css("a::attr(href)")和sel.xpath("//a/@href")都读取一个属性;使用选择器其他部分已经使用的语言。
故障排除
- 从您在浏览器中可以看到的页面中获得零节点。 内容是JavaScript渲染的,而您的获取返回了预渲染的HTML。以已知选择器计数,第一脚本的方法;几乎为空的树是获取问题,用
js_render修复,而不是选择器问题。 get()返回None。 选择器没有匹配到任何内容。检查已渲染的标记,确认类或路径,并传递一个default以免后续代码因为缺失而崩溃。- XPath返回元素时您想要文本。 在路径中添加
/text()或在::textCSS选择器上使用.get();裸元素路径返回节点,而不是它的字符串。 - 相对XPath抓取整个页面。 以
//开头的路径是绝对的,即使在节点上调用。用.前缀 —.//small— 将其范围限制到当前元素。
结论
parsel凭借其作为选择层的地位,能够使用两种方言:CSS适用于常见情况,XPath适用于CSS无法到达的情况,通过一个基于lxml的树。决定这一切是否可行的层是获取 — 第一个脚本的0与10的计数决定了这一点 — 一次服务器渲染的POST弥补了差距。将两者连接起来,演示页面的十个引用以干净记录的形式到达,无论哪种方式更易于阅读。
创建一个免费的Scrapeless账户以获取API密钥,开发者文档涵盖了unlocker.webunlocker参数。当您计划定期作业时,请查看Scrapeless定价。
常见问题
问:parsel能否独立抓取网站?
不。parsel从您已经拥有的HTML中选择数据;它没有HTTP客户端,也不运行JavaScript。将其与获取层配对 — 此处为Scrapeless通用抓取API,它在服务器端渲染页面 — 然后parsel处理从返回的标记中提取数据。
问:parsel和Scrapy选择器有什么区别?
在实践中,parsel是Scrapy使用的选择器引擎,作为一个独立的库发布。如果您在Scrapy爬虫中使用过response.css或response.xpath,那就是parsel。直接使用它可以让您保持选择器API,而不必采用整个框架。
问:我应该使用CSS还是XPath与parsel?
根据需要使用两者。CSS在基于类和标签的选择中更简洁;XPath处理文本、位置或轴的选择,而CSS无法表达。parsel在同一树上运行两者,因此可以根据字段混合使用。
问:parsel能处理JavaScript渲染的页面吗?
不能——它从不执行脚本。如果内容在初始HTML加载后才出现,简单的获取会给parsel一个空树。首先通过Scrapeless API以js_render获取页面,然后从渲染的HTML中选择,就像本指南所做的那样。
问:使用parsel进行爬取合法吗?
选择器库不会改变收集规则。仅获取公共页面,遵守网站条款以及机器人排除协议标准化的爬虫指令,保持爬取量的边界,并根据适用的法律处理任何个人数据。
在Scrapeless,我们仅访问公开可用的数据,并严格遵循适用的法律、法规和网站隐私政策。本博客中的内容仅供演示之用,不涉及任何非法或侵权活动。我们对使用本博客或第三方链接中的信息不做任何保证,并免除所有责任。在进行任何抓取活动之前,请咨询您的法律顾问,并审查目标网站的服务条款或获取必要的许可。



