使用浏览器开发者工具进行抓取¶
这是一份关于如何使用浏览器开发者工具简化抓取过程的通用指南。如今几乎所有浏览器都内置了开发者工具,虽然本指南将使用 Firefox,但这些概念适用于任何其他浏览器。
在本指南中,我们将通过抓取quotes.toscrape.com来介绍浏览器开发者工具中的一些基本工具。
检查实时浏览器 DOM 的注意事项¶
由于开发者工具在实时浏览器 DOM 上运行,因此在检查页面源代码时实际看到的并非原始 HTML,而是在应用了一些浏览器清理并执行 JavaScript 代码后的修改后的 HTML。特别是 Firefox 可能会在表格中添加<tbody>
元素。另一方面,Scrapy 不会修改原始页面 HTML,因此如果您在 XPath 表达式中使用<tbody>
,将无法提取任何数据。
因此,您应该牢记以下几点
在检查用于 Scrapy 的 XPath 的 DOM 时禁用 JavaScript(在开发者工具设置中点击“禁用 JavaScript”)。
永远不要使用完整的 XPath 路径,而要使用基于属性(例如
id
、class
、width
等)或任何识别特征(如contains(@href, 'image')
)的相对且巧妙的 XPath 路径。除非您确实知道自己在做什么,否则永远不要在 XPath 表达式中包含
<tbody>
元素。
检查网站¶
到目前为止,开发者工具中最方便的功能是“检查器”功能,它允许您检查任何网页的底层 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 复制到选定的元素。让我们试一试。
首先在终端中打开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
。您应该会得到两个结果。第一个是具有class="next"
的li
标签,第二个是a
标签的文本。右键单击a
标签,然后选择“滚动到视图”。如果您将鼠标悬停在该标签上,您会看到按钮突出显示。从这里,我们可以轻松地创建一个链接提取器来跟踪分页。在像这样的简单网站上,可能不需要直观地查找元素,但“滚动到视图”功能在复杂的网站上非常有用。
请注意,搜索栏也可用于搜索和测试 CSS 选择器。例如,您可以搜索span.text
以查找所有引文文本。与完整文本搜索不同,此搜索在页面中精确查找具有class="text"
的span
标签。
网络工具¶
在抓取过程中,您可能会遇到一些动态网页,其中页面的一些部分是通过多个请求动态加载的。虽然这可能很棘手,但开发者工具中的“网络”工具极大地促进了这项任务。为了演示网络工具,让我们看一下quotes.toscrape.com/scroll页面。
该页面与基本的quotes.toscrape.com页面非常相似,但与上面提到的“下一步”按钮不同,该页面会在您滚动到底部时自动加载新的引言。我们可以继续尝试不同的 XPath,但我们将检查 Scrapy shell 中的另一个非常有用的命令:
$ scrapy shell "quotes.toscrape.com/scroll"
(...)
>>> view(response)
浏览器窗口将打开网页,但有一个关键区别:我们看到的不是引言,而是一个带有“加载...”字样的绿色条。
view(response)
命令允许我们查看 shell 或稍后我们的爬虫从服务器接收到的响应。在这里,我们看到加载了一些基本模板,其中包括标题、登录按钮和页脚,但引言缺失。这告诉我们引言是从与quotes.toscrape/scroll
不同的请求中加载的。
如果您单击“网络”选项卡,您可能只会看到两个条目。我们首先要做的是通过单击“持久化日志”来启用持久化日志。如果禁用此选项,则每次导航到不同的页面时日志都会自动清除。启用此选项是一个很好的默认设置,因为它使我们能够控制何时清除日志。
如果我们现在重新加载页面,您将看到日志填充了六个新的请求。
在这里,我们看到了重新加载页面时发出的每个请求,并且可以检查每个请求及其响应。所以让我们找出我们的引言来自哪里。
首先点击名称为scroll
的请求。在右侧,您现在可以检查该请求。在Headers
中,您将找到有关请求头的详细信息,例如 URL、方法、IP 地址等。我们将忽略其他选项卡,并直接点击Response
。
您应该在Preview
窗格中看到渲染后的 HTML 代码,这与我们在 shell 中调用view(response)
时看到的内容完全相同。相应地,日志中请求的type
为html
。其他请求的类型可能是css
或js
,但我们感兴趣的是名为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)
此蜘蛛从引号 API 的第一页开始。对于每个响应,我们解析response.text
并将其分配给data
。这让我们可以像操作 Python 字典一样操作 JSON 对象。我们遍历quotes
并打印出quote["text"]
。如果方便的has_next
元素为true
(尝试在浏览器中加载quotes.toscrape.com/api/quotes?page=10或大于 10 的页码),我们会增加page
属性并yield
一个新的请求,将递增的页码插入到我们的url
中。
在更复杂的网站中,可能难以轻松地重现请求,因为我们可能需要添加headers
或cookies
才能使其工作。在这些情况下,您可以将请求导出为cURL格式,方法是在网络工具中右键点击每个请求,并使用from_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 参数。
请注意,要将 cURL 命令转换为 Scrapy 请求,可以使用curl2scrapy。
如您所见,通过在“网络”工具中进行一些检查,我们能够轻松地复制页面滚动功能的动态请求。爬取动态页面可能非常令人生畏,并且页面可能非常复杂,但它(大多数情况下)归结为识别正确的请求并在您的蜘蛛中复制它。