新手必看的 Scrapy 爬虫数据提取与调试实战指南

By | 最新修改:2025-05-22
目录 显示

1. 前言

本文是一篇关于 Scrapy 爬虫数据提取实践的新手入门教程。主要介绍如何从爬虫的响应正文提取数据,学会使用 CSS 选择器、正则表达式和 XPath 表达式,同时以 JSON Lines 格式导出到文件,最后自动化翻页获取更多的信息。

阅读完本文,您将解锁以下的知识:

  1. 进入和使用 Scrapy Shell 进行交互式的爬虫调试;
  2. 利用 CSS 选择器和 XPath 表达式提取数据;
  3. 如何应用正则表达式来提取你想要的内容;
  4. 了解 Request(请求对象) 和 Response(响应对象);
  5. JSONJSON Lines 两种数据保存格式;
  6. JSON Lines 与 JSON 区别;
  7. 如何跟踪链接实现自动翻页爬虫。

前面已经发布了一篇文章(Python Scrapy 爬虫框架入门教程)介绍了基本资料和基础应用:创建爬虫项目、创建和运行蜘蛛等,以及如何安装和使用 Python 的虚拟环境 VirtualEnv。

解析网页元素提取数据


2. 蜘蛛的基本运行逻辑

start() 方法(通过 scrapy.Request 对象)发出 HTTP 请求,到通过 scrapy.Response 对象实例来接收响应,再调用 parse() 方法来处理响应内容。

响应正文往往是结构化数据,如果响应体的内容是 HTML 代码,可根据需求利用蜘蛛类的 parse() 方法的参数同时也是对象 scrapy.Response 的实例 response 变量的元素选择器来提取信息。

总结起来就是,Scrapy的工作流程遵循经典的请求-响应模式:

  1. 通过scrapy.Request发起HTTP请求
  2. 接收scrapy.Response响应对象
  3. parse()方法中处理响应

关于 scrapy.Requestscrapy.Response 两个对象的更多解释,可参考 Scrapy 中文文档:

https://docs.scrapy.net.cn/en/latest/topics/request-response.html


3. 交互式调试:Scrapy Shell 的入门与进阶

官方文档建议在 Scrapy Shell 环境下学习如果提取数据,并且是最佳学习途径。

更多资料,请移步:https://docs.scrapy.net.cn/en/latest/topics/shell.html

更详细实用的使用教程:https://docs.scrapy.net.cn/en/latest/intro/tutorial.html#extracting-data

3.1. 基本概念

Scrapy Shell 是 Scrapy 提供的交互式控制台,用于快速测试数据提取规则。

在这里可以尝试诸多的 Scrapy API 的操作和行为,从而掌握这些能够在蜘蛛中用的方法和对象。

3.2. 什么是响应对象实例变量

官方文档的入门教程中,将介绍如何使用响应对象实例 response 提取 HTML 文本中的结构化数据。这里的 response 变量与蜘蛛代码中的 parse() 方法的同名参数变量是同一个变量,或者说都是 scrapy.Response 这个对象的实例,也就是说它们拥有的类属性和类方法是一致的。

3.3. Scrapy Shell 使用教程

3.3.1. 创建一个 Shell 会话

本文的命令行终端命令均在 VirtualEnv 虚拟环境下运行。

用法如下:

scrapy shell [url|file]
  • url 指的是远程文档的网址;
  • file 指的是电脑本地的文件的全路径。

也就是说,既可从远程文档,也可从本地特定文件的内容中获取数据。

更多的选项请参阅 HELP 文档: scrapy shell --help

官方文档的教程中,以远程 URL https://quotes.toscrape.com/page/1/ 为例,进入 Scrapy Shell:

scrapy shell "https://quotes.toscrape.com/page/1/"

执行上面命令将会出现类似下图的界面:

进入 Scrapy Shell
启动会话后,直接获取 request 和 response 对象。

3.3.2. 基本使用

进入 Scrapy Shell 即可使用之,可在 >>> 提示符后面输入想要运行的对象、属性、函数和方法等 Scrapy API。

比如:

执行 request 能够获取基本请求信息:

此次请求对象的信息

执行 response 能够获取 HTTP 响应的基本信息:

