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

以下是关于如何使用浏览器的开发者工具来简化爬取过程的通用指南。如今,几乎所有浏览器都内置了开发者工具,虽然本指南将使用 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。让我们试试看。

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

$ 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-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 标签并选择 滚动到视图中。如果您将鼠标悬停在该标签上,您会看到按钮被高亮显示。从这里我们可以轻松创建一个 链接提取器 来跟随分页。对于像这样的简单网站,可能不需要通过视觉方式查找元素,但 滚动到视图中 功能在复杂网站上会非常有用。

请注意,搜索栏也可以用于搜索和测试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)

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

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)

这个爬虫从语录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][source]

将 cURL 命令语法转换为请求关键字参数 (Request kwargs)。

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

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

返回:

请求关键字参数 (Request kwargs) 字典

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

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