Scrapy 入门教程之二:解析网页元素提取结构化数据

By | 最新修改:2024-08-17
目录 显示

前言

本文将会分作两个部分发布,这是第二部分。这一部分介绍如何从响应正文提取数据,学会使用 CSS 选择器和 XPath 表达式来获取元素内容,还有用 JSON Lines 格式来导出 Scrapy 提取到的数据到文件,最后自动化翻页来提取更多的数据。之前写作的第一部分,主要介绍 Scrapy 的基本资料和基础应用:创建爬虫项目、创建蜘蛛和运行蜘蛛等,以及如何安装和使用 Python 的虚拟环境 VirtualEnv。

第一部分的链接: Scrapy 入门教程之一:Scrapy 爬虫框架的基础知识

Scrapy and Python Logo

本文的用例

本文会引导你如何抓取网页数据并保存数据到本地文件中。

本文的用例来自 Scrapy 的官方手册 https://docs.scrapy.org/en/2.11/intro/tutorial.html

用例和代码是官方手册复制过来的,本文仅仅做的是翻译成中文,并且产生一些作者的解释,仅此而已。

若有侵权请及时联系站长进行处理:admin@icxzl.com

从响应正文提取数据

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

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

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

https://docs.scrapy.org/en/2.11/topics/request-response.html

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

使用 Scrapy shell 学习提取数据

更多关于 Scrapy shell 的资料,请移步:https://docs.scrapy.org/en/2.11/topics/shell.html

更详细实用的使用教程在:https://docs.scrapy.org/en/2.11/intro/tutorial.html#extracting-data

什么是 response 变量

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

文档中,介绍了如何使用 response 变量获取 HTML 文档中的结构化数据。

什么是 Scrapy shell

Scrapy shell 是用于抓取给定的 URL 或文件中结构化数据的交互式控制台。

在 Scrapy shell 可以尝试诸多的 Scrapy API 快捷方式和各种的 Scrapy 对象的操作和行为来测试从而掌握这些能够在蜘蛛中用的方法和对象。

Scrapy shell 的用法

一、进入 Scrapy shell

用法如下:

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

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

比如:

执行 request 能够获取当前 Scrapy shell 的基本请求信息:

此次请求对象的信息

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

此次响应对象的信息

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

获取响应状态码

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

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

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

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

二、退出 Scrapy shell

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

使用 response.css() 来解析 HTML 信息

Scrapy 的响应对象 Response 提供多种方式来解析 HTML 信息,响应对象实例 response 有不同的方法来解析并获取想要的 HTML 元素。

比如:

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

它们都有各自的语法,并且才能方便地提取到结构化数据。

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

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

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

以及 Scrapy 的官方文档: https://docs.scrapy.org/en/2.11/topics/selectors.html

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

获取网页的标题(Title)

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

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

response.css("title")

上面的代码将获取的是关于 HTML title 元素在 Scrapy 的类似于列表的选择器对象(SelectorList),想要提取出元素的内容,需要执行以下的代码:

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

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

上面的代码执行结果:

使用 .get() 方法提取信息

二、一次性获取多项内容,组成一个字符串列表

使用 .getall() 方法,若是只有一个 HTML 的元素,那么将仅仅获取该元素的内容,并且执行结果也不是个字符串,而是由一个字符串组成的列表。

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

执行结果以下图:

使用 .getall() 方法同时提取多个信息

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

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

执行结果:

使用 .getall() 方法提取出的第一项信息

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

获取 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 的普通列表,可用下标索引来访问里面的字串。

使用 XPath 表达式来获取元素内容

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

XPath 能选择的范围远不是 CSS 选择器能比的,它还可以通过一个内容包含某个关键词或一段字符串而选择后者所在的元素;比如,可以选择元素内容包含“下一页”的锚链接,并且返回的是包含该锚链接的列表选择器对象,而这个锚链接全貌可能是 <a href="https://domain.com/page/2">下一页</a>

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

    执行结果:

    XPath 选择 title 元素

  2. .xpath(query) 得到 title 元素的内容:

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

    执行结果:

    提取 title 元素的内容

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

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

    执行结果:

    XPath 选择下一页

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

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

    执行结果:

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

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

从浏览器打开当前 Scrapy shell 获取的页面

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

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

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

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

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

浏览器显示的网页:

由 Scrapy shell 下载到本地的网页

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

名言和作者

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

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

<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
        >by <small class="author" itemprop="author">Albert Einstein</small>
        <a href="/author/Albert-Einstein">(about)</a>
    </span>
    <div class="tags">
        Tags:
        <meta
            class="keywords"
            itemprop="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>

网页上有多个像这样的结构,所以只要匹配,会一次获取多个不同的名言和作者。

启动 Scrapy shell 测试一下提取数据的规则

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

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

进入 Scrapy shell

一、在每个名言所在的 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
    

    执行结果:

    获取 div.quote 的信息

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

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

    执行结果:

    提取第一个名言的内容

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

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

    执行结果:

    提取第一个名言的作者

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

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

    执行结果:

    提取第一个名言的所有标签

二、迭代所有的“名言”元素
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))

执行结果:

迭代所有的名言信息

在蜘蛛文件中编写代码提取名言数据

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

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

将会代码放进蜘蛛代码文件