此次响应对象的信息

执行 response.status 获取当前响应的状态码,如 200、404 等:

获取响应状态码

200 表示当前 HTTP 请求并接收响应成功,而 response.body 则打印出响应体的文本。

response.attributesrequest.attributes 可分别获取所有它们拥有的类属性:

列出请求和响应对象的属性

RequestResponse 对象的更多资料,请移步:https://docs.scrapy.org/en/latest/topics/request-response.html

3.3.3. 退出当前会话

执行 quit() 函数即可退出 Scrapy Shell。


4. Scrapy 数据提取核心技术:CSS 与 XPath 选择器

Scrapy 数据提取工作原理其实不难理解,就是按用户的需求应用爬虫内置的 API 或者 Python 正则表达式把一项项数据解析出来。

比如:获取 HTML 文档中的某项内容,可以使用 Scrapy 提供的 CSS 或 XPath 选择器,两者都是爬虫系统内置的 API,也可以使用正则表达式,来获取想要的内容。

甚至可以用外部的库来解析内容,比如 BeautifulSoup。

本章节覆盖 CSS 选择器使用技巧,XPath 表达式应用案例,以及 Scrapy Shell 数据提取的调试方法详解。

4.1. 什么是 CSS 和 XPath

系统对 Scrapy 数据提取内置着两套方案,一是 CSS 选择器,另一个是 XPath 表达式,它们都是 Response 对象的方法,同时也是 scrapy.Selector 对象的方法。

接下来的文章内容开始大篇幅讲解如何使用上述的两套方案:

  1. response.xpath(query):它是 TextResponse.selector.xpath(query) 的快捷方式,利用【XPath】表达式来选择元素;
  2. response.css(query):它是 TextResponse.selector.css(query) 的快捷方式,使用【CSS DOM 选择器】来获取元素。

query 指查询的语句,语句的语法要分别符合上述两者的规定。

其中 response.css() 的提取语法类似于著名且广泛使用的 JavaScript 类库 jQuery 的选择器语法。

具体的详细介绍可参考: https://www.w3.org/TR/selectors/

以及中文文档: https://docs.scrapy.net.cn/en/latest/topics/selectors.html

这里不多说,就举几个应用的例子。

4.2. 使用 CSS 选择器提取数据

4.2.1. 获取网页的标题选择器对象

在 HTML 元素中,有个 title 元素,它里面装的是网页的标题,也正是当前要获取的内容所在元素。

这里在 Scrapy Shell 的环境下,获取网页 https://quotes.toscrape.com/page/1/ 的页面标题:

response.css("title")

上面的代码将获取的是 HTML title 元素选择器对象列表(SelectorList)。

4.2.2. 获取单个元素

response.css("title::text").get()
  1. ::text 表示 HTML 元素的内容(不包括 HTML 标签自身),如果就这样执行,也是会获取到一个选择器对象;
  2. .get() 则表示获取元素字符串内容,返回值非 Python 对象。

    此方法仅仅获取一次内容,想要获取多个文本内容组成的列表,则需要使用 .getall()

上面的代码执行结果:

使用 .get() 方法提取单个数据

4.2.3. 获取所有匹配元素

当然,正常情况下,一个 HTML 文档中仅有一个 title 元素,本章节就举个例子,把它当作有很多。

使用 .getall() 方法,获取元素的内容,返回的是由所有匹配元素字符串组成的列表。

response.css("title::text").getall()

执行结果以下图:

使用 .getall() 方法批量提取数据

此时要获取内容,需要在上面代码的后面加上列表的索引,因为是第一个,所以索引是 0

response.css("title::text").getall()[0]

执行结果:

提取所有匹配元素中的第一项内容

4.2.4. 应用正则表达式来获取内容

获取 HTML 元素内容时,除了使用 get()getall() 方法,还可以用更加灵活的正则表达式来提取想要的内容字符串。

response.css() 方法返回的是一个选择器对象列表,此对象有个方法 re(regex) 能用正则表达式来获取匹配的内容。

re() 方法采用的是 Python 标准的正则表达式,有兴趣了解更多可访问:https://docs.python.org/zh-cn/3/library/re.html

从上文可知,当前网页中的标题内容为:

