pyquery网络爬虫:Python中的jQuery风格HTML解析
Senior Web Scraping Engineer
TL;DR:
- pyquery为Python提供了jQuery的API。 如果你知道
$("div.quote").find("small.author").text(),那么你已经掌握了pyquery。 - pyquery不执行抓取。 它封装了
lxml用于解析和选择;它没有HTTP客户端,也不运行JavaScript。 - 这个差异在一个脚本中显现。 对于一个JavaScript渲染的演示页面的普通GET请求,pyquery返回0个引用节点;而通过Scrapeless通用抓取API使用
js_render的相同URL则返回全部10个引用。 - 本指南中提取是真实的。 在线示例使用
.items()、.find()和.text()从渲染的HTML中提取所有10个引用及其作者和标签。 - 这两层保持分离。 Scrapeless负责抓取和渲染;pyquery负责选择。两者之间互不干扰。
- 抓取侧免费开始。 在 app.scrapeless.com 创建你的Scrapeless API密钥。
pyquery是什么,以及它不是什么
pyquery是一个Python库,它在lxml之上提供了jQuery风格的API。你将标记包装在一个PyQuery对象中—通常命名为d—然后使用前端开发者已经使用的相同调用进行选择和遍历:d("selector")、.find()、.eq()、.text()、.attr()、.items()。对于任何来自浏览器的人来说,这是从“我知道如何查询DOM”到“我可以用Python提取这个”的最短路径,因为它依赖于lxml,底层选择很快。
它只是一个解析和选择库,没别的。pyquery没有HTTP客户端,不保持会话,不运行JavaScript。给它一个字符串,它会构建一个可以查询的文档;如果你要求它抓取一个URL,尽管技术上它可以拉取一个,但它实际上使用普通请求并不进行渲染,这是处理任何通过脚本构建的页面的错误工具。因此,每个“pyquery网络抓取”设置由两个层次组成:一个返回真实渲染的HTML,另一个是从中选择的pyquery。本指南使用Scrapeless通用抓取API作为第一层。更广泛的Python网络抓取教程覆盖了周围的生态系统。
安装
pyquery和requests是整个工具链。编写本指南时的版本是pyquery 2.0.1:
bash
pip install "pyquery==2.0.1" requests
将你的密钥存储在环境中,而不是源代码里:
bash
export SCRAPELESS_API_KEY="sk_your_scrapeless_key"
获取值得解析的HTML
选择的质量受限于抓取的准确性,因此从那里开始。在一个JavaScript渲染的页面上,普通HTTP客户端收到的标记不是读者所看到的标记——内容只在脚本构建DOM后到达,这个生命周期由HTML脚本规范定义。一个脚本通过pyquery本身计算差异:
python
# fidelity.py — pyquery所见:普通GET与服务器端渲染的对比
import os
import requests
from pyquery import PyQuery as pq
URL = "https://quotes.toscrape.com/js/"
plain = requests.get(URL, timeout=60).text
print("普通GET字符数:", len(plain), "| 引用节点:", pq(plain, parser="html")("div.quote").length)
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("渲染字符数:", len(rendered), "| 引用节点:", pq(rendered, parser="html")("div.quote").length)
运行打印出现,对于普通抓取,引用节点为0,而对渲染页面为10:
text
普通GET字符数: 5806 | 引用节点: 0
渲染字符数: 8940 | 引用节点: 10
渲染、解锁和代理路由都在那一次POST中发生——通用抓取API是抓取层,而渲染的标记是pyquery进行选择的基础。
使用jQuery API进行提取
拥有真实的HTML后,pyquery用jQuery的方式进行提取。.items()将选择转换为PyQuery对象的迭代器,.find()限制子选择器的作用域,.text()读取文本——选择器遵循W3C选择器规范,与jQuery使用的语法相同:
python
# extract.py — 抓取渲染页面,然后使用jQuery API选择
import os
import requests
from pyquery import PyQuery as pq
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": "https://quotes.toscrape.com/js/", "js_render": True}},
timeout=120,
)
resp.raise_for_status()
d = pq(resp.json().get("data", ""), parser="html")
records = []
for quote in d("div.quote").items():
records.append({
"text": quote.find("span.text").text(),
"author": quote.find("small.author").text(),
"tags": [pq(tag).text() for tag in quote.find("a.tag").items()],
})
print("记录数:", len(records))
print("第一个作者:", records[0]["author"])
print("第一个标签:", records[0]["tags"])
实时运行选择了所有10条记录,标签和所有信息:
```text
记录数: 10
第一个作者: 阿尔伯特·爱因斯坦
第一个标签: ['变化', '深思', '思考', '世界']
这就是整个爬虫:一次POST请求以获取和渲染,一次PyQuery对象进行查询。.items()迭代器是值得记住的pyquery习惯——它将选择转换为每个记录对象,你可以在上面使用.find(),与jQuery的.each()直接对应。
在免费计划中获取您的API密钥:app.scrapeless.com
高级模式
- 始终使用
.items()进行迭代。 直接循环遍历PyQuery选择将产生原始lxml元素,而不是PyQuery对象,因此.find()会失效。.items()为你提供每个对象的完整API。 - 使用
.attr()读取属性。quote.find("a::attr(href)")不是pyquery语法;请使用quote.find("a").attr("href"),与jQuery完全一样。 - 真实页面传递
parser="html"。 它选择lxml宽容的HTML解析器,可以处理真实网站发送的格式错误的标记;默认的可能比你想要的更严格。 - 链式调用,不要重复查询。
d("div.quote").eq(0).find("small.author")将每一步的范围限制在最后一步,这样更快,更接近你已经熟悉的jQuery的阅读方式。
故障排除
- 从可以在浏览器中看到的页面获取零节点。 内容是通过JavaScript渲染的,而你的获取返回的是预渲染的HTML。像第一个脚本一样计算已知选择器;几乎空的文档是抓取问题,通过
js_render解决,而不是选择器问题。 .find()引发AttributeError。 你直接迭代选择而不是使用.items(),因此得到了一个裸lxml元素。将循环切换为for x in sel.items():。.text()返回所有文本连接在一起。 pyquery连接后代文本。用更具体的选择器缩小范围,或使用.eq(0).text()读取单个节点。- 编码看起来不正确。 用
parser="html"优先传递响应文本给pyquery;当解析器是HTML时,lxml读取文档声明的编码。
结论
pyquery赢得了作为选择层的地位,能够使用jQuery:使用你在浏览器中熟知的相同.find()、.text()和.items()调用,基于快速的lxml树。决定是否可以这样做的层是抓取——第一个脚本的0与10之比较量确定了这一点——并且一次服务器渲染的POST填补了这个差距。将两者连接起来,快照页面的十个引用以清晰的记录呈现,以你已经想到的方式进行选择。
创建一个免费的Scrapeless账户以获取API密钥,开发者文档涵盖unlocker.webunlocker参数。计划重复工作时,请查看Scrapeless定价。
常见问题
问:pyquery能否独立抓取网站?
其实不能。pyquery可以拉取一个URL,但它是通过普通请求而没有JavaScript渲染,因此在现代页面上它从空的标记中选择。将其视为解析器:与抓取层配对——这里是Scrapeless Universal Scraping API,它在服务器端渲染页面——然后pyquery处理从返回的HTML中选择。
问:pyquery和jQuery一样吗?
它在Python中镜像jQuery的API——d("selector")、.find()、.text()、.attr()、.items()——但它在lxml的服务端运行,而不是在浏览器中,因此它从静态标记中选择,而不执行脚本或处理事件。选择语法可以转移;运行时则不可以。
问:pyquery还是BeautifulSoup?
偏好选择。pyquery的阅读方式类似于jQuery,如果你来自前端工作,它是一个自然的选择;BeautifulSoup则拥有更Pythonic的API。两者都解析相同的HTML,并且都需要一个单独的抓取层用于JavaScript渲染的页面。
问:pyquery处理JavaScript渲染的页面吗?
不单独使用——它从不执行脚本。如果内容在初始 HTML 加载后才出现,普通的 fetch 会给 pyquery 提供一个空文档。首先通过 Scrapeless API 使用 js_render 抓取页面,然后从渲染的 HTML 中选择,就像本指南所示。
问:使用 pyquery 抓取数据合法吗?
选择库并不改变收集规则。仅抓取公共页面,尊重网站条款和由 机器人排除协议 标准化的机器人指令,保持抓取量的界限,并根据适用的法律处理任何个人数据。
在Scrapeless,我们仅访问公开可用的数据,并严格遵循适用的法律、法规和网站隐私政策。本博客中的内容仅供演示之用,不涉及任何非法或侵权活动。我们对使用本博客或第三方链接中的信息不做任何保证,并免除所有责任。在进行任何抓取活动之前,请咨询您的法律顾问,并审查目标网站的服务条款或获取必要的许可。



