Lzh on GitHub

Using your browser’s Developer Tools for scraping (使用浏览器开发者工具进行网页抓取)

本指南将介绍如何利用浏览器的开发者工具来简化网页抓取过程。如今,几乎所有浏览器都内置了开发者工具,尽管本指南将使用 Firefox 进行演示,但这些概念适用于任何其他浏览器。

在本指南中,我们将通过抓取 quotes.toscrape.com 网站来介绍浏览器开发者工具中的基本使用方法。

Caveats with inspecting the live browser DOM (检查实时浏览器 DOM 的注意事项)

由于开发者工具操作的是实时浏览器 DOM,因此您在检查页面源代码时实际看到的不是原始 HTML,而是经过浏览器清理和执行 JavaScript 代码后修改过的 HTML。特别是 Firefox,已知会向表格添加 <tbody> 元素。另一方面,Scrapy 不会修改原始页面 HTML,因此如果您的 XPath 表达式中包含 <tbody>,您将无法提取任何数据。

因此,您应该记住以下几点:

  • 在检查 DOM 查找用于 Scrapy 的 XPath 时,禁用 JavaScript(在开发者工具设置中点击“禁用 JavaScript”)。
  • 永远不要使用完整的 XPath 路径,而是使用基于属性(如 idclasswidth 等)或任何识别特征(如 contains(@href, 'image'))的相对和巧妙的路径。
  • 永远不要在 XPath 表达式中包含 <tbody> 元素,除非您确实知道自己在做什么。

Inspecting a website (检查网站)

到目前为止,开发者工具最方便的功能是检查器功能,它允许您检查任何网页的底层 HTML 代码。为了演示检查器,我们来看看 quotes.toscrape.com 网站。

在该网站上,我们总共有十条来自不同作者的引用,带有特定标签,以及十大热门标签。假设我们想提取此页面上的所有引用,而不包含作者、标签等任何元信息。

我们无需查看页面的整个源代码,只需右键单击引用并选择检查元素 (Q),这将打开检查器。您应该会看到类似以下内容:

对我们来说有趣的部分是这个:

<div class="quote" itemscope="" itemtype="http://schema.org/CreativeWork">
  <span class="text" itemprop="text">(...)</span>
  <span>(...)</span>
  <div class="tags">(...)</div>
</div>

如果您将鼠标悬停在屏幕截图中突出显示的 span 标签正上方的第一个 div 上,您会看到网页的相应部分也会突出显示。所以现在我们有了一个部分,但是我们找不到我们的引用文本。

检查器的优点是它会自动展开和折叠网页的部分和标签,这大大提高了可读性。您可以通过单击标签前面的箭头或直接双击标签来展开和折叠标签。如果展开带有 class="text"span 标签,我们将看到我们单击的引用文本。检查器允许您复制所选元素的 XPath。让我们尝试一下。

首先在终端中打开 Scrapy shell,网址为 https://quotes.toscrape.com/

$ scrapy shell "https://quotes.toscrape.com/"

然后,回到您的网络浏览器,右键单击 span 标签,选择 Copy > XPath 并将其粘贴到 Scrapy shell 中,如下所示:

>>> response.xpath("/html/body/div/div[2]/div[1]/div[1]/span[1]/text()").getall()
['“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”']

在末尾添加 text(),我们能够使用这个基本选择器提取第一个引用。但这个 XPath 并不是那么巧妙。它所做的只是从 html 开始,沿着源代码中所需的路径向下。所以让我们看看是否可以稍微优化一下我们的 XPath:

如果我们再次检查检查器,我们会发现我们展开的 div 标签正下方有九个相同的 div 标签,每个标签都具有与我们第一个相同的属性。如果我们展开其中任何一个,我们将看到与我们第一个引用相同的结构:两个 span 标签和一个 div 标签。我们可以展开 div 标签内带有 class="text" 的每个 span 标签,并查看每个引用:

<div class="quote" itemscope="" itemtype="http://schema.org/CreativeWork">
  <span class="text" itemprop="text">
    “The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”
  </span>
  <span>(...)</span>
  <div class="tags">(...)</div>
</div>

有了这些知识,我们可以优化我们的 XPath:我们不再遵循路径,而是简单地通过使用 has-class-extension 选择所有带有 class="text"span 标签:

>>> response.xpath('//span[has-class("text")]/text()').getall()
['“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”',
'“It is our choices, Harry, that show what we truly are, far more than our abilities.”',
'“There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.”',
...]

通过一个简单、更巧妙的 XPath,我们能够从页面中提取所有引用。我们本可以对第一个 XPath 构造一个循环来增加最后一个 div 的数量,但这将是多余的复杂,而通过简单地构造一个带有 has-class("text") 的 XPath,我们能够在一行中提取所有引用。

检查器还有许多其他有用的功能,例如在源代码中搜索或直接滚动到您选择的元素。让我们演示一个用例:

假设您想找到页面上的Next按钮。在检查器的右上角搜索栏中输入Next。您应该会得到两个结果。第一个是带有 class="next"li 标签,第二个是 a 标签的文本。右键单击 a 标签并选择Scroll into View。如果您将鼠标悬停在该标签上,您会看到按钮高亮显示。从这里我们可以轻松创建一个 Link Extractor 来跟踪分页。像这样的简单网站可能不需要在视觉上找到元素,但Scroll into View功能在复杂网站上会非常有用。

请注意,搜索栏也可以用于搜索和测试 CSS 选择器。例如,您可以搜索 span.text 来查找所有引用文本。这并不是一个全文搜索,而是精确搜索页面中带有 class="text"span 标签。