Quotes to Scrape
  1. 字符个数为 6 的单词:
    response.css("title::text").re(r"\w{6}")
    

    执行结果:

    ['Quotes', 'Scrape']
    
  2. 前后两个单词,即不要 “to” 以及其前后的空格:
    response.css("title::text").re(r"(\w+) to (\w+)")
    

    执行结果:

    ['Quotes', 'Scrape']
    
  3. 全部匹配的内容:
    response.css("title::text").re(r".+")
    

    执行结果:

     ['Quotes to Scrape']
    

由上述的执行结果可以得出结论:re() 方法得到一个由匹配字串组成的列表,是 Python 的普通列表,可用下标索引来访问里面的字串。

4.3. XPath 提取数据

据文档描述:XPath 表达式非常强大,它是 Scrapy 的基础选择器,就连本文前面但要的 CSS 选择器也是基于它。事实上,CSS 选择器在底层被转换为 XPath。

XPath 能选择的范围远不是 CSS 选择器能比的,它还可以通过测试是否包含一段字符串而选择其所在的元素,使用方式: response.xpath("//element[contains(选择器,'包含的文本字符串')]");比如,可以选择元素内容包含“下一页”的锚链接,此时要用 response.xpath("//a[contains(.//text(),'Next')]") 来选择,若是匹配,则返回的是包含该锚链接的选择器对象列表,而这个锚链接全貌可能是 <a href="https://domain.com/page/2">下一页</a>

  1. .xpath(query) 选择 title 元素:
    response.xpath("//title")
    

    执行结果:

    XPath 选择 title 元素

  2. 使用 XPath 得到 title 元素的内容:

    response.path("//title/text()").get()
    

    执行结果:

    提取 title 元素的内容

  3. 使用 XPath 选择“下一页”的锚链接:

    response.xpath("//a[contains(.//text(),'Next')]")
    

    执行结果:

    XPath 选择下一页

  4. 获取“下一页”锚链接中的 URL:

    response.xpath("//a[contains(.//text(),'Next')]/@href").get()
    

    执行结果:

    提取所有 a 锚链接中的 href 属性值

更多关于 XPath 的资料请参阅 W3C 的标准:XML Path Language (XPath) 3.1

4.4. 在浏览器中打开当前获取的网页

上文的对于用 response.css() 用法的演示都是使用 HTML 标题元素,然而,网页上通常只有一个 title 元素。

而 Scrapy 爬虫要抓取的数据有时是复杂的,至少比取得标题要复杂得多,可能一个查询规则会返回多个元素和内容,这时就要深入地研究网页的构成。

一般情况下,可以用浏览器通过网址访问该远程网页,然后查看网页源代码,再从源代码找到想要的元素和内容,最后才用 .css() 来选择合需求的内容。

如果读者不想访问远程的网址,可以在 Scrapy Shell 通过执行 view(response) 在浏览器打开当前的网页,这访问的是本地的副本,不是远程的。

从浏览器打开当前爬取的网页

浏览器显示的网页:

由爬虫下载到本地的网页

当然,网页上的 CSS/JS 等资源还是要从远程获取的,但是这无关紧要,要的仅是网页的代码。

4.5. 在 Scrapy Shell 中调试提取数据的方法

在网站 https://quotes.toscrape.com/ 的 HTML 源代码中,包含名言和作者的 HTML 元素结构如下所示:

<div class="quote"  >
    <span class="text" 
        >“The world as we have created it is a process of our thinking. It
        cannot be changed without changing our thinking.”</span
    >
    <span
        >by <small class="author" >Albert Einstein</small>
        <a href="/author/Albert-Einstein">(about)</a>
    </span>
    <div class="tags">
        Tags:
        <meta
            class="keywords"
            
            content="change,deep-thoughts,thinking,world"
        />
        <a class="tag" href="/tag/change/page/1/">change</a>
        <a class="tag" href="/tag/deep-thoughts/page/1/">deep-thoughts</a>
        <a class="tag" href="/tag/thinking/page/1/">thinking</a>
        <a class="tag" href="/tag/world/page/1/">world</a>
    </div>
</div>

网页上有多个像这样的结构,所以只要匹配,会一次提取多组数据。

