选择动态加载的内容

一些网页在浏览器中加载时会显示所需的数据。 然而,当你使用Scrapy下载它们时,你无法通过selectors获取到所需的数据。

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

如果你未能做到这一点,但你仍然可以通过你的网页浏览器访问所需的数据,请参阅DOM,参见预渲染JavaScript

寻找数据源

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

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

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

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

如果数据最终来自原始URL本身,你必须 检查网页的源代码 以 确定数据的位置。

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

检查网页的源代码

有时你需要检查网页的源代码(不是 DOM)来确定某些所需数据的位置。

使用Scrapy的fetch命令来下载Scrapy所看到的网页内容:

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

如果所需的数据在元素内的嵌入式JavaScript代码中,请参阅解析JavaScript代码

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

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

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

重现请求

有时我们需要以我们的网页浏览器执行请求的方式来重现它。

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

可能足以生成一个具有相同HTTP方法和URL的Request。然而,您可能还需要复制该请求的主体、头部和表单参数(参见FormRequest)。

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

一旦你得到预期的响应,你可以从中提取所需的数据

你可以使用Scrapy重现任何请求。然而,有时重现所有必要的请求在开发时间上可能看起来并不高效。如果这是你的情况,并且爬取速度对你来说不是主要关注点,你可以考虑JavaScript预渲染

如果你有时得到预期的响应,但并非总是如此,问题可能不在于你的请求,而在于目标服务器。目标服务器可能存在故障、过载,或者禁止了你的部分请求。

请注意,要将cURL命令转换为Scrapy请求, 你可以使用curl2scrapy

处理不同的响应格式

一旦你得到了包含所需数据的响应,如何从中提取所需数据取决于响应的类型:

  • 如果响应是HTML、XML或JSON,请像往常一样使用selectors

  • 如果响应是JSON,使用response.json()来加载所需的数据:

    data = response.json()
    

    如果所需的数据位于嵌入在JSON数据中的HTML或XML代码中,你可以将该HTML或XML代码加载到Selector中,然后像往常一样使用它

    selector = Selector(data["html"])
    
  • 如果响应是JavaScript,或者包含所需数据的元素的HTML,请参阅解析JavaScript代码

  • 如果响应是CSS,使用正则表达式response.text中提取所需数据。

  • 如果响应是图像或基于图像的另一种格式(例如PDF), 请从 response.body 读取响应为字节,并使用OCR 解决方案将所需数据提取为文本。

    例如,您可以使用pytesseract。要从PDF中读取表格,tabula-py可能是更好的选择。

  • 如果响应是SVG,或者包含所需数据的嵌入SVG的HTML,您可能能够使用selectors提取所需数据,因为SVG是基于XML的。

    否则,您可能需要将SVG代码转换为光栅图像,并 处理该光栅图像

解析JavaScript代码

如果所需的数据在JavaScript中是硬编码的,你首先需要获取JavaScript代码:

  • 如果JavaScript代码在JavaScript文件中,只需读取 response.text

  • 如果JavaScript代码位于HTML页面的元素内,使用selectors来提取该元素内的文本。

一旦你有了包含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 文档,然后可以使用 selectors 进行解析。

    例如,如果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

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

然而,有时重现某些请求可能非常困难。或者你可能需要一些请求无法提供的东西,比如在网页浏览器中看到的网页截图。

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

Splash 以 HTML 形式返回网页的 DOM,因此你可以使用 selectors 来解析它。它通过 configurationscripting 提供了极大的灵活性。

如果你需要超出Splash提供的功能,例如从Python代码中动态与DOM交互,而不是使用预先编写的脚本,或者处理多个网页浏览器窗口,你可能需要使用无头浏览器来代替。

使用无头浏览器

一个无头浏览器是一种特殊的网络浏览器,它提供了用于自动化的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以获得更好的集成。