选择动态加载的内容

某些网页在您使用网页浏览器加载时会显示所需数据。但是,当您使用 Scrapy 下载它们时,您无法使用 选择器 获取所需数据。

当这种情况发生时,建议的方法是 查找数据源 并从中提取数据。

如果您无法做到这一点,并且仍然可以通过网页浏览器的 DOM 访问所需数据,请参阅 预渲染 JavaScript

查找数据源

要提取所需数据,您必须首先找到其源位置。

如果数据采用非文本格式,例如图像或 PDF 文档,请使用网页浏览器的 网络工具 查找相应的请求,并 重现它

如果您的网页浏览器允许您选择所需数据作为文本,则数据可能定义在嵌入的 JavaScript 代码中,或以基于文本的格式从外部资源加载。

在这种情况下,您可以使用像 wgrep 这样的工具来查找该资源的 URL。

如果数据原来来自原始 URL 本身,则必须 检查网页的源代码 以确定数据位于何处。

如果数据来自不同的 URL,则需要 重现相应的请求

检查网页的源代码

有时您需要检查网页的源代码(而不是 DOM)以确定某些所需数据位于何处。

使用 Scrapy 的 fetch 命令下载 Scrapy 所见的网页内容

scrapy fetch --nolog https://example.com > response.html

如果所需数据位于 <script/> 元素内的嵌入式 JavaScript 代码中,请参阅 解析 JavaScript 代码

如果您找不到所需数据,请首先确保它不仅仅是 Scrapy:使用像 curlwget 这样的 HTTP 客户端下载网页,并查看是否可以在他们获得的响应中找到信息。

如果他们得到包含所需数据的响应,请修改您的 Scrapy Request 以匹配其他 HTTP 客户端的响应。例如,尝试使用相同的用户代理字符串 (USER_AGENT) 或相同的 headers

如果他们也得到没有所需数据的响应,则需要采取措施使您的请求更类似于网页浏览器。请参阅 重现请求

重现请求

有时我们需要像我们的网页浏览器一样重现请求。

使用网页浏览器的 网络工具 查看您的网页浏览器如何执行所需的请求,并尝试使用 Scrapy 重现该请求。

使用相同的 HTTP 方法和 URL 生成 Request 可能就足够了。但是,您可能还需要重现该请求的主体、标头和表单参数(请参阅 FormRequest)。

由于所有主要的浏览器都允许以 curl 格式导出请求,因此 Scrapy 集成了 from_curl() 方法,以根据 cURL 命令生成等效的 Request。要获取更多信息,请访问网络工具部分中的 来自 curl 的请求

获得预期响应后,您可以 从中提取所需数据

您可以使用 Scrapy 重现任何请求。但是,有时重现所有必要的请求在开发人员时间上似乎效率不高。如果是这种情况,并且爬取速度不是您主要关心的问题,您可以选择考虑 JavaScript 预渲染

如果您 有时 获取到预期的响应,但并非总是如此,则问题可能不是您的请求,而是目标服务器。目标服务器可能存在错误、过载或 禁止 某些请求。

请注意,要将 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>'
    

JavaScript 预渲染

在从额外请求中获取数据的网页上,重现包含所需数据的那些请求是首选方法。付出的努力通常值得结果:结构化、完整的数据,以及最少的解析时间和网络传输。

但是,有时重现某些请求可能非常困难。或者您可能需要某些请求无法提供的功能,例如在 Web 浏览器中看到的网页截图。

在这些情况下,请使用 Splash JavaScript 渲染服务,以及 scrapy-splash 实现无缝集成。

Splash 将网页的 DOM 作为 HTML 返回,以便您可以使用 选择器 进行解析。它通过 配置脚本编写 提供了极大的灵活性。

如果您需要 Splash 提供的功能之外的其他功能,例如从 Python 代码而不是使用预先编写的脚本动态与 DOM 交互,或处理多个 Web 浏览器窗口,您可能需要 使用无头浏览器

使用无头浏览器

无头浏览器是一种特殊的 Web 浏览器,它提供用于自动化的 API。通过安装 asyncio 反应器,可以集成处理无头浏览器的基于 asyncio 的库。

其中一个库是 playwright-pythonplaywright 的官方 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 以获得更好的集成。