4.5.1. 启动 Scrapy Shell 测试一下提取规则

进入 scrapy shell "https://quotes.toscrape.com"

刚进入的界面如下图所示:

进入 Scrapy Shell

4.5.2. 从 HTML 结构中获取多个不同元素及其元素值

我们要从网页的 HTML 代码中提取出名言相关信息,其中,从分析 HTML 代码可知,单个独立的 div class="quote" 中有若干项数据,包括名言内容、作者名称和链接,和标签。而一个页面中有若干个 div class="quote"

  1. 获取 div.quote,并把选择器列表赋值给变量,以备以后使用:
    # 获取所有的名言 HTML 结构
    quotes = response.css("div.quote")
    # 获取第一个名言 HTML 结构
    quote = response.css("div.quote")[0]
    quotes
    quote
    

    执行结果:

    使用 CSS 选择器通过元素的类名来选择数据

  2. 获取“名言”:所在的结构 div.quote > span.text,因此:

    quote.css("span.text::text").get()
    

    执行结果:

    从得到的选择器中提取元素内容

  3. 获取“作者”:它的结构是 div.quote > span > small.author

    quote.css("small.author::text").get()
    

    执行结果:

    从 a 链接中提取锚文本

  4. 获取“标签”:所在结构是 div.quote > div.tags > a.tag

    quote.css("div.tags>a.tag::text").getall()
    

    执行结果:

    从 CSS 选择器中提取所有的标签内容

4.5.3. 迭代输出所有获得的元素内容

for quote in response.css("div.quote"):
    text = quote.css("span.text::text").get()
    author = quote.css("small.author::text").get()
    tags = quote.css("div.tags>a.tag::text").getall()
    print(dict(text=text, author=author, tags=tags))

执行结果:

迭代选择器列表提取出结构化数据组成字典以 JSON 格式输出


5. Scrapy 数据提取实战案例

本文的前面部分介绍了如何从 Scrapy 爬取的网页提取数据,这一部分将说明如何在蜘蛛代码中应用上述的提取技能。

通常蜘蛛会爬取很多的网页,每个网页又提取很多信息项。这种情况下需要用到 yield 关键词,它会将每次迭代提取的信息打印在终端上,或者保存到数据库或文件中。

现在你已经了解了一些有关选择和提取的知识,接下来完成通过编写蜘蛛的代码从网页中提取数据。

5.1. 最简单的 Scrapy 爬虫实例

将原有的蜘蛛文件清空,然后复制粘贴以下的代码到蜘蛛文件中(这里是 tutorial/spiders/quotes.py):

import scrapy

class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = [
        "https://quotes.toscrape.com/page/1/",
        "https://quotes.toscrape.com/page/2/",
    ]

    def parse(self, response):
        for quote in response.css("div.quote"):
            yield {
                "text": quote.css("span.text::text").get(),
                "author": quote.css("small.author::text").get(),
                "tags": quote.css("div.tags a.tag::text").getall(),
            }

5.2. 运行蜘蛛

scrapy crawl quotes

运行的部分结果:

运行蜘蛛举例示意图

5.3. 停止正在运行的蜘蛛

按下键盘上的 Ctrl + D 组合键即可,按一次不停止,就按多次。

这是个简单的爬虫案例,实际应用中,蜘蛛可以编写得很复杂。

那么,Scrapy 爬虫爬取的数据怎样处理储存呢?一般有两种方式,一是写入文本文件,二是存入数据库。

由于数据库要额外学习,在本教程的宗旨是快速入门,因此,容易写入和读取的文本文件成为本文使用的数据存放方式。

接下来介绍两种常见的文本数据文件格式:JSON LinesJSON


6. Scrapy 数据存储格式

最简单的提取数据的储存方式是用文件,序列化的格式使用 .json.jsonl 两种导出方式的文件类型。

Scrapy 官方文档建议采用 JSONL 文件格式保存提取的数据。

6.1. JSON Lines 文件格式

JSON 行(JSON Lines)文本格式,也称为换行分隔的 JSON。JSON 行是一种存储结构化数据的方便格式,能够一次处理一条记录,每行是单独的一条数据,这与 JSON 格式明显不同。它适用于 unix 风格的文本处理工具和 Shell 管道。这是一种很好的日志文件格式。它还是在协作进程之间传递消息的灵活格式。

