爬虫之scrapy框架

一、scrapy框架介绍

1、介绍

Scrapy,Python开发的一个快速、高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据。Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试。

Scrapy吸引人的地方在于它是一个框架,任何人都可以根据需求方便的修改。它也提供了多种类型爬虫的基类,如BaseSpider、sitemap爬虫等,最新版本又提供了web2.0爬虫的支持。

Scrap,是碎片的意思,这个Python的爬虫框架叫Scrapy。

Scrapy 是基于twisted框架开发而来,twisted是一个流行的事件驱动的python网络框架。因此Scrapy使用了一种非阻塞(又名异步)的代码来实现并发。

2、scrapy架构图

1. 这是官方给出的架构图

2. 各个组件的作用

引擎(Engine)
引擎负责控制数据流在系统中所有组件中流动,并在相应动作发生时触发事件。


调度器(Scheduler)
用来接收引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 相当于一个URL的优先级队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址


下载器(Downloader)
用于下载网页内容, 并将网页内容返回给EGINE,下载器是建立在twisted这个高效的异步模型上的


下载器中间件(Downloader middlewares)
下载器中间件是在引擎及下载器之间的特定钩子(specific hook),处理Downloader传递给引擎的response(也包括引擎传递给下载器的Request)。 其提供了一个简便的机制,通过插入自定义代码来扩展Scrapy功能。


爬虫(Spiders)
Spider是Scrapy用户编写用于分析response并提取item(即获取到的item)或额外跟进的URL的类。 每个spider负责处理一个特定(或一些)网站


爬虫中间件(Spider middlewares)
Spider中间件是在引擎及Spider之间的特定钩子(specific hook),处理spider的输入(response)和输出(items及requests)。 其提供了一个简便的机制,通过插入自定义代码来扩展Scrapy功能。


项目管道(Item Pipeline)
Item Pipeline负责处理被spider提取出来的item。典型的处理有清理、 验证及持久化(例如存取到数据库中)。

3、scrapy框架的基本流程

0. 程序开始运行

1. Spiders用yeild将url发送给Engine(引擎)

2. Engine把url发送给调度器(Scheduler)

3. 调度器(Scheduler)会将url生成request返回给Engine(引擎)

4. Engine(引擎)拿到request,通过下载器中间件(Downloader middlewares)进行层层过滤发送给下载器(Downloader)

5. 下载器(Downloader)在网上获取到response数据之后,又经过下载器中间件(Downloader middlewares)进行层层过滤发送给Engine(引擎)

6. Engine(引擎)获取到response数据之后,返回给Spiders,Spiders的parse()方法对获取到的response数据进行处理,解析出items或者requests

7. 将解析出来的items或者requests发送给Engine(引擎)

8. Engine(引擎)获取到items或者requests,将items发送给Item Pipeline进行数据的存储

9. 注意,只有当调度器中不存在任何request了,整个程序才会停止,也就是说,对于下载失败的URL,Scrapy也会重新下载

4、scrapy框架中间件

# 下载器中间件Downloader Middleware
1. process_request(request, spider)
当每个request通过下载中间件时,该方法被调用,这里可以修改UA,代理,Refferer

2. process_response(request, response, spider)
下载结果经过中间件时被此方法处理

3. process_exception(request, exception, spider)
下载过程中出现异常时被调用,这里可以处理超时


# 爬虫中间件Spider Middleware
SpiderMiddleware主要处理解析Item的相关逻辑修正,比如数据不完整要添加默认,增加其他额外信息等
0. process_start_requests(start_requests, spider):
当spider发出请求时,被调用。
# Scheduler是scrapy官方结构图中的组件
运行流程:Spiders --> process_start_requests --> Scheduler

1. process_spider_input(response, spider)
"""参数
response(Response对象) - 被处理的response
spider(Spider对象) - 该response对应的spider
"""
当response通过spider中间件时,该方法被调用,处理该response。
# Downloader和Spiders是scrapy官方结构图中的组件,即上面那张图
运行流程:Downloader --> process_spider_input--> Spiders

