Python爬虫03:Scrapy库
本文参考 Scrapy官方文档写成,详细内容参见文档.
Scrapy库的示例程序
Scrapy爬虫示例1: 使用爬虫发送请求
创建并运行一个爬虫项目
-
创建Scrapy项目: 在命令行中输入
scrapy startproject tutorial
即可创建一个Scrapy项目,该项目名为tutorial
,生成的项目文件的目录结构如下:tutorial/ scrapy.cfg # Scrapy项目的配置文件 tutorial/ # 项目的Python代码 __init__.py items.py # 定义实体类的文件 middlewares.py # 定义中间件的文件 pipelines.py # 定义管道的文件 settings.py # 定义设置的文件 spiders/ # 存储爬虫的目录 __init__.py
-
编写爬虫: 在命令行输入
scrapy genspider quotes quotes.toscrape.com
,可以发现在tutorial/spiders
目录下有一个新建的爬虫文件quotes.py
,修改其内容如下:import scrapy class QuotesSpider(scrapy.Spider): # 定义爬虫名 name = "quotes" # 定义爬虫发出的第一个请求的方法 def start_requests(self): urls = [ 'http://quotes.toscrape.com/page/1/', 'http://quotes.toscrape.com/page/2/', ] for url in urls: yield scrapy.Request(url=url, callback=self.parse) # 定义处理响应的方法 def parse(self, response): page = response.url.split("/")[-2] filename = 'quotes-%s.html' % page with open(filename, 'wb') as f: f.write(response.body) self.log('Saved file %s' % filename)
我们的爬虫类
QuotesSpider
继承自爬虫基类Spider
,其各属性和方法的意义如下:name
属性: 表示爬虫名,一个项目中的爬虫名字不能重复.start_requests()
方法: 返回一个可迭代的Request
集合(一个列表或生成器),爬虫从该请求开始爬取内容.parse(self, response)
方法: 定义如何处理每个请求返回的数据,response
参数为一个TextResponse
对象,代表每次请求返回的响应值.该方法可以将爬取到的数据以字典或Item
对象形式返回,或者创建新的Request
对象发起请求.
-
运行爬虫: 在命令行中输入
scrapy crawl quotes
即可运行刚刚写好的爬虫,可以看到控制台输出日志且爬取到的数据被存储进文件中,正如我们在parse()
函数中定义的那样.
上述爬虫程序的执行过程: Scrapy框架调度start_requests()
方法返回的Request
对象,在得到响应时,实例化Response
对象并调用callback
参数指定的回调方法(在本例中为parse()
函数)并将该Response
对象作为参数传递给回调方法.
使用start_urls
属性替代start_requests()
方法指定起始请求
通过在start_urls
属性中定义一个URL列表,我们可以替代start_requests()
发起爬虫的起始请求.Scrapy框架会遍历start_urls
并发起请求,并以parse()
方法作为默认的回调函数.
import scrapy
class QuotesSpider(scrapy.Spider):
name = "quotes"
# 定义 start_urls 替代 start_requests()方法
start_urls = [
'http://quotes.toscrape.com/page/1/',
'http://quotes.toscrape.com/page/2/',
]
def parse(self, response):
page = response.url.split("/")[-2]
filename = 'quotes-%s.html' % page
with open(filename, 'wb') as f:
f.write(response.body)
Scrapy爬虫示例2: 使用爬虫解析响应
解析响应提取数据
-
解析响应数据:
parse(self, response)
方法的response
参数是一个Response
对象,可以调用该对象的css()
,xpath()
和re()
方法对响应数据进行CSS,XPath和正则解析.对
response
调用css()
或xpath()
方法解析得到的是一个Selector
对象列表.对调用其getall()
方法会以字符串列表形式返回所有内容,调用get(self, default=None)
方法可以以字符串形式返回其第一个元素的内容.Selector
对象的extract()
和extract_first()
方法是旧版本的方法,已经被弃用.response.css('title') # 得到 [<Selector xpath='descendant-or-self::title' data='<title>Quotes to Scrape</title>'>] response.css('title').getall() # 得到 ['<title>Quotes to Scrape</title>'] response.css('title::text') # 得到 [<Selector xpath='descendant-or-self::title/text()' data='Quotes to Scrape'>] response.css('title::text').get() # 得到 'Quotes to Scrape'
调用
Selector
对象的re()
方法也可以以字符串列表的形式返回所有匹配元素的内容.response.css('title::text').re(r'Quotes.*') # 得到 ['Quotes to Scrape'] response.css('title::text').re(r'Q\w+') # 得到 ['Quotes'] response.css('title::text').re(r'(\w+) to (\w+)') # 得到 ['Quotes', 'Scrape']
-
在
parse()
函数中返回一个字典对象即可以把爬取到的数据以字典形式返回.import scrapy class QuotesSpider(scrapy.Spider): name = "quotes" start_urls = [ 'http://quotes.toscrape.com/page/1/', 'http://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
启动爬虫,可以看到我们所爬的数据被打印在控制台上了.
将解析到的数据存储到文件中
在scrapy crawl
命令后加参数-o [输出文件名]
,即可将运行爬虫得到的数据存储到文件中.
scrapy crawl quotes -o quotes.json
运行上述命令后,可以在当前目录下看到quotes.json
文件,其中存储的是以json格式存储的爬取到的数据.
可以使用-t 数据格式
将爬取到的数据以其它格式存储,例如:
scrapy crawl quotes -t csv -o quotes.csv
Scrapy爬虫示例3: 使用爬虫实现自动翻页
要实现自动翻页的功能,就要解析下一页的URL.对于下面的页面:
<ul class="pager">
<li class="next">
<a href="/page/2/">Next <span aria-hidden="true">→</span></a>
</li>
</ul>
我们使用response.css('li.next a::attr(href)').get()
或response.css('li.next a').attrib['href']
都可以得到下一页的相对URL路径.
通过使parse()
方法返回Request
请求以跳转到下一页
parse()
方法不仅可以返回数据,也可以返回Request
对象,表示发起另一个请求,需要我们显式定义解析请求的回调函数.
import scrapy
class QuotesSpider(scrapy.Spider):
name = "quotes"
start_urls = [
'http://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(),
}
# 若存在下一页的功能,则请求下一页并调用parse()方法解析响应
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(url=next_page, callback=self.parse)
在这里,我们通过
urljoin()
方法将相对路径转为绝对路径,传递给Requset构造方法的是绝对URL路径.
使用follow()
方法跳转到下一页
使用follow()
方法可以简化上面创建Request
的操作,将下一页的地址作为url
参数传递给follow()
方法后,Scrapy框架会请求该URL并将该函数自身作为回调方法,这样就达到了跟随URL的效果.值得注意的是,follow()
方法既支持绝对URL路径,也支持相对URL路径.
import scrapy
class QuotesSpider(scrapy.Spider):
name = "quotes"
start_urls = [
'http://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:
# 使用follow()方法"跟随"下一页,该方法支持相对路径
yield response.follow(url=next_page, callback=self.parse)
Scrapy爬虫示例4: 一个完整的爬虫
下面爬虫是一个较完整的爬虫,该爬虫可以自动翻页,爬取整个网站所有的作者信息.
import scrapy
class AuthorSpider(scrapy.Spider):
name = 'author'
start_urls = ['http://quotes.toscrape.com/']
def parse(self, response):
# follow links to author pages
for href in response.css('.author + a::attr(href)'):
yield response.follow(href, self.parse_author)
# follow pagination links
for href in response.css('li.next a::attr(href)'):
yield response.follow(href, 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'),
}
Scrapy命令
创建和管理Scrapy项目的命令
-
创建项目的命令:
scrapy startproject <项目名>
-
创建爬虫的命令:
scrapy genspider <爬虫名> <爬取网站的域名>
-
启动爬虫的命令:
scrapy crawl <爬虫名>
Scrapy shell
因为我们是通过Scrapy命令而非运行python脚本的方式启动爬虫,debug比较困难.所以我们可以使用Scrapy控制台来动态的查看Scrapy的运行情况.
进入Scrapy shell的命令
进入Scrapy控制台的语法如下:
scrapy shell [URL]
其中URL
参数表示要请求的URL地址,也可以是本地文件的相对或绝对路径
# Web resources
scrapy shell 'http://quotes.toscrape.com/page/1/'
# UNIX-style
scrapy shell ./path/to/file.html
scrapy shell ../other/path/to/file.html
scrapy shell /absolute/path/to/file.html
# File URI
scrapy shell file:///absolute/path/to/file.html
Scrapy shell的内置方法和对象
进入Scrapy shell后,可以调用shelp()
方法查看所有的内置的方法和对象.
Scrapy shell的常用内置方法和对象如下:
方法或对象 | 作用 |
---|---|
request | 上一个请求对象 |
response | 上一个请求的响应对象 |
settings | Scrapy设置对象,即setiings.py 中的内容 |
spider | 能处理当前URL的Spider 对象若当前项目中没有能处理当前URL的 Spider 实现类,则将被设置为一个DefaultSpider 对象 |
fetch(url[, redirect=True]) 或fetch(req) | 利用该URL或Request 对象发送请求,并更新当前作用域内的对象(包括request 和response 对象) |
view(response) | 在浏览器中查看该response 对象 |
fetch("https://reddit.com")
response.xpath('//title/text()').get() # 得到 'reddit: the front page of the internet'
request = request.replace(method="POST")
fetch(request)
response.status # 得到 404
response.headers # 得到 {'Accept-Ranges': ['bytes'], 'Cache-Control': ['max-age=0, must-revalidate'], 'Content-Type': ['text/html; charset=UTF-8'], ...}
在爬虫程序中进入Scrapy shell
在爬虫程序中调用scrapy.shell.inspect_response()
方法,当爬虫程序执行到此行时,可以进入Scrapy shell.
例如下面程序
import scrapy
class MySpider(scrapy.Spider):
name = "myspider"
start_urls = [ "http://example.com", "http://example.org", "http://example.net"]
def parse(self, response):
# 我们要在请求"http://example.org"后进入Scrapy shell检查响应内容
if ".org" in response.url:
from scrapy.shell import inspect_response
inspect_response(response, self)
# 剩余代码
运行程序后会进入Scrapy shell
>>> response.url
'http://example.org'
对爬取数据进行处理
数据实体类
要想使用管道处理爬取到的数据,就要定义数据实体类(Item
)并在parse()
方法中将数据以该实体类对象的形式返回.
数据实体类的定义
数据实体类一般被定义在项目根目录的items.py
文件内.所有数据实体类必须继承自Item
对象,且其字段必须定义为Field
对象.
import scrapy
class Product(scrapy.Item):
name = scrapy.Field()
price = scrapy.Field()
stock = scrapy.Field()
tags = scrapy.Field()
last_updated = scrapy.Field(serializer=str)
值得注意的是,这些Field
对象将不会被赋值为类属性,但是我们可以通过Item.fields
属性访问它们.
数据实体类的使用
Item
类支持dict-API,可以像使用字典一样使用Item
对象.
-
创建实体类
product = Product(name='Desktop PC', price=1000) print(product) # 得到 Product(name='Desktop PC', price=1000)
-
获取字段值
product['name'] # 得到 'Desktop PC' product.get('name') # 得到 'Desktop PC' product['price'] # 得到 1000 # 读取未赋值的字段值 product['last_updated'] # 报错 KeyError: 'last_updated' product.get('last_updated', 'not set') # 得到 not set # 读取未定义的字段值 product['lala'] # 报错 KeyError: 'lala' product.get('lala', 'unknown field') # 得到'unknown field' 'name' in product # True 'last_updated' in product # False 'last_updated' in product.fields # True 'lala' in product.fields # False
-
向字段赋值
# 向已定义字段赋值 product['last_updated'] = 'today' # 向未定义字段赋值 product['lala'] = 'test' # 报错: KeyError: 'Product does not support field: lala'
-
遍历其属性
product.keys() # 得到 ['price', 'name'] product.items() # 得到 [('price', 1000), ('name', 'Desktop PC')]
管道(Item Pipeline)
管道(Item Pipeline)可以对爬取到的数据进行处理,其典型应用有:
- 清理HTML数据
- 验证数据(检查爬取到的数据是否包含某字段)
- 数据去重(并丢弃)
- 将爬取到的数据存储进数据库
创建管道
管道一般被定义在项目根目录下的pipelines.py
文件内.
管道是一个Python类,它必须定义process_item()
方法:
-
process_item(self, item, spider)
: 该方法定义了管道如何处理传入的数据,会对每个传入本节管道的元素都调用一次.它的参数意义如下:item
: 表示从上一节管道传入的数据,必须为一个Item
对象或字典.spider
: 表示爬取到该数据的爬虫,必须为一个Spider
.
若管道不丢弃该
item
数据,则必须返回一个Item
对象或字典,该返回值将会被传入下一节管道.若管道丢弃该item
数据,只需抛出DropItem
异常,被抛弃的元素不会进入下一节管道.
除此之外,管道还可以定义如下3个方法:
open_spider(self, spider)
: 该方法在爬虫开启时被调用,spider
参数代表被开启的爬虫.close_spider(self, spider)
: 该方法在爬虫关闭时被调用,spider
参数代表被关闭的爬虫.from_crawler(cls, crawler)
: 该方法若存在,则必须被定义为类方法.该方法被Crawler
调用以创建管道,它必须返回一个管道对象.
下面3个例子演示如何创建管道:
-
第一个管道
PricePipeline
演示了process_item()
方法的使用from scrapy.exceptions import DropItem class PricePipeline(object): vat_factor = 1.15 def process_item(self, item, spider): """ 定义管道处理实体类的逻辑: 若该数据存在price属性,则进行处理,否则丢弃该数据 """ if item.get('price'): if item.get('price_excludes_vat'): item['price'] = item['price'] * self.vat_factor return item else: raise DropItem("Missing price in %s" % item)
-
第二个管道
JsonWriterPipeline
演示了open_spider()
方法和close_spider()
方法的使用:import json class JsonWriterPipeline(object): def open_spider(self, spider): self.file = open('items.jl', 'w') def close_spider(self, spider): self.file.close() def process_item(self, item, spider): line = json.dumps(dict(item)) + "\n" self.file.write(line) return item
-
第三个管道
MongoPipeline
演示了from_crawler
方法的使用:import pymongo class MongoPipeline(object): collection_name = 'scrapy_items' def __init__(self, mongo_uri, mongo_db): self.mongo_uri = mongo_uri self.mongo_db = mongo_db @classmethod def from_crawler(cls, crawler): return cls( mongo_uri=crawler.settings.get('MONGO_URI'), mongo_db=crawler.settings.get('MONGO_DATABASE', 'items') ) def open_spider(self, spider): self.client = pymongo.MongoClient(self.mongo_uri) self.db = self.client[self.mongo_db] def close_spider(self, spider): self.client.close() def process_item(self, item, spider): self.db[self.collection_name].insert_one(dict(item)) return item
在配置文件中注册管道
管道被定义好后,必须被注册到根目录下的settings.py
文件中才能发挥作用,在settings.py
文件中配置ITEM_PIPELINES
变量以注册管道,这是一个字典对象,其键为管道类的全类名,值为其优先级(越小越优先)
# 将爬取到的数据先进行验证,再写入json文件和数据库中
ITEM_PIPELINES = {
'tutorial.pipelines.PricePipeline': 300,
'tutorial.pipelines.JsonWriterPipeline': 400,
'tutorial.pipelines.MongoPipeline': 500,
}
Request
类和Response
类
Request
对象和Response
对象封装了http请求与响应,我们有必要深入了解它们.
Request
类
Request
基类
Request
基类封装了一般的HTTP请求,它的构造函数如下:
scrapy.http.Request(url, callback=None, method='GET', headers=None, body=None, cookies=None, meta=None, encoding='utf-8', priority=0, dont_filter=False, errback=None, flags=None, cb_kwargs=None)
,各参数意义如下:
-
url
(string): 请求的URL. -
callback
(callable): 对响应调用的回调函数,接收对应的的Response
响应对象为其第一个参数. -
method
(string): HTTP请求的方法,默认为"GET"
. -
meta
(dict): 请求的元数据,供Scrapy组件(中间件和插件)修改,有一些Scrapy保留的元数据键,不应当被覆盖.保留的元数据键有:
dont_redirect
,dont_retry
,handle_httpstatus_list
,handle_httpstatus_all
,dont_merge_cookies
,cookiejar
,dont_cache
,redirect_reasons
,redirect_urls
,bindaddress
,dont_obey_robotstxt
,download_timeout
,download_maxsize
,download_latency
,download_fail_on_dataloss
,proxy
,ftp_user
,ftp_password
,referrer_policy
,max_retry_times
. -
body
(str或unicode): 请求体. -
headers
(dict): 请求头. -
cookies
(dict或list): cookies数据,可以为字典或字典列表,后者可以定制cookies的domain
和path
属性.# cookies属性为字典 request_with_cookies = Request(url="http://www.example.com", cookies={'currency': 'USD', 'country': 'UY'})
# cookies属性为字典列表 request_with_cookies = Request(url="http://www.example.com", cookies=[{'name': 'currency', 'value': 'USD', 'domain': 'example.com', 'path': '/currency'}])
-
encoding
(string): 编码(默认为"utf-8"
). -
priority
(int): 请求的优先级(默认为0
),数字越大越优先,允许负值. -
dont_filter
(boolean): 指定调度器是否过滤重复请求(默认为False
). -
errback
(callable): 处理请求中发生异常时调用的方法,接收对应的异常对象为其第一个参数. -
flags
(list): 请求的标记,可以用于日志. -
cb_kwargs
(dict): 传给回调函数的参数.
Request
子类
FormRequest
: 封装表单请求
scrapy.http.FormRequest
类封装了表单请求,其formdata
属性为一个dict,封装了所有的表单参数.
return [FormRequest(url="http://www.example.com/post/action",
formdata={'name': 'John Doe', 'age': '27'},
callback=self.after_post)]
JsonRequest
: 封装JSON请求
scrapy.http.JsonRequest
类封装了JSON请求,其构造函数接收两个参数:
data
(JSON序列化对象): 代表JSON请求.body
属性会覆盖该属性.dumps_kwargs
(dict): 传递给json.dumps
方法的用于序列化数据的参数
Response
类
Response
基类
Response
基类封装了一般的HTTP响应,它的构造函数如下:
classscrapy.http.Response(url, status=200, headers=None, body=b'', flags=None, request=None)
,各参数意义如下:
url
(string): 响应的URLstatus
(integer): 响应的HTTP状态码,默认为200
.headers
(dict): 响应的头信息.body
(bytes): 响应体flags
(list): 响应的标记(如'cached'
,'redirected
’),用于日志.request
(Request object): 对应的请求对象.
Response
子类
TextResponse
: 封装文字响应
scrapy.http.TextResponse
类封装了文字响应,有如下的属性和方法:
encoding
: 响应的编码,获取相应编码的机制有以下四种(优先级从高到低):- 构造函数传入的
encoding
属性 - HTTP响应头定义的
Content-Type
属性 - 响应体定义的编码属性
- 从响应体内容中推断的编码格式
- 构造函数传入的
text
: 以unicode形式返回响应体,等价于response.body.decode(response.encoding)
selector
: 返回针对当前响应体的Selector
对象.xpath(query)
: 等价于TextResponse.selector.xpath(query)
css(query)
: 等价于TextResponse.selector.css(query)
follow(url, callback=None, method='GET', headers=None, body=None, cookies=None, meta=None, encoding=None, priority=0, dont_filter=False, errback=None, cb_kwargs=None)
: 向调度器注册一个新的Request
请求对象.url属性可以是:- 绝对URL
- 相对URL
scrapy.link.Link
对象Selector
对象,如:response.css('a::attr(href)')[0]
,response.css('a')[0]
HtmlResponse
: 封装HTML响应
HtmlResponse
是TextResponse
的子类,支持解析<meta>
标签的http-equiv
属性.
XmlResponse
: 封装XML响应
XmlResponse
是TextResponse
的子类,支持解析XML首行的声明.
Scrapy中间件
Scrapy库的结构和数据流
Scrapy各组件关系及数据流如下图所示:
Scrapy数据流是被执行引擎所控制的,一个完整的数据流如下:
- 引擎从爬虫中得到了初始的
Requset
对象. - 引擎将
Request
对象传给调度器调度,并向调度器请求要被处理的Request
对象. - 调度器将要被处理的
Request
对象返回给引擎. - 引擎将
Request
对象传递给下载器.其间经过下载器中间件(Downloader Middlewares). - 一旦页面下载完成,下载器生成一个
Response
对象并将其返回给引擎.其间经过下载器中间件(Downloader Middlewares). - 引擎接收到下载器返回的
Response
对象后,将其传递给爬虫对象来处理.其间经过爬虫中间件(Spider Middleware). - 爬虫处理
Response
对象并将爬取到的数据或新的Request
对象返回给引擎.其间经过爬虫中间件(Spider Middleware). - 若爬虫返回的是爬取到的数据,则将其送入数据管道;若爬虫返回的是新的
Request
对象,则将其传递给调度器并请求下一个要处理的Request
对象. - 从第1步开始重复直到调度器中没有新的
Request
对象.
下载中间件(Downloader Middlewares)
下载中间件组成一个中间件调用链.当一个Request
对象经过中间件时,Scrapy会对其按正向顺序依次调用中间件的process_request()
方法;一个Response
对象经过中间件时,Scrapy会对其按逆向顺序依次调用中间件的process_request()
方法.
创建下载中间件
下载中间件是一个Python类,它定义了下面4个方法中的一个或多个:
-
process_request(request, spider)
:当有
Request
对象通过下载中间件时,该方法就会被调用.它的返回值可以是None
,或一个Response
对象,或一个Request
对象,或抛出一个IgnoreRequest
异常.其意义分别如下:- 返回
None
时,表示正常传递该Request
对象.Scrapy将会对该Request
对象调用下一个中间件的process_request()
方法或传递给下载器组件. - 返回
Response
对象时,表示终止传递该Request
对象且直接返回该Response
对象.Scrapy将不会对该Request
对象调用下一个中间件的process_request()
和process_exception()
方法,且该Request
对象不会传递给下载器组件.新的Response
对象会被返回且Scrapy会对其调用中间件的process_response()
方法. - 返回
Request
对象时,表示停止请求原有Request
并向调度器传入一个新的Request
请求.crapy将不会对该Request
对象调用下一个中间件的process_request()
和process_exception()
方法. - 抛出
IgnoreRequest
异常时,Scrapy框架将会忽略该Request
对象,并调用该中间件的process_exception()
方法.若该方法不存在,则调用原Request
对象的errback
属性指定的方法.若原Request
对象未指定errback
属性,则该异常将会被忽略(甚至不会被打印在日志里).
- 返回
-
process_response(request, response, spider)
:当有
Response
对象通过下载中间件时,该方法就会被调用.它的返回值可以是一个Response
对象,或一个Request
对象,或抛出一个IgnoreRequest
异常.其意义分别如下:- 返回
Response
对象(返回的可以是传入的Response
对象,也可以是一个全新的Response
对象)时,该Response
对象将会被传递给下一个中间件的process_response()
方法. - 返回
Request
对象时,表示停止请求原有Request
并向调度器传入一个新的Request
请求.Scrapy框架的行为与process_request()
方法返回Request
对象时完全相同. - 抛出
IgnoreRequest
异常时,Scrapy框架将会调用原Request
对象的errback
属性指定的方法.若原Request
对象未指定errback
属性,则该异常将会被忽略(甚至不会被打印在日志里).
- 返回
-
process_exception(request, exception, spider)
:当下载器出现异常或中间件的
process_request()
方法抛出异常时,该方法就会被调用.它的返回值可以是None
,或一个Response
对象,或一个Request
对象.其意义分别如下:- 返回
None
时,表示正常抛出该异常.Scrapy将会调用下一个中间件的process_exception()
方法. - 返回
Response
对象时,表示终止抛出该异常且直接返回该Response
对象.Scrapy将不会调用其他中间件的process_exception()
方法. - 返回
Request
对象时,表示停止抛出该异常并向调度器传入一个新的Request
请求.Scrapy同样将不会调用其他中间件的process_exception()
方法.
- 返回
-
from_crawler(cls, crawler)
:该方法若存在,则必须被定义为类方法.该方法被
Crawler
调用以创建中间件,它必须返回一个中间件对象.
下面定义的中间件的作用是为请求添加代理.
class CustomProxyMiddleware(object):
def __init__(self, settings):
self.proxies = settings.getlist('PROXIES')
@classmethod
def from_crawler(cls, crawler):
'''
创建中间件的逻辑: 根据HTTPPROXY_ENABLED配置项决定是否创建中间件
'''
if not crawler.settings.getbool('HTTPPROXY_ENABLED'):
raise NotConfigured
return cls(crawler.settings)
def process_request(self, request, spider):
'''
处理请求的逻辑: 若请求未指定代理,则为请求添加一个代理
'''
if not request.meta.get('proxy'):
request.meta['proxy'] = random.choice(self.proxies)
def process_response(self, request, response, spider):
'''
处理响应的逻辑: 根据响应内容判断代理是否有效
若无效则删除该代理并重新发起请求
若有效则直接返回该响应
'''
proxy = request.meta.get('proxy')
if response.status in (401, 403):
self.proxies.remove(proxy)
del request.meta['proxy']
return request
return response
def process_exception(self, request, exception, spider):
'''
处理异常的逻辑:
若接收到的是代理网络质量的异常,则删除该代理并重新发起请求
若接收到的是其它异常,则交由其它中间件或请求对象自身来处理
'''
proxy = request.meta.get('proxy')
if proxy and isinstance(exception, (ConnectionRefusedError, TimeoutError)):
self.proxies.remove(proxy)
del request.meta['proxy']
return request
上面代码只是为了演示中间件的定义而写,若要为请求指定代理池,请使用
HttpProxyMiddleware
中间件.
在配置文件中注册下载中间件
下载中间件被注册在项目根目录下settings.py
文件的DOWNLOADER_MIDDLEWARES
属性中.DOWNLOADER_MIDDLEWARES
属性将会与DOWNLOADER_MIDDLEWARES_BASE
属性合并,并按定义的权重顺序被调用.
DOWNLOADER_MIDDLEWARES = {
'myproject.middlewares.CustomProxyMiddleware': 543,
}
爬虫中间件(Spider Middleware)
爬虫中间件的创建与使用与下载中间件大同小异,可以参考官方文档.