JSON Lines 文件内容的格式如下:

{"text": "“I like nonsense, it wakes up the brain cells. Fantasy is a necessary ingredient in living.”", "author": "Dr. Seuss", "tags": ["fantasy"]}
{"text": "“I may not have gone where I intended to go, but I think I have ended up where I needed to be.”", "author": "Douglas Adams", "tags": ["life", "navigation"]}
{"text": "“The opposite of love is not hate, it's indifference. The opposite of art is not ugliness, it's indifference. The opposite of faith is not heresy, it's indifference. And the opposite of life is not death, it's indifference.”", "author": "Elie Wiesel", "tags": ["activism", "apathy", "hate", "indifference", "inspirational", "love", "opposite", "philosophy"]}
...

JSON Lines 有以下的特点:

  1. 使用 UTF-8 编码;
  2. 每行都是一个合法的 JSON 值(JSON 序列化的值,只是不包含方括号);
  3. 行分隔符是’\n’,但是 ‘\r\n’ 也会被接受;

    然而处理 JSON Lines 文件的每一行时,忽略掉行分隔符,就好像没有分隔符一般,这给了用户极大的方便;

  4. Scrapy 约定俗成把 .jsonl 为后缀的文件保存 JSON Lines 的内容。

    • 建议使用 gzip 或 bzip2 等流压缩器来节省空间,从而生成 .jsonl.gz 或 .jsonl.bz2 文件。
    • .jsonl 文件的 MIME 类型可能是 application/jsonl,但是它至今还没有标准化。
    • 文件内,行的称呼问题:文本编辑器中称第一行为“行1”,而在 JSON Lines 文件中的第一个(行)值称呼为“值1”。

6.2. JSON 文件格式

JSON 全称“JavaScript Object Notation”,是一种通用的数据保存格式,它以字典和列表的形式储存在文本文件中。

JSON 文件内容的格式大致如下:

[ {"text": "“I like nonsense, it wakes up the brain cells. Fantasy is a necessary ingredient in living.”", "author": "Dr. Seuss", "tags": ["fantasy"]},
{"text": "“I may not have gone where I intended to go, but I think I have ended up where I needed to be.”", "author": "Douglas Adams", "tags": ["life", "navigation"]},
{"text": "“The opposite of love is not hate, it's indifference. The opposite of art is not ugliness, it's indifference. The opposite of faith is not heresy, it's indifference. And the opposite of life is not death, it's indifference.”", "author": "Elie Wiesel", "tags": ["activism", "apathy", "hate", "indifference", "inspirational", "love", "opposite", "philosophy"]},
...
]

从以上的举例可以看出, JSON 格式有明显的局限性,如果往文件添加数据,它将会再追加一个 JSON 段落,就像如下:

[
{"text": "“I like nonsense, it wakes up the brain cells. Fantasy is a necessary ingredient in living.”", "author": "Dr. Seuss", "tags": ["fantasy"]},
{"text": "“I may not have gone where I intended to go, but I think I have ended up where I needed to be.”", "author": "Douglas Adams", "tags": ["life", "navigation"]},
...
][
{"text": "“Try not to become a man of success. Rather become a man of value.”", "author": "Albert Einstein", "tags": ["adulthood", "success", "value"]},
{"text": "“It is better to be hated for what you are than to be loved for what you are not.”", "author": "André Gide", "tags": ["life", "love"]},
...
]

毫不意外,这就破坏了 JSON 约定俗成的格式。

而 JSON Lines 数据的添加则更简单,并一如既往地易读取和写入,就是在原先的文件内容的末尾追加若干行。

由此可见,以行(一行为一个值)为单位的 JSON Lines 格式更适合读取和添加数据,而 JSON 格式更易序列化和反序列化。

以上内容充分讲述了 JSON 与 JSON Lines 在 Scrapy 中的区别与选择,显然后者更加方便,但这不是绝对,JSON 格式也有其可取之处,主要看使用场景决定而已。

6.3. 导出爬虫提取的数据

将数据从蜘蛛中输出到文件,这里采用建议的 JSON Lines 格式。