2. process_spider_output(response, result, spider)
"""参数
response(Response对象) - 生成该输出的response
result(包含Reques或Item对象的可迭代对象(iterable)) - spider返回的result
spider(Spider对象) - 其结果被处理的spider
"""
当Spider处理response返回result时,该方法被调用。
运行流程:Spiders --> process_spider_input--> Item Pipelines

3. process_spider_exception(response, exception, spider)
当spider或(其他spider中间件的) process_spider_input()
"""参数
response(Response对象) - 异常被抛出时被处理的response
exception(Exception对象) - 被抛出的异常
spider(Spider对象) - 抛出异常的spider
"""
抛出异常时, 该方法被调用。

二、scrapy的基础

1、安装

1. Linux系统
    pip install scrapy  # 从官网下载
    pip install -i https://pypi.douban.com/simple/ scrapy  # 从豆瓣源下载


2. Windows系统
    1. pip install -i https://pypi.douban.com/simple/ wheel

    2. pip install twisted

    3. pip install -i https://pypi.douban.com/simple/ pywin32

    4. pip install -i https://pypi.douban.com/simple/ scrapy

2、cmd下的命令行工具

1. 查看帮助
    scrapy -h
    scrapy <command> -h


2. 有两种命令:其中Project-only必须切到项目文件夹下才能执行,而Global的命令则不需要
    Global commands:
        startproject  # 创建项目
        genspider     # 创建爬虫程序
        settings      # 如果是在项目目录下,则得到的是该项目的配置
        runspider     # 运行一个独立的python文件,不必创建项目
        shell         # scrapy shell url地址  在交互式调试,如选择器规则正确与否
        fetch         # 独立于程单纯地爬取一个页面,可以拿到请求头
        view          # 下载完毕后直接弹出浏览器,以此可以分辨出哪些数据是ajax请求
        version       # scrapy version 查看scrapy的版本,scrapy version -v查看scrapy依赖库的版本
    
    Project-only commands:
        crawl         # 运行爬虫,必须创建项目才行,确保配置文件中ROBOTSTXT_OBEY = False
        check         # 检测项目中有无语法错误
        list          # 列出项目中所包含的爬虫名
        edit          # 编辑器,一般不用
        parse         # scrapy parse url地址 --callback 回调函数  #以此可以验证我们的回调函数是否正确
        bench         # scrapy bentch压力测试

3. 官网链接
    https://docs.scrapy.org/en/latest/topics/commands.html

3、目录结构

project_name/
   scrapy.cfg
   project_name/
       __init__.py
       items.py
       middlewares.py
       pipelines.py
       settings.py
       spiders/
           __init__.py
           爬虫1.py
           爬虫2.py
           爬虫3.py

文件说明:
    scrapy.cfg     项目的主配置信息,用来部署scrapy时使用,爬虫相关的配置信息在settings.py文件中。
    items.py       设置数据存储模板,用于结构化数据,如:Django的Model
    middlewares.py 中间件是处于引擎(crawler.engine)和下载器(crawler.engine.download())之间的一层组件
    pipelines      数据处理行为,如:一般结构化的数据持久化
    settings.py    配置文件,如:递归的层数、并发数,延迟下载等。强调:配置文件的选项必须大写否则视为无效
    spiders        爬虫目录,如:创建文件,编写爬虫规则

注意:
1、一般创建爬虫文件时,以网站域名命名
2、默认只能在终端执行命令,为了更便捷操作:
# 在项目根目录下新建:entrypoint.py
from scrapy.cmdline import execute
execute(['scrapy', 'crawl', 'qsbk', '--nolog'])

如何在pycharm中运行scrapy程序

1、一般创建爬虫文件时,以网站域名命名
2、默认只能在终端执行命令,为了更便捷操作:
# 在项目根目录下新建:entrypoint.py
from scrapy.cmdline import execute
execute(['scrapy', 'crawl', 'qsbk', '--nolog'])

