1. 前言
本文是一篇关于 Scrapy 爬虫数据提取实践的新手入门教程。主要介绍如何从爬虫的响应正文提取数据,学会使用 CSS 选择器、正则表达式和 XPath 表达式,同时以 JSON Lines 格式导出到文件,最后自动化翻页获取更多的信息。
阅读完本文,您将解锁以下的知识:
- 进入和使用 Scrapy Shell 进行交互式的爬虫调试;
- 利用 CSS 选择器和 XPath 表达式提取数据;
- 如何应用正则表达式来提取你想要的内容;
- 了解
Request
(请求对象) 和Response
(响应对象); JSON
与JSON Lines
两种数据保存格式;- JSON Lines 与 JSON 区别;
- 如何跟踪链接实现自动翻页爬虫。
前面已经发布了一篇文章(Python Scrapy 爬虫框架入门教程)介绍了基本资料和基础应用:创建爬虫项目、创建和运行蜘蛛等,以及如何安装和使用 Python 的虚拟环境 VirtualEnv。
2. 蜘蛛的基本运行逻辑
从 start()
方法(通过 scrapy.Request
对象)发出 HTTP 请求,到通过 scrapy.Response
对象实例来接收响应,再调用 parse()
方法来处理响应内容。
响应正文往往是结构化数据,如果响应体的内容是 HTML 代码,可根据需求利用蜘蛛类的 parse()
方法的参数同时也是对象 scrapy.Response
的实例 response
变量的元素选择器来提取信息。
:
总结起来就是,Scrapy的工作流程遵循经典的请求-响应模式:
- 通过
scrapy.Request
发起HTTP请求 - 接收
scrapy.Response
响应对象 - 在
parse()
方法中处理响应
关于
scrapy.Request
跟scrapy.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/"
执行上面命令将会出现类似下图的界面:
启动会话后,直接获取 request 和 response 对象。
3.3.2. 基本使用
进入 Scrapy Shell 即可使用之,可在 >>>
提示符后面输入想要运行的对象、属性、函数和方法等 Scrapy API。
比如:
执行 request
能够获取基本请求信息:
执行 response
能够获取 HTTP 响应的基本信息:
执行 response.status
获取当前响应的状态码,如 200、404 等:
200
表示当前 HTTP 请求并接收响应成功,而 response.body
则打印出响应体的文本。
response.attributes
和 request.attributes
可分别获取所有它们拥有的类属性:
Request
和 Response
对象的更多资料,请移步: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 对象的方法。
接下来的文章内容开始大篇幅讲解如何使用上述的两套方案:
response.xpath(query)
:它是TextResponse.selector.xpath(query)
的快捷方式,利用【XPath】表达式来选择元素;-
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()
::text
表示 HTML 元素的内容(不包括 HTML 标签自身),如果就这样执行,也是会获取到一个选择器对象;-
.get()
则表示获取元素字符串内容,返回值非 Python 对象。此方法仅仅获取一次内容,想要获取多个文本内容组成的列表,则需要使用
.getall()
。
上面的代码执行结果:
4.2.3. 获取所有匹配元素
当然,正常情况下,一个 HTML 文档中仅有一个 title
元素,本章节就举个例子,把它当作有很多。
使用 .getall()
方法,获取元素的内容,返回的是由所有匹配元素字符串组成的列表。
response.css("title::text").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
- 字符个数为 6 的单词:
response.css("title::text").re(r"\w{6}")
执行结果:
['Quotes', 'Scrape']
- 前后两个单词,即不要 “to” 以及其前后的空格:
response.css("title::text").re(r"(\w+) to (\w+)")
执行结果:
['Quotes', 'Scrape']
- 全部匹配的内容:
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>
。
- 用
.xpath(query)
选择title
元素:response.xpath("//title")
执行结果:
-
使用 XPath 得到
title
元素的内容:response.path("//title/text()").get()
执行结果:
-
使用 XPath 选择“下一页”的锚链接:
response.xpath("//a[contains(.//text(),'Next')]")
执行结果:
-
获取“下一页”锚链接中的 URL:
response.xpath("//a[contains(.//text(),'Next')]/@href").get()
执行结果:
更多关于 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"
。
刚进入的界面如下图所示:
4.5.2. 从 HTML 结构中获取多个不同元素及其元素值
我们要从网页的 HTML 代码中提取出名言相关信息,其中,从分析 HTML 代码可知,单个独立的 div class="quote"
中有若干项数据,包括名言内容、作者名称和链接,和标签。而一个页面中有若干个 div class="quote"
。
- 获取
div.quote
,并把选择器列表赋值给变量,以备以后使用:# 获取所有的名言 HTML 结构 quotes = response.css("div.quote") # 获取第一个名言 HTML 结构 quote = response.css("div.quote")[0] quotes quote
执行结果:
-
获取“名言”:所在的结构
div.quote > span.text
,因此:quote.css("span.text::text").get()
执行结果:
-
获取“作者”:它的结构是
div.quote > span > small.author
:quote.css("small.author::text").get()
执行结果:
-
获取“标签”:所在结构是
div.quote > div.tags > a.tag
:quote.css("div.tags>a.tag::text").getall()
执行结果:
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))
执行结果:
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 Lines
和 JSON
。
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 有以下的特点:
- 使用 UTF-8 编码;
- 每行都是一个合法的 JSON 值(JSON 序列化的值,只是不包含方括号);
- 行分隔符是’\n’,但是 ‘\r\n’ 也会被接受;
然而处理
JSON Lines
文件的每一行时,忽略掉行分隔符,就好像没有分隔符一般,这给了用户极大的方便; -
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
。
导出蜘蛛 quotes
到 quotes.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)
代码解释:
start_urls
是一个 URL 列表,列表里面的 URL 会自动放进蜘蛛请求队列中,并且会自动地下载相关的 HTML 代码,然后将响应对象的实例交给parse()
方法,这是蜘蛛中默认的响应处理方法;yield
在 Python 中表示脚本代码解释器执行到这一步才让返回它所代表的数据,其他时候时时处于待命状态,这异于return
关键词;response.urljoin()
方法合并响应 URL 与相对 URL(这里是以 http 开头的首页 URL 与分页的相对 URL),从而组成一个绝对 URL;若下一页的链接不是个相对 URL,而是个拥有绝对路径的完整 URL,那么这里就不用该方法,这时可直接把
.get()
方法提取到的 URL 作为第一个参数传递进scrapy.Request()
方法中;-
yield scrapy.Request(next_page, callback=self.parse)
该行代码将对每一个由下一页锚链接提取的 URL 进行爬取。
运行爬虫:
scrapy crawl quotes -o quotes.jsonl
执行上述命令将会爬取所有页面,从而提取所有页面中想要的信息。
执行结果:
导出的文件名为 quotes.jsonl
,它的文件内容中每一行分别为一个值,值是个序列化的字典,里面包含每次提取的数据,然后一行行地写入文件。
quotes.jsonl
文件头:
文件尾:
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)
这里得到与上一个章节同样的结果。
- 与
scrapy.Request()
不同,response.follow()
方法直接支持相对 URL,不再需要调用 urljoin; -
response.follow()
同样返回一个请求实例,用户必须yield
这个请求实例; -
可直接传递
href
的选择器给response.follow()
方法的第一个参数,而非字符串 URL:for href in response.css("ul.pager a::attr(href)"): yield response.follow(href, callback=self.parse)
- 可直接用
a
元素的选择器:for a in response.css("ul.pager a"): yield response.follow(a, callback=self.parse)
7.4. 以 CSS 选择器对象列表的方式批量传递进 .follow_all() 方法
- 还可用
response.follow_all()
从可迭代对象同时创建多个请求:anchors = response.css("ul.pager a") yield from response.follow_all(anchors, callback=self.parse)
- 最短代码做同样的事:
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"),
}
上面的代码中:
- 从主页开始,它会跟踪所有指向作者详情页面的锚链接并为每个页面调用
parse_author()
回调方法; - 在
parse()
方法中对页面的请求回调parse_author()
方法,然后在该方法中提取数据,这些数据可以由管道(pipeline)交给 Items 处理;
关于“管道”和“Items” 请参阅中文文档获取更多的信息: - 用
yield
来持续地为每次响应返回该返回的字典数据。 - 该方法中还有个辅助函数,该函数中的
.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
在上述运行的命令中:
-O
选项指定从网页提取出来的信息的输出文件,这里选用JSON Lines
格式,如果文件扩展名为.json
,则使用 JSON 格式,这由 Scrapy 自动判断。-
-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 实现高效存储,并通过请求 / 响应对象构建灵活的爬虫逻辑,可显著提升信息采集的效率与稳定性。
题外话
- 本文把 Scrapy 的官方文档中的入门教程以容易理解同时使用更多注解的方式编写出来,篇章比较长,但是可贵在于实用。
- 这是个优秀的爬虫框架,使用它来爬取网站或者 API 的内容,可以省下许多不必要的代码,从而做到 Python 哲学中的“Write less code, do more”信条。
- 本文所接触的仅是该爬虫框架能做的其中一点点,要想学习更多,还须继续耕耘和努力。
- 所有的一切都在官方文档中,要是你想更好地使用 Scrapy 来做爬虫,写最少的代码,做最多的事,那么有空就多看看文档,边用边学更好。
- 本文的先修课:Python Scrapy 爬虫框架入门教程。
最后,非常感谢你看到文章最后笔者的啰嗦,祝用好!
(本文完)
鉴于本人的相关知识储备以及能力有限,本博客的观点和描述如有错漏或是有考虑不周到的地方还请多多包涵,欢迎互相探讨,一起学习,共同进步。
本文章可以转载,但是需要说明来源出处!
本文使用的部分图片来源于网上,若是侵权,请与本文作者联系删除: admin@icxzl.com