使用浏览器开发者工具进行抓取

这是一份关于如何使用浏览器开发者工具简化抓取过程的通用指南。如今,几乎所有浏览器都内置了开发者工具,虽然本指南将使用 Firefox,但这些概念适用于任何其他浏览器。

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

检查实时浏览器 DOM 的注意事项

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

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

  • 在检查 DOM 以查找 Scrapy 中使用的 XPath 时禁用 JavaScript(在开发者工具设置中点击 禁用 JavaScript

  • 切勿使用完整的 XPath 路径,请使用基于属性(例如 idclasswidth 等)或任何识别特征(如 contains(@href, 'image'))的相对路径和巧妙的路径。

  • 除非您真正了解自己在做什么,否则切勿在 XPath 表达式中包含 <tbody> 元素

检查网站

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

该网站总共有来自不同作者、带有特定标签的十条引言,以及“十大标签”。假设我们想提取此页面上的所有引言,而不需要关于作者、标签等的任何元信息。

我们无需查看页面的全部源代码,只需右键点击一条引言并选择 检查元素 (Q),这将打开 检查器。您应该在其中看到类似以下内容:

Firefox's Inspector-tool

对我们来说,有趣的部分是这里

<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。我们来试试看。

首先在终端中打开 https://quotes.toscrape.com/ 的 Scrapy shell

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

然后,回到您的网页浏览器,右键点击 span 标签,选择 复制 > 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 扩展 来简单地选择所有带有 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 标签并选择 滚动到视图中。如果您将鼠标悬停在该标签上,您会看到按钮被突出显示。从这里我们可以轻松创建一个 链接提取器 来跟踪分页。对于像这样简单的网站,可能没有必要在视觉上找到一个元素,但 滚动到视图中 功能在复杂的网站上会非常有用。

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

网络工具

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

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

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

应该会打开一个浏览器窗口显示该网页,但有一个关键区别:我们没有看到引言,而只看到一个带有 正在加载... 字样的绿色条。

Response from quotes.toscrape.com/scroll

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

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

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

Network tab with persistent logs and requests

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

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

您应该在 预览 面板中看到的是渲染后的 HTML 代码,这正是我们在 shell 中调用 view(response) 时看到的内容。相应地,日志中该请求的 类型html。其他请求的类型是 cssjs,但我们感兴趣的是那个名为 quotes?page=1、类型为 json 的请求。

如果我们点击这个请求,会看到请求 URL 是 https://quotes.toscrape.com/api/quotes?page=1,响应是一个包含我们引言的 JSON 对象。我们也可以右键点击该请求,选择 在新标签页中打开 以获得更好的概览。

JSON-object returned from the quotes.toscrape API

有了这个响应,我们现在可以轻松解析 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 中。

在更复杂的网站中,可能难以轻松重现请求,因为我们可能需要添加 headerscookies 才能使其正常工作。在这种情况下,您可以通过在网络工具中右键点击每个请求,并使用 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][源代码]

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

参数:
  • curl_command (str) – 包含 curl 命令的字符串

  • ignore_unknown_options (bool) – 如果为 True,则仅在遇到未知 cURL 选项时发出警告,否则会引发错误。(默认为 True)

返回:

包含 Request kwargs 的字典

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

正如您所见,通过对 网络 工具进行少量检查,我们就能轻松复制页面滚动功能的动态请求。抓取动态页面可能相当令人生畏,且页面可能非常复杂,但这(主要)归结于识别正确的请求并在您的爬虫中重现它。