4、Spider主爬虫程序类

Spiders是定义如何抓取某个站点(或一组站点)的类,包括如何执行爬行(即跟随链接)以及如何从其页面中提取结构化数据(即抓取项目)。换句话说,Spiders是为特定站点(或者在某些情况下,一组站点)爬网和解析页面定义自定义行为的地方。 

1. 生成初始的Requests来爬取第一个URLS,并且标识一个回调函数
    第一个请求定义在start_requests()方法内默认从start_urls列表中获得url地址来生成Request请求,
    默认的回调函数是parse方法。回调函数在下载完成返回response时自动触发

2. 在回调函数中,解析response并且返回值
    返回值可以4种:
        包含解析数据的字典
        Item对象
        新的Request对象(新的Requests也需要指定一个回调函数)
        或者是可迭代对象(包含Items或Request)

3. 在回调函数中解析页面内容
   通常使用Scrapy自带的Selectors,但很明显你也可以使用Beutifulsoup,lxml或其他你爱用啥用啥。

4. 最后,针对返回的Items对象将会被持久化到数据库
   通过Item Pipeline组件存到数据库:https://docs.scrapy.org/en/latest/topics/item-pipeline.html#topics-item-pipeline)
   或者导出到不同的文件(通过Feed exports:https://docs.scrapy.org/en/latest/topics/feed-exports.html#topics-feed-exports)

5、Scrapy自带的选择器Selectors(xpath)

使用Scrapy自带的选择器,获取到的数据都是Selectors对象,
需要使用extract方法把Selectors对象的内容提取出来。

1,div.xpath('.//div[@class="author clearfix"]/a/h2/text()')
结果是Selector对象组成的列表:[<Selector xpath='xxx' data='xxx'>,]

2,div.xpath('.//div[@class="author clearfix"]/a/h2/text()').extract()
把Selector对象的内容提取出来,还是列表:['data的内容',]

3,div.xpath('.//div[@class="author clearfix"]/a/h2/text()').extract()[0]
把提取出来的列表按索引取出某一个值,若只提取一个,也可以使用extract_first()
div.xpath('.//div[@class="author clearfix"]/a/h2/text()').extract_first()

4,div.xpath('.//div[@class="author clearfix"]/a/h2/text()')[0].extract()
先在列表中取出某个Selector对象,在把Selector对象的内容提取出来

6、DupeFilter(去重)

1. 默认使用方式
DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter'
Request(...,dont_filter=False)  # 如果dont_filter=True则告诉Scrapy这个URL不参与去重。


2. 自定义去重规则
from scrapy.dupefilter import RFPDupeFilter,看源码,仿照BaseDupeFilter
 
# 步骤一:在项目目录下自定义去重文件dup.py
class UrlFilter(object):
    def __init__(self):
        self.visited = set()  # 或者放到数据库
 
    @classmethod
    def from_settings(cls, settings):
        return cls()
 
    def request_seen(self, request):
        if request.url in self.visited:
            return True
        self.visited.add(request.url)
 
    def open(self):  # can return deferred
        pass
 
    def close(self, reason):  # can return a deferred
        pass
 
    def log(self, request, spider):  # log that a request has been filtered
        pass

三、scrapy的小Demo

1、创建并执行爬虫应用程序

1. 在cmd下输入命令
cd 工作目录
scrapy  startproject  项目名  # 创建项目
cd 项目名
scrapy  genspider  应用名称  爬取的起始url # 创建爬虫程序
scrapy crawl 应用名称  # 该种执行形式会显示执行的日志信息
scrapy crawl 应用名称 --nolog  # 该种执行形式不会显示执行的日志信息
scrapy crawl 应用名称 -o xxx.xml # 存储数据文本格式


2. 示例
# 爬取糗事百科
cd E:SpiderProject爬虫scrapy框架  # 进入工作目录
scrapy startproject QSBK  # 创建一个名为QSBK的项目
cd QSBK  # 进入项目目录
scrapy genspider qsbk www.qiushibaike.com  # 创建爬虫程序(会在项目下的spiders目录下创建qsbk.py程序)
scrapy  crawl  qsbk  # 执行应用程序
scrapy  crawl  qsbk -o qsbk.xml  # 将爬取到的数据解析后存储成xml格式的文件


3.spiders目录下创建的qsbk.py程序
# -*- coding: utf-8 -*-
import scrapy


class QsbkSpider(scrapy.Spider):
    name = 'qsbk'  # 应用名称
    # 允许爬取的域名(如果遇到非该域名的url则爬取不到数据)
    allowed_domains = ['www.qiushibaike.com']
    # 起始爬取的url
    start_urls = ['http://www.qiushibaike.com/']
    
    # 访问起始URL并获取结果后的回调函数,该函数的response参数就是向起始的url发送请求后,获取的响应对象.
    # 该函数返回值必须为可迭代对象或者NUll
    def parse(self, response):
        print(response.text)  # 获取字符串类型的响应内容
        print(response.body)  # 获取字节类型的响应内容


4. 项目的配置文件(settings.py)相关配置
修改内容及其结果如下:
19行:伪装请求载体身份(伪装成浏览器)
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36'

22行:可以忽略或者不遵守robots协议
ROBOTSTXT_OBEY = False

注意:settings.py的配置项的名称都要大写


5. 爬取糗百首页中文字段子的内容和作者
对qsbk.py进行修改
"""
注意:记得修改settings.py的配置哦!!!

类QsbkSpider是继承了scrapy.Spider这个类的,
scrapy.Spider中的start_requests方法是我们爬虫主逻辑、策略,
因为start_requests方法默认只是做了一些简单的爬取措施,
如果要爬取的网页有了很强的反爬措施,此时就需要我们重写start_requests方法,破解反爬措施。
start_requests方法必须yield Request对象,因此需要导入 from scrapy import Request
"""

import scrapy
from scrapy import Request
# from scrapy.http import Request  # 跟上面导入的Request是一样的


class QsbkSpider(scrapy.Spider):
    name = 'qsbk'  # 应用名称
    # 允许爬取的域名(如果遇到非该域名的url则爬取不到数据)
    allowed_domains = ['www.qiushibaike.com']
    # 起始爬取的url
    start_urls = ['https://www.qiushibaike.com/text/']

    def start_requests(self):
        # 爬虫主逻辑 策略
        yield Request(
            url="https://www.qiushibaike.com/text/",
            callback=self.parse,  # 定义回调函数,不写默认就是 parse
        )

    def parse(self, response):
        # 用xpath或者BS或者 css selector进行数据解析
        # 这里的response自带xpath方法,可以将xpath表达式直接作用于该函数中
        odiv = response.xpath('//div[@id="content-left"]/div')
        content_list = []  # 用于存储解析到的数据
        # xpath函数返回的为列表,列表中存放的数据为Selector类型的数据
        # 我们解析到的内容被封装在了Selector对象中,需要调用extract()函数将解析的内容从Selecor中取出
        for div in odiv:
            # 1.获取用户名author
            author = div.xpath('.//div[@class="author clearfix"]/a/h2/text()')
            if author:
                author = author[0].extract()
            else:
                author = "匿名用户"
            # 2.获取这个用户的段子的内容contents
            contents = div.xpath('.//div[@class="content"]/span/text()')  # 遇到换行br就会生成一个Selector对象

            content_list.append({
                "author": author,
                "content": "".join([selector.extract().strip() for selector in contents])
            })
        print("content_list", content_list)
        return content_list

2、使用BeautifulSoup解析

"""
除了解析代码,其余代码跟上面的例子一样
"""

from bs4 import BeautifulSoup

def parse(self, response): print(response.text) soup = BeautifulSoup(response.text, 'lxml') odiv = soup.find_all('div', class_='article') content_list = [] # 用于存储解析到的数据 for div in odiv: author = div.find('div', class_='author').text print(author) content = div.find('div', class_='content').text print(content) dic = { '作者': author, '内容': content } print(dic) # 将数据存储到content_list这个列表中 content_list.append(dic) return content_list

四、爬取亚马逊的iphoneXs商品

1.主爬虫程序
"""
登录亚马逊官方,搜索:iphone xs max
需要解析出来的数据:商品简介、价格、配送方
爬取前10页

注意在settings中设置UA反爬和不遵守robots
"""

import scrapy
from scrapy import Request


class AmazonSpider(scrapy.Spider):
    name = 'amazon'
    allowed_domains = ['www.amazon.cn']
    start_urls = ['https://www.amazon.cn/']

    def start_requests(self):
        yield Request(
            url="https://www.amazon.cn/s/ref=nb_sb_noss_1?__mk_zh_CN=%E4%BA%9A%E9%A9%AC%E9%80%8A%E7%BD%91%E7%AB%99&url=search-alias%3Daps&field-keywords=iphone+xs+max",
            callback=self.parse_index,
            dont_filter=True
        )

    def parse_index(self, response):
        '''
        解析商品首页获取几十个商品详情的url,发请求
        解析函数的返回值:
           (1) 字典或者迭代数据(比如列表套字典)
           (2) 请求Request对象
           (3) item对象(通过pipeline做持久化的)
        '''

        # 商品列表页的所有商品的URL
        detail_urls = response.xpath('//*[contains(@id,"result_")]/div/div[3]/div[1]/a/@href').extract()
        for detail_url in detail_urls:
            print("detail_url", detail_url)
            yield Request(
                url=detail_url,
                callback=self.parse_detail,  # parse_detail解析详情页的函数
                dont_filter=True
            )

        # 解析一个下一页的URL,经过测试发现下面xpath解析出来的url是相对路径,因此需要我们拼接出完整的url
        # response.urljoin方法能帮我们把要爬取的网页域名和需要拼接的url进行拼接
        next_url = response.urljoin(response.xpath('//*[@id="pagnNextLink"]/@href').extract_first())
        yield Request(
            url=next_url,
            callback=self.parse_index,
            dont_filter=True
        )

    def parse_detail(self, response):
        '''
        response: 某一个商品详情页的响应体
        '''
        title = response.xpath('//*[@id="productTitle"]/text()')[0].extract().strip()
        price = response.xpath('//*[@id="priceblock_ourprice"]/text()')[0].extract().strip()
        delivery = response.xpath('//*[@id="ddmMerchantMessage"]/*[1]/text()').extract()

        print(title)
        print(price)
        print(delivery)


2.免费代理网站
http://www.goubanjia.com/


3.设置代理池
当你的ip访问亚马逊太过频繁的时候,亚马逊会对你进行反爬(暂封你的ip),
若你发现无法爬取亚马逊网页的时候,可设置代理池(下面会详细讲,现在先把网页爬下来)

步骤:
    1. 在免费代理网站上找一个能用的代理ip
    2. 在middlewares.py中找到AmazonDownloaderMiddleware类下的process_request方法
    3. 在process_request中写入如下几行
        proxy = 'http://77.70.115.104:8080'
        request.meta['download_timeout'] = 20
        request.meta['proxy'] = proxy

五、Item

Item对象是用于收集抓取数据的简单容器。它们提供类似字典的 API,并具有用于声明其可用字段的方便语法。
Scrapy Items类似于Django Models,但是Scrapy Items更简单,因为没有不同字段类型的概念。

1.声明Item
在项目中找到items.py,使用简单的类定义语法和Field对象声明项目

import scrapy

class AmazonItem(scrapy.Item):
    # define the fields for your item here like:
    title = scrapy.Field()
    price = scrapy.Field()
    delivery = scrapy.Field()


2.使用Item
# 在需要使用的地方(爬虫主程序)导入这个类
from ..items import AmazonItem

# 实例化对象并添加数据
item = AmazonItem()
item["title"] = title
item["price"] = price
item["delivery"] = delivery


3. Demo
# 上面亚马逊的parse_detail可以这样写(先实例化再设置数据)
def parse_detail(self, response):
    '''
    response: 某一个商品详情页的响应体
    '''
    title = response.xpath('//*[@id="productTitle"]/text()')[0].extract().strip()
    price = response.xpath('//*[@id="priceblock_ourprice"]/text()')[0].extract().strip()
    delivery = response.xpath('//*[@id="ddmMerchantMessage"]/*[1]/text()').extract()

    print(title)
    print(price)
    print(delivery)

    item = AmazonItem()  # 先实例化
    item["title"] = title  # 再设置数据
    item["price"] = price
    item["delivery"] = delivery

    return item

# 还可以这样写(实例化的时候初始化数据)
def parse_detail(self, response):
    '''
    response: 某一个商品详情页的响应体
    '''
    title = response.xpath('//*[@id="productTitle"]/text()')[0].extract().strip()
    price = response.xpath('//*[@id="priceblock_ourprice"]/text()')[0].extract().strip()
    delivery = response.xpath('//*[@id="ddmMerchantMessage"]/*[1]/text()').extract()

    print(title)
    print(price)
    print(delivery)

    item = AmazonItem(title=title, price=price, delivery=delivery)  # 实例化的时候初始化数据
    # 获取字段值
    print(item['title'])
    print(item.get('price'))
    
    # 访问所有键
    print(item.keys())
    
    # 访问所有的键值对
    print(item.items())
    
    return item

六、Item PipeLine

在一个项目被蜘蛛抓取之后,它被发送到项目管道,该项目管道通过顺序执行的几个组件处理它。

每个项目管道组件(有时简称为“项目管道”)是一个实现简单方法的Python类。他们收到一个项目并对其执行操作,同时决定该项目是否应该继续通过管道或被丢弃并且不再处理。

项目管道的典型用途是:
cleansing HTML data(清洗数据)
validating scraped data (checking that the items contain certain fields)(校验数据)
checking for duplicates (and dropping them)(去重)
storing the scraped item in a database(排序存储)

PipeLine只接收Item对象,PipeLine在项目中的pipelines.py里面定义。
定义好的PipeLine类需要在settings里面进行声明,只要在主程序中return item就会自动去settings中找到声明的PipeLine类,
然后对item进行处理,存储。

# 大概在settings的67行设置pipeline类,
ITEM_PIPELINES = {
   'Amazon.pipelines.AmazonPipeline': 300,
}

1、编写自己的项目管道

每个项管道组件都是一个以下方法的Python类:

# item进入pipeline前会调用这个方法
open_spider(self,蜘蛛)
打开蜘蛛时会调用此方法。

# 必须实现的方法,用来处理item数据的方法(核心方法)
process_item(self,项目,蜘蛛)
为每个项目管道组件调用此方法。process_item() 
返回带数据的dict,返回一个Item (或任何后代类)对象,返回Twisted Deferred或引发 DropItem异常。丢弃的项目不再由其他管道组件处理。

# item处理完毕后调用这个方法
close_spider(self,蜘蛛)
当蜘蛛关闭时调用此方法。

# 当pipeline类实例化的时候,如果有from_crawler方法会先执行这个方法,再执行init方法
# 参数crawler代表这个爬虫程序,可以在此拿到爬虫的名字、域名、配置参数等,都可以从这里拿到
from_crawler(cls,crawler)
如果存在,则调用此类方法以从a创建管道实例Crawler。它必须返回管道的新实例。Crawler对象提供对所有Scrapy核心组件的访问,
如设置和信号; 它是管道访问它们并将其功能挂钩到Scrapy的一种方式。

2、使用MongoDB存储item

1. settings中的配置
# 与Mongodb数据库相关配置
HOST = "127.0.0.1"
PORT = 27017
USER = "root"
PWD = ""
DB = "amazon"
TABLE = "goods"

# 声明pipeline类
ITEM_PIPELINES = {
   'Amazon.pipelines.MongodbPipeline': 300,
}


2. 定义pipeline类
from pymongo import MongoClient


class MongodbPipeline(object):
    def __init__(self, host, port, user, pwd, db, table):
        self.host = host
        self.port = port
        self.user = user
        self.pwd = pwd
        self.db = db
        self.table = table

    @classmethod
    def from_crawler(cls, crawler):
        """
        Scrapy会先通过getattr判断我们是否自定义了from_crawler,有则调它来完成实例化
        从爬虫程序crawler中的settings配置拿到我们需要的数据,返回给这个类,然后调用init实例化
        """
        HOST = crawler.settings.get('HOST')
        PORT = crawler.settings.get('PORT')
        USER = crawler.settings.get('USER')
        PWD = crawler.settings.get('PWD')
        DB = crawler.settings.get('DB')
        TABLE = crawler.settings.get('TABLE')
        # 必须返回这个类的对象
        return cls(HOST, PORT, USER, PWD, DB, TABLE)

    def open_spider(self, spider):
        """
        爬虫刚启动时执行一次
        """
        # self.client = MongoClient('mongodb://%s:%s@%s:%s' %(self.user,self.pwd,self.host,self.port))
        self.client = MongoClient(host=self.host, port=self.port)

    def close_spider(self, spider):
        """
        爬虫关闭时执行一次
        """
        self.client.close()

    def process_item(self, item, spider):
        # 操作并进行持久化
        d = dict(item)  # 把item转换成字典类型
        if all(d.values()):  # 当字典不为空的时候插入数据
            self.client[self.db][self.table].save(d)  # save方法也等于插入数据并保存
        return item

3、item流向多个pipeline

1. 基于上面的settings再进行配置
# 声明pipeline类
ITEM_PIPELINES = {
   'Amazon.pipelines.MongodbPipeline': 300,
   'Amazon.pipelines.FilePipeline': 400,
}


2. 再定义一个pipeline类
class FilePipeline(object):
    def open_spider(self, spider):
        """
        爬虫刚启动时执行一次
        """
        print("文件写入一个item")
        # self.client = MongoClient('mongodb://%s:%s@%s:%s' %(self.user,self.pwd,self.host,self.port))
        self.file = open("file_pipeline.txt", "w")

    def close_spider(self, spider):
        """
        爬虫关闭时执行一次
        """
        self.file.close()

    def process_item(self, item, spider):
        # 操作并进行持久化
        d = dict(item)
        import json
        self.file.write(json.dumps(d) + "
")

3. 多个管道的流程
ITEM_PIPELINES = {
   'Amazon.pipelines.MongodbPipeline': 300,
   'Amazon.pipelines.FilePipeline': 400,
}
300和400是权重,设置多少都行,看自己喜好,权重小的管道先执行,
因此MongodbPipeline先执行,执行完毕后,process_item方法必须return item,
return item后会把item传给下一个管道FilePipeline进行处理。

如果你想处理完后不再给别人处理了,那么可以
from scrapy.exceptions import DropItem

def process_item(self, item, spider):
    if 某种情况:
        raise DropItem("Duplicate item found: %s" % item)

七、下载中间件

scrapy的下载中间件个Django的中间件类似,
process_request、process_response等方法也是类似的

不同的是:
Django中process_request返回一个对象后,process_response是从process_request对应的process_response开始返回,
而scrapy的下载中间件process_request返回一个对象后,process_response是从第一个process_response开始返回,


1. settings中的配置
# 大概在settings的55行
DOWNLOADER_MIDDLEWARES = {
   'Amazon.middlewares.MyDownMiddleware': 543,
}

2. 权重
process_request权重小的先执行
process_response权重大的先执行
process_request权重小的先执行

3. 各个方法的作用
class MyDownMiddleware(object):
    def process_request(self, request, spider):
        """
        请求需要被下载时,经过所有下载器中间件的process_request调用
        :param request: 
        :param spider: 
        :return:  
            None,继续后续中间件去下载;
            Response对象,停止process_request的执行,开始执行process_response
            Request对象,停止中间件的执行,将Request重新调度
            raise IgnoreRequest异常,停止process_request的执行,开始执行process_exception
        """
        pass

    def process_response(self, request, response, spider):
        """
        spider处理完成,返回时调用
        :param response:
        :param result:
        :param spider:
        :return: 
            Response 对象:转交给其他中间件process_response
            Request 对象:停止中间件,request会被重新调度下载
            raise IgnoreRequest 异常:调用Request.errback
        """
        print('response1')
        return response

    def process_exception(self, request, exception, spider):
        """
        当下载处理器(download handler)或 process_request() (下载中间件)抛出异常
        :param response:
        :param exception:
        :param spider:
        :return: 
            None:继续交给后续中间件处理异常;
            Response对象:停止后续process_exception方法
            Request对象:停止中间件,request将会被重新调用下载
        """
        return None


因此我们更换代理的时候,应该放在process_exception中
def process_exception(self, request, exception, spider):
    # Called when a download handler or a process_request()
    # (from other downloader middleware) raises an exception.

    # Must either:
    # - return None: continue processing this exception
    # - return a Response object: stops process_exception() chain
    # - return a Request object: stops process_exception() chain
    proxy = 'http://77.70.115.104:8080'
    request.meta['download_timeout'] = 20
    request.meta['proxy'] = proxy
    return request  # 返回request,即把请求返回给调度器,调度器再重新去发请求

八、代理池

1、原理

我们跟换代理的时候,应该是在某个代理网站上,把可用的代理全部爬下来,
当出现异常的时候就去代理池中把真正能用的ip跟换到我的程序中,
因此还需要写一个爬代理ip网站的程序,这个不用担心,去github中搜就可以,
把搜到的应用结合到我们的爬虫程序中

2、步骤

1. 把下载到的程序放到我们爬虫程序中
2. 读README.md文件,根据步骤进行一些初始化配置
3. 把爬ip的程序启动后,把爬取到的代理ip存到MongoDB中
    [DB]
    ;Configure the database information
    ;type: SSDB/MONGODB if use redis, only modify the host port,the type should be SSDB
    type = MONGODB
    host = 127.0.0.1
    port = 27017
    name = proxy

4. 如果要在爬虫代码中使用的话, 可以将此api封装成函数直接使用,例如:
import requests

def get_proxy():
    return requests.get("http://127.0.0.1:5010/get/").content

def delete_proxy(proxy):
    requests.get("http://127.0.0.1:5010/delete/?proxy={}".format(proxy))

开发的api接口在这里设置
[API]
# API config http://127.0.0.1:5010
# The ip specified when starting the web API
ip = 0.0.0.0
# he port on which to run the web API
port = 5010

3、代码实现

1. 首先确保已经把代理ip的那个网站爬下来了

2. 确保DB和API设置好了

3. 在我们的爬虫程序中新建一个proxy.py文件
代码如下:
import requests

def get_proxy():
    return requests.get("http://127.0.0.1:5010/get/").content

def delete_proxy(proxy):
    requests.get("http://127.0.0.1:5010/delete/?proxy={}".format(proxy))

4. 我们爬虫程序的下载中间件代码
from .proxy import get_proxy,delete_proxy

class AmazonDownloaderMiddleware(object):
    def process_exception(self, request, exception, spider):
        # Called when a download handler or a process_request()
        # (from other downloader middleware) raises an exception.

        # Must either:
        # - return None: continue processing this exception
        # - return a Response object: stops process_exception() chain
        # - return a Request object: stops process_exception() chain
        proxy = "http://"+get_proxy()
        request.meta['download_timeout'] = 20
        request.meta["proxy"] = proxy
        return request
原文地址:https://www.cnblogs.com/Zzbj/p/10380983.html