The Network-tool (网络工具)

在抓取过程中,您可能会遇到动态网页,其中页面的一些部分通过多个请求动态加载。虽然这可能非常棘手,但开发者工具中的网络工具极大地简化了这项任务。为了演示网络工具,我们来看看 quotes.toscrape.com/scroll 页面。

该页面与基本的 quotes.toscrape.com 页面非常相似,但不同之处在于,当您滚动到底部时,页面会自动加载新的引用,而不是上面提到的Next按钮。我们可以直接尝试不同的 XPath,但我们还是先看看 Scrapy shell 中另一个非常有用的命令:

$ scrapy shell "quotes.toscrape.com/scroll"
(...)
>>> view(response)

应该会打开一个带有网页的浏览器窗口,但有一个关键区别:我们只看到一个带有“Loading...”字样的绿色条,而不是引用。

view(response) 命令让我们查看我们的 shell 或稍后我们的爬虫从服务器接收到的响应。在这里我们看到加载了一些基本模板,其中包括标题、登录按钮和页脚,但引用缺失。这告诉我们引用是从与 quotes.toscrape/scroll 不同的请求加载的。

如果您单击“网络”选项卡,您可能只会看到两个条目。我们做的第一件事是单击Persist Logs以启用持久日志。如果此选项被禁用,每次您导航到不同的页面时,日志都会自动清除。启用此选项是一个很好的默认设置,因为它可以让我们控制何时清除日志。

如果我现在重新加载页面,您会看到日志中填充了六个新请求。

在这里,我们看到重新加载页面时发出的每个请求,并且可以检查每个请求及其响应。那么让我们找出我们的引用来自哪里:

首先点击名为 scroll 的请求。在右侧,您现在可以检查该请求。在Headers中,您将找到有关请求头的详细信息,例如 URL、方法、IP 地址等。我们将忽略其他选项卡,直接点击Response

您在Preview窗格中应该看到的是渲染的 HTML 代码,这与我们在 shell 中调用 view(response) 时看到的完全相同。因此,日志中请求的 typehtml。其他请求的类型是 cssjs,但我们感兴趣的是名为 quotes?page=1 且类型为 json 的请求。

如果我们点击此请求,我们会看到请求 URL 是 https://quotes.toscrape.com/api/quotes?page=1,并且响应是一个包含我们引用的 JSON 对象。我们还可以右键单击该请求并打开Open in new tab以获得更好的概览。

有了这个响应,我们现在可以轻松解析 JSON 对象并请求每个页面以获取网站上的每个引用:

import scrapy
import json

class QuoteSpider(scrapy.Spider):
    name = "quote"
    allowed_domains = ["quotes.toscrape.com"]
    page = 1
    start_urls = ["https://quotes.toscrape.com/api/quotes?page=1"]

    def parse(self, response):
        data = json.loads(response.text)
        for quote in data["quotes"]:
            yield {"quote": quote["text"]}
        if data["has_next"]:
            self.page += 1
            url = f"https://quotes.toscrape.com/api/quotes?page={self.page}"
            yield scrapy.Request(url=url, callback=self.parse)

这个爬虫从 quotes-API 的第一页开始。每次响应时,我们都会解析 response.text 并将其分配给 data。这使我们能够像操作 Python 字典一样操作 JSON 对象。我们遍历 quotes 并打印出 quote["text"]。如果方便的 has_next 元素为 true(尝试在浏览器中加载 quotes.toscrape.com/api/quotes?page=10 或大于 10 的页码),我们会递增 page 属性并 yield 一个新请求,将递增的页码插入到我们的 url 中。

在更复杂的网站中,可能很难轻松重现请求,因为我们可能需要添加标头cookie 才能使其工作。在这些情况下,您可以通过右键单击网络工具中的每个请求并使用 from_curl() 方法生成等效请求来以 cURL 格式导出请求:

from scrapy import Request

request = Request.from_curl(
    "curl 'https://quotes.toscrape.com/api/quotes?page=1' -H 'User-Agent: Mozil"
    "la/5.0 (X11; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0' -H 'Acce"
    "pt: */*' -H 'Accept-Language: ca,en-US;q=0.7,en;q=0.3' --compressed -H 'X"
    "-Requested-With: XMLHttpRequest' -H 'Proxy-Authorization: Basic QFRLLTAzM"
    "zEwZTAxLTk5MWUtNDFiNC1iZWRmLTJjNGI4M2ZiNDBmNDpAVEstMDMzMTBlMDEtOTkxZS00MW"
    "I0LWJlZGYtMmM0YjgzZmI0MGY0' -H 'Connection: keep-alive' -H 'Referer: http"
    "://quotes.toscrape.com/scroll' -H 'Cache-Control: max-age=0'")

或者,如果您想知道重新创建该请求所需的参数,可以使用 curl_to_request_kwargs() 函数获取一个包含等效参数的字典:

scrapy.utils.curl.curl_to_request_kwargs(curl_command: str, ignore_unknown_options: bool = True) -> dict[str, Any][source]

将 cURL 命令语法转换为 Request kwargs。

参数:

  • curl_command (str) – 包含 curl 命令的字符串
  • ignore_unknown_options (bool) – 如果为 True,则在 cURL 选项未知时仅发出警告。否则会引发错误。(默认:True)

返回:

Request kwargs 的字典

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

正如您所看到的,通过对网络工具进行一些检查,我们能够轻松地复制页面滚动功能的动态请求。抓取动态页面可能非常艰巨,页面可能非常复杂,但它(大部分)归结为识别正确的请求并在您的爬虫中复制它。