需要用到 -o 选项。导出数据的命令格式为:scrapy crawl 蜘蛛名称 -o 文件名.文件格式-o 选项表示追加到文件中,不会覆盖原有的文件内容。

然而,如果用选项 -O(大写的字母 o) 会覆盖原文件。

具体的选项列表在 scrapy crawl --help

导出蜘蛛 quotesquotes.jsonl,并且以追加内容的方式:

scrapy crawl quotes -o quotes.jsonl

Scrapy 的程序会自动识别导出的文件格式。

执行结果:

爬虫导出数据到文件

此章节运行一个爬虫例子,演示了 Scrapy 如何导出 JSON Lines 格式数据,倘若要保存到 JSON 文件,那么仅须将 -o quotes.jsonl 更换为 -o quotes.json,系统会自动识别。


7. 爬虫自动跟踪链接与 Scrapy 翻页技巧

通过获取指向下一页的链接来抓取更多的数据,这就是爬虫跟踪链接,自动获得下一个要爬取的 URL。

7.1. 利用响应对象从结构化文本获取下一页链接

根据浏览器工具的检查可知,页面 https://quotes.toscrape.com/ 的下一页链接文字“Next”所在的 HTML 结构如下所示:

<ul class="pager">
    <li class="next">
        <a href="/page/2/">Next <span aria-hidden="true">→</span></a>
    </li>
</ul>

前文就介绍到 Scrapy Shell,可以调试提取数据的查询规则。

创建并进入会话:scrapy shell "https://quotes.toscrape.com/"

经过调试,就可以把这些规则和代码写入到蜘蛛代码文件中,让蜘蛛正确地爬取和处理数据。

比如,获取 Next 所在的锚链接的 href 属性,可执行以下的代码:

response.css("li.next a::attr(href)").get()

执行结果:

'/page/2/'

也可以使用 attrib 属性做与上述同样的事情:

response.css("li.next a").attrib["href"]

元素属性参考中文文档:https://docs.scrapy.net.cn/en/latest/topics/selectors.html#selecting-attributes

7.2. 翻页跟踪实现

复制粘贴以下的代码到蜘蛛 quotes 代码文件中:

import scrapy

class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = [
        "https://quotes.toscrape.com/page/1/",
    ]

    def parse(self, response):
        for quote in response.css("div.quote"):
            yield {
                "text": quote.css("span.text::text").get(),
                "author": quote.css("small.author::text").get(),
                "tags": quote.css("div.tags a.tag::text").getall(),
            }

        next_page = response.css("li.next a::attr(href)").get()
        if next_page is not None:
            next_page = response.urljoin(next_page)
            yield scrapy.Request(next_page, callback=self.parse)

代码解释:

  1. start_urls 是一个 URL 列表,列表里面的 URL 会自动放进蜘蛛请求队列中,并且会自动地下载相关的 HTML 代码,然后将响应对象的实例交给 parse() 方法,这是蜘蛛中默认的响应处理方法;
  2. yield 在 Python 中表示脚本代码解释器执行到这一步才让返回它所代表的数据,其他时候时时处于待命状态,这异于 return 关键词;
  3. response.urljoin() 方法合并响应 URL 与相对 URL(这里是以 http 开头的首页 URL 与分页的相对 URL),从而组成一个绝对 URL;

    若下一页的链接不是个相对 URL,而是个拥有绝对路径的完整 URL,那么这里就不用该方法,这时可直接把 .get() 方法提取到的 URL 作为第一个参数传递进 scrapy.Request() 方法中;

  4. yield scrapy.Request(next_page, callback=self.parse) 该行代码将对每一个由下一页锚链接提取的 URL 进行爬取。

运行爬虫:

scrapy crawl quotes -o quotes.jsonl

执行上述命令将会爬取所有页面,从而提取所有页面中想要的信息。

执行结果:

爬取所有分页同时输出到 JSON Lines 格式的文件

导出的文件名为 quotes.jsonl,它的文件内容中每一行分别为一个值,值是个序列化的字典,里面包含每次提取的数据,然后一行行地写入文件。

quotes.jsonl 文件头:

JSON Lines 格式文件内容的开头

