前面介绍爬虫分类的时候,我们就对各个网络爬虫工具的优缺点进行了分析。Requests库适合进行轻量化、数据量较小、对速度不敏感的网页爬取;而要进行数据量较大、对网页爬取速度较为敏感的网站爬取,就需要使用Scrapy框架。Scrapy为什么是一个框架而不是库?如何使用这样一个性能更强但又较Requests库复杂的工具进行网站爬取?请看本文讲解。本文涵盖了
Scrapy框架开发的几乎所有基础知识以及相关联知识,建议收藏。

Scrapy框架简介
Scrapy框架是一个用于爬取网站内容并进行数据提取的应用程序框架,在数据挖掘、信息处理与网站历史存档中有非常广泛的应用。之所以被称为“框架”,是由于其具有实现爬虫功能的软件结构和一系列功能组件集合。类似于工业产品中的半成品,可以根据开发者定制不同功能特性的专业网络爬虫。
Scrapy框架结构
Scrapy框架具有“5+2”结构,包含五个功能模块和两个中间件。分别为Engine(引擎)、Spiders(爬虫)、Downloader(下载器)、Schedular(调度器)和Item Pipelines(管道),两个中间件Downloader Middleware 为模块间提供数据流服务。其中Engine Downloader Scheduler已经实现,另外两个模块Item Piplines Spiders需要开发者自行编写配置。

Engine
- 控制所有模块之间的数据流
- 根据条件触发事件
- 不接触Internet环境,仅作为传递、控制数据流使用
- 不需要用户另行编写
Downloader
- 根据来自Engine的
Requests请求数据提交请求并获取响应内容传回Engine - 整个框架中唯一与Internet直接接触的部分
- 不需要用户另行编写
Scheduler
- 对所有来自Spider的爬取请求进行调度管理
- 由于数据规模较大,它将决定各类
Requests的优先级 - 功能相对固定,所以不需要用户另行编写
- 这个模块提供了整个框架的高并发性特性
Spider
- 解析Downloader返回的Response(响应)
- 产生Scraped Item(爬取项)
- 产生额外的爬取请求
- 是整个框架中的核心内容
Item Pipelines
- 以流水线方式处理Spider产生的爬取项
- 由一组操作顺序组成,类似于流水线,每一个操作都是一个
Item Pipline类型 - 操作包含:清理、检验和查重爬取项中的HTML数据、存储到数据库
Downloader Middleware
- 目的:实施
Engine、Scheduler、Downloader之间进行用户可配置的控制 - 功能:修改、丢弃、新增请求和响应
Spider Middleware
- 目的:对请求和爬取项的再处理
- 功能:修改、丢弃、新增请求或爬取项
Scrapy常用命令
Scrapy是一个完整的框架,所以也为我们提供了命令行来进行控制,通过在控制台使用
scrapy -h命令查看scrapy支持的命令。
scrapy [options] <command>
| 命令 | 说明 | 语法 |
|---|---|---|
| startproject | 创建一个新工程 | scrapy startproject [dir] |
| genspider | 创建一个爬虫 | scrapy genspider [options] |
| settings | 获取配置信息 | scrapy settings [options] |
| crawl | 运行一个爬虫 | scrapy crawl |
| list | 列出工程中所有爬虫 | scrapy list |
| shell | 启动URL调试命令行 | scrapy shell [URL] |
在命令的必要参数前可增加选项,下面介绍部分全局选项,各命令的特殊选项输入命令后加上-h选项可以获得
| 选项 | 说明 |
|---|---|
| –logfile=FILE | 将日志记录写入指定文件中 |
| –loglevel=LEVEL | 指定日志输出等级,默认为DEBUG |
| –nolog | 关闭日志输出 |
| –profile=FILE | 将python cProfile性能状态输出到文件中 |
| –pidfile=FILE | 将进程ID写入到文件中 |
| –set=NAME=VALUE | 重写配置 |
| –pdb | 在失败后启动pdb调试 |
fetch
使用downloader模块下载指定的URL并输出在终端中,如果不是文本编码也会强制解码。
scrapy fetch https://www.demo.com/
startproject
在终端中使用如下命令在当前目录下生成一个名为demo的工程
scrapy startproject demo
Scrapy框架生成一个工程时,会创建一个工程目录,并在其中放置已经准备好的框架文件,开发时仅需要对这些框架文件进行编辑即可。