将原有的蜘蛛文件清空,然后复制粘贴以下的代码到蜘蛛文件中(这里是 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(),
            }

运行蜘蛛

scrapy crawl quotes

执行的部分结果:

运行爬虫提取第1和2 页的名言信息

停止正在运行的 Scrapy 蜘蛛

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

存储抓取的数据

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

Scrapy 官方文档是建议采用 JSONL 文件的。

JSON Lines 文件格式

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

JSON Lines有以下的特点:

  1. 使用 UTF-8 编码;
  2. 每行都是一个合法的 JSON 值(JSON 序列化的值,只是不包含方括号);

  3. 行分隔符是’\n’,但是 ‘\r\n’ 也会被接受;

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

  4. 建议约定用以 .jsonl 为后缀的文件保存 JSON Lines 的内容。

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

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 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 格式有明显的局限性,如果往文件添加数据,它将会再追加一个 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 格式更易序列化和反序列化。

导出 Scrapy 提取的数据

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

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

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

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

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

scrapy crawl quotes -o quotes.jsonl

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

执行结果:

Scrapy 蜘蛛运行后导出提取的信息到文件

Scrapy 蜘蛛跟踪链接

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

利用 Scrapy 的响应对象从 HTML 提取下一页链接

根据浏览器工具的检查可知,页面 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 中测试提取数据的查询规则。

进入 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.org/en/2.11/topics/selectors.html#selecting-attributes

编写能够访问下一页的 Scrapy 蜘蛛代码

复制粘贴以下的代码到蜘蛛 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 会自动放进 Scrapy 蜘蛛请求队列中,并且会自动地下载相关的 HTML 代码,然后将响应对象的实例交给 parse() 方法,这是蜘蛛中默认的响应处理方法;
  2. yield 在 Python 中表示脚本代码解释器执行到这一步才让返回它所代表的数据,其他时候时时处于待命状态,这不同于 return 关键词;

  3. response.urljoin() 方法合并响应 URL 与相对 URL(这里是以 http 开头的首页 URL 与分页的相对 URL),从而组成一个绝对 URL;

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

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

运行 Scrapy 蜘蛛:

scrapy crawl quotes -o quotes.jsonl

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

执行结果:

爬取所有页面并且提取名言信息同时输出到 JSON Lines 格式的文件

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

quotes.jsonl 文件头:

JSON Lines 格式文件内容的开头

文件尾:

JSON Lines 格式文件内容的末尾

用响应的跟踪方法创建相对链接的 HTTP 请求

将下面的代码替换 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)
    
  5. 还可用 response.follow_all() 从可迭代对象创建多个请求:
    anchors = response.css("ul.pager a")
    yield from response.follow_all(anchors, callback=self.parse)
    
  6. 最短代码做同样的事:
    yield from response.follow_all(css="ul.pager a", callback=self.parse)
    

章节总结

本章节主要介绍了如何利用 Scrapy 的蜘蛛自动地爬取下一页并提取每一页上面的信息,并且还介绍了用不同的方式实现如何去发出跟踪每一页的请求的代码。

用上文的方法递归地爬取作者信息页面

本章节要创建一个新的蜘蛛,用于爬取所有名言作者的页面,同时提取需要的信息。

创建蜘蛛:

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

关于新蜘蛛的代码,Scrapy 官方文档的教程已经写好了,可直接复制粘贴到蜘蛛 author 的文件(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"),
        }

上面的代码中,从主页开始,它会跟踪所有指向名言作者的页面并为每个页面调用 parse_author 回调方法,并且在 parse() 方法中对作者页面的请求回调 parse_author() 方法,然后在该方法中提取和处理信息,用 yield 来持续地为每次响应返回该返回的字典数据;此外,该方法中还有个辅助函数,该函数中的 .get() 方法添加了一个默认值,当 CSS 查询匹配不到规则时,’response.css()’ 方法就返回此默认值,而不是 None

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

运行蜘蛛:

scrapy crawl author -O authors.jsonl

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

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

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

将附加数据传递给回调函数: https://docs.scrapy.org/en/2.11/topics/request-response.html#topics-request-response-ref-request-callback-arguments

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

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

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

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

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

import scrapy


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

    def start_requests(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.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。

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

全文总结

本文把 Scrapy 的官方文档中的 Scrapy 入门教程以容易理解同时使用更多注解的方式编写出来,篇章比较长,但是贵在实用。

Scrapy 是个优秀的爬虫框架,使用它来爬取网站或者 API 的内容,可以省下许多不必要的代码,从而做到 Python 哲学中的“Write less code, do more”信条。

本文所接触的仅是 Scrapy 能做的其中一点点,要想学习更多,还须继续耕耘和努力。

关于 Scrapy 所有的一切都在官方文档中,要是你想更好地使用 Scrapy 来做爬虫,写最少的代码,做最多的事,那么有空就多看看文档,边用边学更好。

作者也是个 Scrapy 小白,数年前接触过,并且做过一些爬虫项目,但是都没有用到 Scrapy 真正方便的功能,因为不了解,写代码时走了不少的弯路。要不是因为博客被百度处罚,说是内容质量太差,要整顿,作者也不会翻出几年前写的类似的文章。

本文不是原来的文章,原来的已经 404 了,本文是重新编写的。

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

(本文完)


程序知路

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

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

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