Python爬虫03:Scrapy库

mac2025-06-05  12

Python爬虫03:Scrapy库

Scrapy库的示例程序Scrapy爬虫示例1: 使用爬虫发送请求创建并运行一个爬虫项目使用`start_urls`属性替代`start_requests()`方法指定起始请求 Scrapy爬虫示例2: 使用爬虫解析响应解析响应提取数据将解析到的数据存储到文件中 Scrapy爬虫示例3: 使用爬虫实现自动翻页通过使`parse()`方法返回`Request`请求以跳转到下一页使用`follow()`方法跳转到下一页 Scrapy爬虫示例4: 一个完整的爬虫 Scrapy命令创建和管理Scrapy项目的命令Scrapy shell进入Scrapy shell的命令Scrapy shell的内置方法和对象在爬虫程序中进入Scrapy shell 对爬取数据进行处理数据实体类数据实体类的定义数据实体类的使用 管道(Item Pipeline)创建管道在配置文件中注册管道 `Request`类和`Response`类`Request`类`Request`基类`Request`子类`FormRequest`: 封装表单请求`JsonRequest`: 封装JSON请求 `Response`类`Response`基类`Response`子类`TextResponse`: 封装文字响应`HtmlResponse`: 封装HTML响应`XmlResponse`: 封装XML响应 Scrapy中间件Scrapy库的结构和数据流下载中间件(Downloader Middlewares)创建下载中间件在配置文件中注册下载中间件 爬虫中间件(Spider Middleware) 本文参考 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">&rarr;</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上一个请求的响应对象settingsScrapy设置对象,即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相对URLscrapy.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)

爬虫中间件的创建与使用与下载中间件大同小异,可以参考官方文档.

最新回复(0)