文件尾:

JSON Lines 格式文件内容的末尾

7.3. 利用 response.follow () 实现分页爬取

将下面的代码替换 tutorial/spiders/quotes.py 文件中的内容:

import scrapy

class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = [
        "https://quotes.toscrape.com/page/1/",
    ]

    def parse(self, response):
        for quote in response.css("div.quote"):
            yield {
                "text": quote.css("span.text::text").get(),
                "author": quote.css("span small::text").get(),
                "tags": quote.css("div.tags a.tag::text").getall(),
            }

        next_page = response.css("li.next a::attr(href)").get()
        if next_page is not None:
            yield response.follow(next_page, callback=self.parse)

这里得到与上一个章节同样的结果。

  1. scrapy.Request() 不同,response.follow() 方法直接支持相对 URL,不再需要调用 urljoin;
  2. response.follow() 同样返回一个请求实例,用户必须 yield 这个请求实例;

  3. 可直接传递 href 的选择器给 response.follow() 方法的第一个参数,而非字符串 URL:

    for href in response.css("ul.pager a::attr(href)"):
        yield response.follow(href, callback=self.parse)
    
  4. 可直接用 a 元素的选择器:
    for a in response.css("ul.pager a"):
        yield response.follow(a, callback=self.parse)
    

7.4. 以 CSS 选择器对象列表的方式批量传递进 .follow_all() 方法

  1. 还可用 response.follow_all() 从可迭代对象同时创建多个请求:
    anchors = response.css("ul.pager a")
    yield from response.follow_all(anchors, callback=self.parse)
    
  2. 最短代码做同样的事:
    yield from response.follow_all(css="ul.pager a", callback=self.parse)
    

7.5. 运行这一章节的示例蜘蛛

scrapy crawl quotes -o quotes.jsonl

这一部分也演示了如何让 Scrapy 保存数据到 JSON Lines 文件。

7.6. 章节总结

本章节主要介绍了如何让蜘蛛自动翻页,并且还介绍了实现分别用锚链接字符串或选择器对象列表通过 response.follow() 或 response.follow_all() 以不同的方式发出翻页请求。其实这也是个 Scrapy 爬虫自动翻页实现技巧。


8. Scrapy 爬虫跟踪链接自动翻页实战

本章节要创建一个稍为复杂的新爬虫,用于爬取所有名言作者的详情页面,同时提取需要的数据,以 JSON Lines 的格式输出到文件。并且验证上文到的 Scrapy 链接跟踪和自动翻页技术。

8.1. 创建蜘蛛

scrapy genspider author "https://quotes.toscrape.com/"

8.2. 覆盖新创建的蜘蛛代码

官方文档的教程已经给出了新的代码,可直接复制粘贴到文件 tutorial/spiders/author.py 中:

import scrapy

class AuthorSpider(scrapy.Spider):
    name = "author"

    start_urls = ["https://quotes.toscrape.com/"]

    def parse(self, response):
        author_page_links = response.css(".author + a")
        yield from response.follow_all(author_page_links, self.parse_author)

        pagination_links = response.css("li.next a")
        yield from response.follow_all(pagination_links, self.parse)

    def parse_author(self, response):
        def extract_with_css(query):
            return response.css(query).get(default="").strip()

        yield {
            "name": extract_with_css("h3.author-title::text"),
            "birthdate": extract_with_css(".author-born-date::text"),
            "bio": extract_with_css(".author-description::text"),
        }

上面的代码中:

  1. 从主页开始,它会跟踪所有指向作者详情页面的锚链接并为每个页面调用 parse_author() 回调方法;
  2. parse() 方法中对页面的请求回调 parse_author() 方法,然后在该方法中提取数据,这些数据可以由管道(pipeline)交给 Items 处理;
    关于“管道”和“Items” 请参阅中文文档获取更多的信息:
  3. yield 来持续地为每次响应返回该返回的字典数据。
  4. 该方法中还有个辅助函数,该函数中的 .get() 方法添加了一个默认值,当 CSS 查询匹配不到规则时,’response.css()’ 方法就返回此默认值,而不是 None

