Python爬虫03:Scrapy库


本文参考 Scrapy官方文档写成,详细内容参见文档.

Scrapy库的示例程序

Scrapy爬虫示例1: 使用爬虫发送请求

创建并运行一个爬虫项目

  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
    
  2. 编写爬虫: 在命令行输入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对象发起请求.
  3. 运行爬虫: 在命令行中输入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: 使用爬虫解析响应

解析响应提取数据

  1. 解析响应数据: 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']
    
  2. 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项目的命令

  1. 创建项目的命令:

    scrapy startproject <项目名>
    
  2. 创建爬虫的命令:

    scrapy genspider <爬虫名> <爬取网站的域名>
    
  3. 启动爬虫的命令:

    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对象发送请求,并更新当前作用域内的对象(包括requestresponse对象)
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对象.

  1. 创建实体类

    product = Product(name='Desktop PC', price=1000)
    print(product)
    # 得到 Product(name='Desktop PC', price=1000)
    
  2. 获取字段值

    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
    
  3. 向字段赋值

    # 向已定义字段赋值
    product['last_updated'] = 'today'
    
    # 向未定义字段赋值
    product['lala'] = 'test' # 报错: KeyError: 'Product does not support field: lala'
    
  4. 遍历其属性

    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个例子演示如何创建管道:

  1. 第一个管道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)
    
    
  2. 第二个管道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
    
    
  3. 第三个管道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的domainpath属性.

    # 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): 响应的URL
  • status(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响应

HtmlResponseTextResponse的子类,支持解析<meta>标签的http-equiv属性.

XmlResponse: 封装XML响应

XmlResponseTextResponse的子类,支持解析XML首行的声明.

Scrapy中间件

Scrapy库的结构和数据流

Scrapy各组件关系及数据流如下图所示:

img

Scrapy数据流是被执行引擎所控制的,一个完整的数据流如下:

  1. 引擎从爬虫中得到了初始的Requset对象.
  2. 引擎将Request对象传给调度器调度,并向调度器请求要被处理的Request对象.
  3. 调度器将要被处理的Request对象返回给引擎.
  4. 引擎将Request对象传递给下载器.其间经过下载器中间件(Downloader Middlewares).
  5. 一旦页面下载完成,下载器生成一个Response对象并将其返回给引擎.其间经过下载器中间件(Downloader Middlewares).
  6. 引擎接收到下载器返回的Response对象后,将其传递给爬虫对象来处理.其间经过爬虫中间件(Spider Middleware).
  7. 爬虫处理Response对象并将爬取到的数据或新的Request对象返回给引擎.其间经过爬虫中间件(Spider Middleware).
  8. 若爬虫返回的是爬取到的数据,则将其送入数据管道;若爬虫返回的是新的Request对象,则将其传递给调度器并请求下一个要处理的Request对象.
  9. 从第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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值