选择动态加载的内容

有些网页在网页浏览器中加载时会显示所需数据。然而,当你使用 Scrapy 下载它们时,无法使用选择器获取所需数据。

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

如果你未能做到这一点,但仍可以通过网页浏览器中的DOM访问所需数据,请参阅使用无头浏览器

查找数据源

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

如果数据是非文本格式,例如图像或 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 字符串 (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-pythonplaywright 的官方 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 以获得更好的集成。