选择动态加载的内容
有些网页在浏览器中加载时会显示所需数据。然而,当你使用 Scrapy 下载它们时,无法通过选择器获取到所需数据。
发生这种情况时,建议的方法是查找数据源并从中提取数据。
如果你无法做到这一点,但仍然可以通过网络浏览器的DOM访问所需数据,请参阅使用无头浏览器。
查找数据源
要提取所需数据,你必须首先找到其源位置。
如果数据是非文本格式,例如图像或 PDF 文档,请使用网络浏览器的网络工具查找相应的请求,并复现它。
如果你的网络浏览器允许你将所需数据选择为文本,则该数据可能定义在嵌入式 JavaScript 代码中,或者从外部资源以文本格式加载。
在这种情况下,你可以使用像 wgrep 这样的工具来查找该资源的 URL。
如果数据显示来自原始 URL 本身,你必须检查网页源代码以确定数据位于何处。
如果数据来自不同的 URL,你需要复现相应的请求。
检查网页源代码
有时你需要检查网页的源代码(而不是DOM)以确定所需数据位于何处。
使用 Scrapy 的 fetch 命令下载 Scrapy 所见的网页内容
scrapy fetch --nolog https://example.com > response.html
如果所需数据位于 <script/> 元素内的嵌入式 JavaScript 代码中,请参阅解析 JavaScript 代码。
如果你找不到所需数据,首先要确保这不只是 Scrapy 的问题:使用 curl 或 wget 等 HTTP 客户端下载网页,看看它们获取的响应中是否包含所需信息。
如果它们获得了包含所需数据的响应,请修改你的 Scrapy Request 以匹配其他 HTTP 客户端的请求。例如,尝试使用相同的 user-agent 字符串 (USER_AGENT) 或相同的 headers。
如果它们也获得了不包含所需数据的响应,你需要采取措施使你的请求更接近网络浏览器的请求。请参阅复现请求。
复现请求
有时我们需要以网络浏览器执行请求的方式来复现请求。
使用网络浏览器的网络工具查看你的网络浏览器如何执行所需请求,并尝试使用 Scrapy 复现该请求。
只用相同的 HTTP 方法和 URL 发出Request可能就足够了。但是,你可能还需要复现该请求的主体、头部和表单参数(请参阅创建提交 HTML 表单的请求)。
由于所有主流浏览器都允许以 curl 格式导出请求,Scrapy 集成了 from_curl() 方法,可以从 cURL 命令生成等效的 Request。要获取更多信息,请访问网络工具部分中的从 curl 获取请求。
一旦你获得了预期响应,就可以从中提取所需数据。
你可以使用 Scrapy 复现任何请求。然而,有时复现所有必要的请求在开发时间上可能效率不高。如果这是你的情况,并且抓取速度不是你的主要关注点,你可以选择使用无头浏览器。
如果你有时能获得预期响应,但并非总是如此,那么问题可能不在于你的请求,而在于目标服务器。目标服务器可能存在 bug、过载,或者禁止了你的某些请求。
请注意,要将 cURL 命令转换为 Scrapy 请求,你可以使用 curl2scrapy。
处理不同的响应格式
一旦你获得了包含所需数据的响应,如何从中提取所需数据取决于响应的类型
如果响应是 HTML、XML 或 JSON,照常使用选择器。
如果响应是 JSON,请使用
response.json()加载所需数据data = response.json()
如果所需数据位于嵌入在 JSON 数据中的 HTML 或 XML 代码内部,你可以将该 HTML 或 XML 代码加载到
Selector中,然后照常使用它selector = Selector(data["html"])
如果响应是 JavaScript,或者包含所需数据的
<script/>元素的 HTML,请参阅解析 JavaScript 代码。如果响应是 CSS,请使用正则表达式从
response.text中提取所需数据。
如果响应是图像或基于图像的其他格式(例如 PDF),请从
response.body中以字节形式读取响应,并使用 OCR 解决方案将所需数据提取为文本。例如,你可以使用 pytesseract。要从 PDF 中读取表格,tabula-py 可能是更好的选择。
如果响应是 SVG,或者包含所需数据的嵌入式 SVG 的 HTML,由于 SVG 基于 XML,你可能能够使用选择器提取所需数据。
否则,你可能需要将 SVG 代码转换为栅格图像,并处理该栅格图像。
解析 JavaScript 代码
如果所需数据硬编码在 JavaScript 中,你首先需要获取 JavaScript 代码
如果 JavaScript 代码位于 JavaScript 文件中,只需读取
response.text。如果 JavaScript 代码位于 HTML 页面的
<script/>元素内,请使用选择器提取该<script/>元素内的文本。
一旦你获得了包含 JavaScript 代码的字符串,就可以从中提取所需数据
你可能能够使用正则表达式以 JSON 格式提取所需数据,然后使用
json.loads()进行解析。例如,如果 JavaScript 代码包含一行类似
var data = {"field": "value"};的内容,你可以按如下方式提取该数据>>> pattern = r"\bvar\s+data\s*=\s*(\{.*?\})\s*;\s*\n" >>> json_data = response.css("script::text").re_first(pattern) >>> json.loads(json_data) {'field': 'value'}
chompjs 提供了一个 API,用于将 JavaScript 对象解析为
dict。例如,如果 JavaScript 代码包含
var data = {field: "value", secondField: "second value"};,你可以按如下方式提取该数据>>> import chompjs >>> javascript = response.css("script::text").get() >>> data = chompjs.parse_js_object(javascript) >>> data {'field': 'value', 'secondField': 'second value'}
否则,使用 js2xml 将 JavaScript 代码转换为 XML 文档,然后可以使用选择器进行解析。
例如,如果 JavaScript 代码包含
var data = {field: "value"};,你可以按如下方式提取该数据>>> import js2xml >>> import lxml.etree >>> from parsel import Selector >>> javascript = response.css("script::text").get() >>> xml = lxml.etree.tostring(js2xml.parse(javascript), encoding="unicode") >>> selector = Selector(text=xml) >>> selector.css('var[name="data"]').get() '<var name="data"><object><property name="field"><string>value</string></property></object></var>'
使用无头浏览器
对于从额外请求获取数据的网页,复现包含所需数据的那些请求是首选方法。这种努力通常是值得的:可以获得结构化、完整的数据,同时最大程度地减少解析时间和网络传输。
然而,有时复现某些请求可能非常困难。或者你可能需要某些请求无法提供的东西,例如在网页浏览器中看到的网页截图。在这种情况下,使用无头浏览器会有所帮助。
无头浏览器是一种特殊的网络浏览器,提供用于自动化的 API。通过安装asyncio reactor,可以集成处理无头浏览器的基于 asyncio 的库。
其中一个库是 playwright-python(playwright 的官方 Python 移植)。下面是一个简单的代码片段,用于说明它在 Scrapy 爬虫中的用法
import scrapy
from playwright.async_api import async_playwright
class PlaywrightSpider(scrapy.Spider):
name = "playwright"
start_urls = ["data:,"] # avoid using the default Scrapy downloader
async def parse(self, response):
async with async_playwright() as pw:
browser = await pw.chromium.launch()
page = await browser.new_page()
await page.goto("https://example.org")
title = await page.title()
return {"title": title}
然而,像上述示例那样直接使用 playwright-python 会绕过 Scrapy 的大部分组件(中间件、重复过滤器等)。我们建议使用 scrapy-playwright 以获得更好的集成。