- 首先,Scrapy会创建一个外层目录用于展开整个工程。在外层目录中有一个
scrapy.cfg配置文件,能在部署Scrapy爬虫框架时为部署服务器提供配置信息;另一个是工程同名资源目录,包含整个框架的所有代码。 - 在资源目录中,存在
__init__.py初始化脚本(无需编辑)、items.pyItems代码模板(继承类,一般不需要编辑)、middlewares.pyMiddleware代码模板(继承类)、pipelines.pyPipelines代码模板(继承类)、settings.pyScrapy爬虫的配置文件、spiders/Spiders代码模板目录,里面存放工程建立的爬虫
genspider
使用scrapy的模板生成一个爬虫框架模板。如果当前在工程主目录中,则爬虫将会在资源目录的spiders/目录中,否则将在当前目录中。
scrapy genspider <spider_name> <domain>
必须指定domain参数,它将指定爬虫在特定服务器中爬取,这个允许域名将被写入生成的spider框架中(可修改)
生成的框架如下(以名为demo1的spider为例)
import scrapy
class Demo1Spider(scrapy.Spider):
name = 'demo1'
allowed_domains = ['www.demo.com']
start_urls = ['http://www.demo.com']
def parse(self, response):
pass
- 生成的爬虫类名称为
<Spider_name>Spider name属性:爬虫名称allowed_domains属性:为一个列表,设置爬虫允许访问的域名称(放置外站跳转),初始为在命令行中设置的domain参数start_urls属性:为一个列表,设置爬虫开始访问的URL,将以generator object类型迭代,下面有其等价代码。该属性初始为http://<domain>/parse方法:对所有爬取得到的响应进行分析的方法,一开始将会返回爬取start_urls中网页的响应,能被自身递归调用(用于得到链接继续爬取)
其中,start_urlsl属性可以用一段等价代码代替
def start_requests(self):
urls = [
'http://www.demo.com/'
]
for url in urls:
yield scrapy.Request(url=url, callback=self.parse)
关于yield生成器,将在后面的补充知识中讲解
list
列出所有当前可用的爬虫。(一个爬虫符合框架的格式要求(并在spiders目录中),就能够成为可用的爬虫)
scrapy list
crawl
运行一个爬虫
scrapy crawl <spider_name>
runspider
运行一个爬虫。
scrapy runspider <spider_name>
与crawl命令不同的是,crawl仅能运行在一个工程中的爬虫(工作目录在工程的外层目录下),可以对另外的模块(middlewares pipelines)进行配置;而runspider可以运行一个独立的(自包含)爬虫,不需要在工程中,这个爬虫将使用其他模块的默认配置
shell
将启动一个交互式命令行界面。
scrapy shell
view
将使用scrapy框架爬取一个指定的URL,并将结果使用浏览器打开(CSS等内容由浏览器解析下载)
scrapy view <URL>
补充知识
yield生成器
在一般情况下,我们都需要一个函数具有良好的可复用性(一个函数只做一步工作,返回值,而不是执行特定的操作),能够方便不同的函数进行调用。当一个函数产生的值需要进行迭代时,如果需要迭代的数集合非常大,则会出现诸多问题,而生成器很好地解决了这个问题。
不同迭代方式及其特性
-
普通迭代:直接在函数运行中即时输出结果
def sqr(n): for i in range(n): print(i**2) sqr(5)这样就能够输出想要的结果。但是,正如本段前言中所说,这段代码仅能完成生成并输出的一套操作,代码可复用性较差。如果想要这个函数的输出能够被下一步处理,则需要用到别的方式。
-
生成列表:由于列表是一个可迭代类型,生成一个列表可以解决这个问题
def sqr1(n): l = [] for i in range(n): l.append(i**2) return l for i in sqr(5): print(i)生成一个列表并输出的操作较易理解,但在
n非常大时,生成的列表数据量巨大,将占用很大一部分空间;并且,在完成整个列表的生成工作前,我们并不能输出一个结果,这将导致程序有一段较长的无响应时间,影响运行效率。
在了解普通方法及其存在的缺点后,我们一起来了解一下生成器的原理及其对应优势
生成器原理及其优势
生成器generator object,就是一个不断产生值的函数,大多包含yield语句,但它不会一次执行完成并返回值,而是具有可迭代性质。例如,在for循环中,如果循环集合是一个生成器类型,则它不会将所有数据一并算出,**而是执行语句,直到遇到一个yield语句,这个语句将返回一个值,作为循环变量的一个值,而后生成器函数冻结,开始运行循环体语句,直到再次运行到循环头时,这个函数被唤醒,所有变量将与它被冻结时的状态保持一致,函数将再次运行到yiled语句并再次输出冻结……**直到函数运行结束再无遇到yield语句,则会抛出一个错误StopIteration(停止迭代),可以设置一个迭代结束的输出并加以处理以避免遇到这类错误。除了在可迭代变量的位置触发迭代之外,generator object提供了一个函数.__next__()直接进入这个生成器的下一迭代并返回值下面的两个例子展示了两种产生一个生成器的方法,后附运行顺序说明。
-
生成器(1):使用
yield语句产生一个生成器def sqr3(n): for i in range(n): yield i ** 2 for i in sqr3(5): print(i)当执行到主程序的
for循环语句时,sqr3函数将被启动并传入参数,函数内循环第一次中,返回一个值0,生成器冻结,主函数输出这个值0,在主程序运行到第二个循环头时,生成器再次被唤醒,变量i为原值0,进入生成器中的下一循环,i变为1,再次输出1并冻结……直到完成所有迭代与输出,程序结束。 -
生成器(2):使用嵌入式循环语句产生一个生成器
def sqr2(n): return (i**2 for i in range(n)) for i in sqr2(5): print(i)这个样例与上一个的工作流程相同,只不过,
sqr2返回值本身是一个generator object生成器,在一行中包含了循环体和循环头, 较为精简,但与上面的程序功能完全相同。所以,当循环体较为简单(一个操作)时,可以使用样例2的方法,否则需要使用第一种方法。
Scrapy使用方法
创建一个Scrapy爬虫的步骤
- 创建一个工程和
Spider模板 - 编写
Spider,配置其具体功能 - 编写
Item Pipeline,对Spider获取内容进行进一步处理 - 优化配置策略
Scrapy爬虫框架中的数据类型
在配置并使用Scrapy框架的过程中,我们会接触到三种数据类型:
- Request类,代表向
Internet提交的请求 - Response类,代表网络上的服务器返回的响应
- Item类,代表由
Spider产生的信息
Request类
class scrapy.http.Request()
Request对象标识一个HTTP请求- 其由
Spider生成,由Downloader执行 Request类的属性或方法
| 属性/方法 | 说明 |
|---|---|
.url | Request对应的请求URL地址 |
.method | 与HTTP对应的请求方法 |
.headers | HTTP请求头部信息,以字典形式组织 |
.body | 请求内容主体,为字符串类型 |
.meta | 添加的扩展信息,在Scrapy内部模块间传递信息用 |
.copy() | 复制该请求 |
Response类
class scrapy.http.Response()
Response对象标识一个HTTP响应- 其由
Downloader生成,由Spider处理
| 属性/方法 | 说明 |
|---|---|
.url | Response响应对应的URL地址 |
.status | HTTP响应状态码 |
.headers | Response对应的头部信息 |
.body | Response对应的内容信息(str) |
.flags | 一组标记 |
.request | 产生Response类型对应的Request对象 |
.copy() | 复制该响应 |
Item类
class scrapy.item.Item()
Item对象标识一个从HTML页面中提取的信息内容- 由
Spider生成,由Item Pipeline处理 Item类似字典类型,可以按照字典类型的方法进行操作
Scrapy框架下信息提取方法
Scrapy爬虫支持多种HTML信息提取方法,这些方法主要放置在Spider模块下
- Beautiful Soup (引用
BeautifulSoup库,并将Response.body作为分析材料进行文档分析) - lxml
- re
- XPath Selector
- CSS Selector
在Python 网络爬虫从0到1 (4):Beautiful Soup 4库入门详解中,我们已经介绍过Beautiful Soup库的基本使用方法。本文我们将主要介绍另一个常用标准化信息提取工具,CSS Selector
CSS Selector基本使用
CSS Selector,即CSS选择器,是一种HTML文档分析和信息提取工具。由W3C组织维护并规范,是标准化较好的文档分析工具,在Scrapy框架下应用较为广泛。
在一个Resposne响应对象或是Selector选择器对象(scrapy.selector.unified.Selector)中,可以使用.css()方法,返回对象是一个Select选择器对象,如果有多个符合条件的,则将返回一个列表,这也意味着,一个Selector对象是可迭代的,可使用for循环遍历每一个匹配内容。如果不指定编号,则默认返回第一个。
注意:必须使用.get()方法才能获取到该选择器的对应内容(str)
以下是几个示例:
# 1
next_page = response.css('li.next a::attr("href")').get()
# 2
for quote in response.css('div.quote'):
yield {
'text': quote.css('span.text::text').get()
}
CSS Selector 基本语法
基本选择器
| 选择器 | 语法 | 说明 | 样例 |
|---|---|---|---|
| 通用选择器 | * ns|* |* | | 选择所有元素,|表示命名空间,见1 | *匹配所有,a|*匹配命名空间a下的所有元素 |
| 元素选择器 | element_name | 匹配指定节点名称的元素 | a匹配所有超链接元素 |
| 类选择器 | .class_name | 匹配指定class类属性的元素,见2 | span.text匹配所有类型为text的span元素 |
| ID 选择器 | #id_name | 匹配一个指定ID属性的元素,见3 | #toc匹配ID为toc的元素 |
| 属性选择器 | [attr] [attr=value] … | 匹配属性与匹配符符合的元素,见4 | [href="/a"]匹配href属性为/a的所有元素 |
-
命名空间和通用选择器符号用
*分隔,命名空间在左侧。特殊样例,|*表示匹配不在任何命名空间中的元素,*|*表示匹配在命名空间(无论是哪个)中的元素 -
类选择器前依然可以使用元素选择器,见上样例。若不加元素选择器,则会匹配任何该类元素而不考虑名称
-
由于在一个文档中,
ID是唯一的,所以ID选择器最多匹配一个元素 -
匹配符并不只有一个,而是有一组,提供不同功能选项
匹配符 说明 样例 [attr] 带有某属性的元素 [href]表示带有href属性的元素[attr=value] 属性等于某值的元素 [href="/a"]表示href属性为/a的元素[attr~=value] 该属性为一个列表,且一值匹配 [a~="name"]表示a属性为列表,且有name的元素[attr|=value] 表示属性值以 value或value-开头的元素[class|="a"]表示class属性以a或a-开头的元素,如a-name[attr&=value] 表示属性值以 value结尾的元素[href&="/s"]表示href属性以/s结尾的元素[attr*=value] 表示多值属性值含 value的元素[class*="name"]表示class属性含有name的元素- 与
~=不同的是,*=为多值属性,~=表示属性值为列表 - 属性选择器同样可以与名称选择器,类选择器等一起使用,如
span[itemprop="plain"].text等
- 与
分组选择器
分组选择器只有一个,,可以将两个选择器组合在一起,以或方式组合,即元素任意匹配一个选择器,就能被选中。
组合器
| 名称 | 形式 | 说明 | 样例 |
|---|---|---|---|
| 后代组合器(Descendant combinator) | A B | 选择前一个节点A的所有后代节点B | div span匹配所有div元素下的span元素 |
| 直接子代组合器(Child combinator) | A > B | 选择前一个节点A的直接子代节点B | div > span匹配div元素子代的span元素 |
| 一般兄弟组合器(General sibling combinator) | A ~ B | 选择A节点后的所有兄弟节点B | p ~ a匹配p元素后面的a兄弟元素 |
| 紧邻兄弟组合器(Adjacent sibling combinator) | A + B | 选择紧邻A节点后的兄弟节点B | p + a匹配紧邻p后的a兄弟元素 |
伪选择器
伪类
CSS中定义了伪类,用于支持按照未被包含在文档中的状态信息来选择元素,指定了要选择的元素的特殊状态。如,a:link表示所有未访问过的超链接。由于伪类大多描述元素交互状态,而对于爬虫来说影响较小,这里就不再赘述,有兴趣的朋友可查看伪类 - CSS层叠样式表 | MDN获得更多详细信息
伪元素
伪元素用于选择匹配元素的特定部分,置于选择器最后
| 伪元素 | 说明 | 样例 |
|---|---|---|
| ::text | 选中元素的文本信息(两尖括号包含内容) | span.text::text表示获取class属性为text的span元素的文本 |
| ::attr(“attribute”) | 选中元素的attribute属性值 | li.next a::attr("href")获取类型为next的li元素的a后代的href属性值 |
| ::after | 表示已选中元素中的最后一个 | a::after最后一个a元素 |
| ::befor | 表示已选中元素中的第一个 | a::before第一个a元素 |
| ::first-letter | 表示选中块级元素中的第一个字符 | p::first-letter表示p元素内容中的第一个字符 |
| ::first-line | 表示选中块级元素中的第一行 | p::first-line表示p元素内容中的第一行 |
| ::selection | 表示文档中被用户高亮的部分 | ::selection表示文档中被选中的部分 |
Pipeline流水线处理模块
Pipelines流水线处理模块由多个Pipeline类组成。一个Pipeline处理模块包含三个方法/函数,
| 方法/函数名称 | 是否必选 | 说明 |
|---|---|---|
| open_spider(self, spider) | 否 | 在爬虫被启动时调用的方法,一般作为流水线处理模块的初始化方法(打开文件等) |
| process_item(self, item, spider) | 是 | 处理item主要方法,接受来自spider 的 item并处理 |
| close_spider(self, spider) | 否 | 在爬虫结束时调用的方法,一般作为流水线处理模块的结束方法(关闭文件等) |
其中,process_item函数一般将得到的item返回,方便其他流水线处理模块处理
流水线处理模块需要在settings.py工程配置文件中注册并设置优先级,为字典格式,格式样例
ITEM_PIPELINES = {
'demo.pipelines.DemoPipeline': 300,
'demo.pipelines.QuotePipeline': 100
}
模块类名称<project_name>.pipelines.<Pipeline_name>,后为优先级,越小越优先。
样例
下面用一个样例复习主要知识点并了解其实际应用,这个样例向http://quotes.toscrape.com/网站爬取某类名句以及作者,并保存在文件Quotes.json中
-
创建一个工程
-
创建一个爬虫
Quote,domain和start_urls配置见下 -
修改爬虫框架中的代码
import scrapy class QuotesSpider(scrapy.Spider): name = 'Quotes' allowed_domains = ['quotes.toscrape.com'] start_urls = ['http://quotes.toscrape.com/tag/inspirational/'] def parse(self, response, **kwargs): # find the quotes and return the text and author for quote in response.css('div.quote'): yield { # 'author': quote.xpath('span/small/text()').get(), 'author': quote.css('span small::text').get(), 'text': quote.css('span.text::text').get() } # find next page next_page = response.css('li.next a::attr("href")').get() if next_page is not None: yield response.follow(next_page, self.parse)下面主要讲解响应分析函数
parse(self, response)中的原理、流程-
寻找页面中所有名句,并将其中文本与作者输出
首先分析网页,所有的名句都在
class属性为quote的div域元素下,则先选择所有该类元素。上文已经讲过,selection类是可迭代类型,所以使用for循环对其中名句一一分析返回。具体解析文本见CSS Selector基本语法中的内容(包含伪元素)。 -
找到页面中的下一页链接并跟进爬取
分析网页,发现下一页链接在
class属性为next的li元素下的a元素中的href属性中,将其提取出,验证正确性后跟进爬取。
-
-
编辑
pipelines.py文件,为工程添加一个流水器处理模块,将内容保存在文件中
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
# useful for handling different item types with a single interface
# from itemadapter import ItemAdapter
import json
import os
class DemoPipeline:
def process_item(self, item, spider):
return item
class QuotePipeline:
def open_spider(self, spider):
self.f = open('Quotes.json', 'w')
self.f.write('[\n')
def process_item(self, item, spider):
try:
json.dump(item, self.f)
self.f.write(',\n')
except:
print('error')
return item
def close_spider(self, spider):
self.f.close()
self.f = open('Quotes.json', 'rb+')
self.f.seek(-3, os.SEEK_END)
# self.f.seek(-3, 2)
self.f.truncate()
self.f.write(b'\n]')
self.f.close()
添加的流水线处理模块名称为QuotePipeline
open_spider,close_spider的内容主要是文件IO以及开头与结尾的填充,process_item使用json.dump填充json格式数据,收尾时为了去掉原文件的错误字符,需要使用文件截断,去掉,\n共三个字节,可以不引用os库,而将os.SEEK_END常量直接替换为2
-
由于在配置
pipeline中使用了自定义类,需要在ITEM_PIPELINES选项中进行设置打开
settings.py,找到ITEM_PIPELINES选项,进行编辑ITEM_PIPELINES = { 'demo.pipelines.DemoPipeline': 300, 'demo.pipelines.QuotePipeline': 100 }注册改流水线处理模块并设置优先级后,
settings.py配置就此完成 -
至此,配置全部完成,在终端中输入爬取命令,爬虫启动,文件保存在工程外层目录中
scrapy crawl Quotes
64万+

被折叠的 条评论
为什么被折叠?