文档教程中还指出:用户不用担心会有重复的页面被请求,因为在同一次爬取中, Scrapy 会自动过滤掉重复的内容,以及不会再次访问已经访问过的 URL。

8.3. 运行蜘蛛

scrapy crawl author -O authors.jsonl

这次用上 -O 选项,为的是再次爬取时覆盖同名文件。

当你阅读到这里时,很大程度上你已经了解和使用 Scrapy 如何跟踪链接和回调的机制。

Scrapy 分页爬虫至此详述完毕。

你也可以了解一下另外一种利用跟踪链接机制的爬虫——CrawlSpider

将附加数据传递给回调函数:https://docs.scrapy.net.cn/en/latest/topics/request-response.html#passing-additional-data-to-callback-functions


9. 利用蜘蛛参数指定爬取的条件

这是《入门教程》中的最后一个示例,通过在爬虫代码中设定属性,然后在爬取命令中使用 -a 参数指定键值对。

设置好这些,爬虫将按 -a 参数指定的属性键值对来过滤爬取的访问,只会访问符合该属性键与值标记的 URL。

阅读爬取命令选项的帮助,请在终端执行:scrapy crawl --help

修改蜘蛛 quotes 的代码文件 tutorial/spiders/quotes.py,然后用文档中的代码覆盖:

import scrapy

class QuotesSpider(scrapy.Spider):
    name = "quotes"

    async def start(self):
        url = "https://quotes.toscrape.com/"
        tag = getattr(self, "tag", None)
        if tag is not None:
            url = url + "tag/" + tag
        yield scrapy.Request(url, self.parse)

    def parse(self, response):
        for quote in response.css("div.quote"):
            yield {
                "text": quote.css("span.text::text").get(),
                "author": quote.css("small.author::text").get(),
            }

        next_page = response.css("li.next a::attr(href)").get()
        if next_page is not None:
            yield response.follow(next_page, self.parse)

运行蜘蛛:

scrapy crawl quotes -o quotes-humor.jsonl -a tag=humor

在上述运行的命令中:

  1. -O 选项指定从网页提取出来的信息的输出文件,这里选用 JSON Lines 格式,如果文件扩展名为 .json,则使用 JSON 格式,这由 Scrapy 自动判断。
  2. -a 选项指定要过滤的条件,在代码中指定了该属性(用 getattr() 函数,该函数的第二个参数便是),然后用赋值的方式指定 tag 属性的值。

    代码中,tag = getattr(self, "tag", None) 表示:获取命令行中由-a选项指定的 tag 属性的值,然后赋这个值给变量 tag

    如果 -a 选项设为 -a tag=humor,则表示仅访问 tag 值为 humor 的 URL。

    此选项要结合实际情况来使用。


10. 全文总结

本文系统介绍了 Scrapy 在数据提取中的核心技术,包括 Shell 调试、CSS/XPATH 选择器应用、正则表达式、数据存储及链接跟踪。并且利用 JSON Lines 实现高效存储,并通过请求 / 响应对象构建灵活的爬虫逻辑,可显著提升信息采集的效率与稳定性。


题外话

  1. 本文把 Scrapy 的官方文档中的入门教程以容易理解同时使用更多注解的方式编写出来,篇章比较长,但是可贵在于实用。
  2. 这是个优秀的爬虫框架,使用它来爬取网站或者 API 的内容,可以省下许多不必要的代码,从而做到 Python 哲学中的“Write less code, do more”信条。
  3. 本文所接触的仅是该爬虫框架能做的其中一点点,要想学习更多,还须继续耕耘和努力。
  4. 所有的一切都在官方文档中,要是你想更好地使用 Scrapy 来做爬虫,写最少的代码,做最多的事,那么有空就多看看文档,边用边学更好。
  5. 本文的先修课:Python Scrapy 爬虫框架入门教程

最后,非常感谢你看到文章最后笔者的啰嗦,祝用好!

(本文完)


程序知路

鉴于本人的相关知识储备以及能力有限,本博客的观点和描述如有错漏或是有考虑不周到的地方还请多多包涵,欢迎互相探讨,一起学习,共同进步。

本文章可以转载,但是需要说明来源出处!

本文使用的部分图片来源于网上,若是侵权,请与本文作者联系删除: admin@icxzl.com