选择动态加载的内容
有些网页在网页浏览器中加载时会显示所需数据。然而,当你使用 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 重现该请求。
可能只需要 yield 一个具有相同 HTTP 方法和 URL 的 Request
就足够了。但是,你可能还需要重现该请求的 body、headers 和表单参数(参见 FormRequest
)。
由于所有主流浏览器都允许以 curl 格式导出请求,Scrapy 集成了 from_curl()
方法,可以从 cURL 命令生成等效的 Request
。更多信息请访问网络工具部分内的 request from 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 spider 中的用法
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 以获得更好的集成。