爬虫框架:scrapy

Posted A-a

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了爬虫框架:scrapy相关的知识,希望对你有一定的参考价值。

一 介绍

    Scrapy一个开源和协作的框架,其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的,使用它可以以快速、简单、可扩展的方式从网站中提取所需的数据。但目前Scrapy的用途十分广泛,可用于如数据挖掘、监测和自动化测试等领域,也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。

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

The data flow in Scrapy is controlled by the execution engine, and goes like this:

  1. The Engine gets the initial Requests to crawl from the Spider.
  2. The Engine schedules the Requests in the Scheduler and asks for the next Requests to crawl.
  3. The Scheduler returns the next Requests to the Engine.
  4. The Engine sends the Requests to the Downloader, passing through the Downloader Middlewares (see process_request()).
  5. Once the page finishes downloading the Downloader generates a Response (with that page) and sends it to the Engine, passing through the Downloader Middlewares (see process_response()).
  6. The Engine receives the Response from the Downloader and sends it to the Spider for processing, passing through the Spider Middleware (see process_spider_input()).
  7. The Spider processes the Response and returns scraped items and new Requests (to follow) to the Engine, passing through the Spider Middleware (see process_spider_output()).
  8. The Engine sends processed items to Item Pipelines, then send processed Requests to the Scheduler and asks for possible next Requests to crawl.
  9. The process repeats (from step 1) until there are no more requests from the Scheduler.
1.引擎获得爬虫的初始请求。
2.引擎在调度程序中调度请求,并要求抓取下一个请求。 3.调度程序返回下一个请求给引擎 4.引擎通过下载中间件发出下载请求, 5.一旦页面完成下载,下载器会生成一个响应(包含该页面)并通过下载器的中间件将其发送到引擎 6.引擎收到来自下载器的响应并通过Spider的中间件将其发送给Spider进行处理 7.Spider处理响应,并将抓取的项目和新的请求通过Spider的中间件返回给引擎 8.引擎将处理过的项目发送到项目管道,然后将处理过的请求发送给调度程序,并请求下一个请求爬行。 9.重复上述步骤(从步骤1),直到没有更多的调度器请求。

 

Components:

  1. 引擎(EGINE)

    引擎负责控制系统所有组件之间的数据流,并在某些动作发生时触发事件。有关详细信息,请参见上面的数据流部分。

  2. 调度器(SCHEDULER)
    用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL的优先级队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
  3. 下载器(DOWLOADER)
    用于下载网页内容, 并将网页内容返回给EGINE,下载器是建立在twisted这个高效的异步模型上的
  4. 爬虫(SPIDERS)
    SPIDERS是开发人员自定义的类,用来解析responses,并且提取items,或者发送新的请求
  5. 项目管道(ITEM PIPLINES)
    在items被提取后负责处理它们,主要包括清理、验证、持久化(比如存到数据库)等操作
  6. 下载器中间件(Downloader Middlewares)
    位于Scrapy引擎和下载器之间,主要用来处理从EGINE传到DOWLOADER的请求request,已经从DOWNLOADER传到EGINE的响应response,你可用该中间件做以下几件事
    1. process a request just before it is sent to the Downloader (i.e. right before Scrapy sends the request to the website);
    2. change received response before passing it to a spider;
    3. send a new Request instead of passing received response to a spider;
    4. pass response to a spider without fetching a web page;
    5. silently drop some requests.
  7. 爬虫中间件(Spider Middlewares)
    位于EGINE和SPIDERS之间,主要工作是处理SPIDERS的输入(即responses)和输出(即requests)

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

二 安装

#Windows平台
    1、pip3 install wheel #安装后,便支持通过wheel文件安装软件,wheel文件官网:https://www.lfd.uci.edu/~gohlke/pythonlibs
    3、pip3 install lxml
    4、pip3 install pyopenssl
    5、下载并安装pywin32:https://sourceforge.net/projects/pywin32/files/pywin32/
    6、下载twisted的wheel文件:http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
    7、执行pip3 install 下载目录\\Twisted-17.9.0-cp36-cp36m-win_amd64.whl
    8、pip3 install scrapy
  
#Linux平台
    1、pip3 install scrapy

三 命令行工具

#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
#1、执行全局命令:请确保不在某个项目的目录下,排除受该项目配置的影响
scrapy startproject MyProject

cd MyProject
scrapy genspider baidu www.baidu.com

scrapy settings --get XXX #如果切换到项目目录下,看到的则是该项目的配置

scrapy runspider baidu.py

scrapy shell https://www.baidu.com
    response
    response.status
    response.body
    view(response)
    
scrapy view https://www.taobao.com #如果页面显示内容不全,不全的内容则是ajax请求实现的,以此快速定位问题

scrapy fetch --nolog --headers https://www.taobao.com

scrapy version #scrapy的版本

scrapy version -v #依赖库的版本


#2、执行项目命令:切到项目目录下
scrapy crawl baidu
scrapy check
scrapy list
scrapy parse http://quotes.toscrape.com/ --callback parse
scrapy bench
    
示范用法

四 项目结构以及爬虫应用简介 

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

文件说明:

  • scrapy.cfg  项目的主配置信息,用来部署scrapy时使用,爬虫相关的配置信息在settings.py文件中。
  • items.py    设置数据存储模板,用于结构化数据,如:Django的Model
  • pipelines    数据处理行为,如:一般结构化的数据持久化
  • settings.py 配置文件,如:递归的层数、并发数,延迟下载等。强调:配置文件的选项必须大写否则视为无效,正确写法USER_AGENT=\'xxxx\'
  • spiders      爬虫目录,如:创建文件,编写爬虫规则

注意:一般创建爬虫文件时,以网站域名命名

默认只能在cmd中执行爬虫,如果想在pycharm中执行需要做

#在项目目录下新建:entrypoint.py
from scrapy.cmdline import execute
execute([\'scrapy\', \'crawl\', \'xiaohua\'])
View Code
关于windows编码
import sys,os sys.stdout
=io.TextIOWrapper(sys.stdout.buffer,encoding=\'gb18030\')

五 Spiders

1、介绍

#1、Spiders是由一系列类(定义了一个网址或一组网址将被爬取)组成,具体包括如何执行爬取任务并且如何从页面中提取结构化的数据。

#2、换句话说,Spiders是你为了一个特定的网址或一组网址自定义爬取和解析页面行为的地方

2、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)

3、Spiders总共提供了五种类:

#1、scrapy.spiders.Spider #scrapy.Spider等同于scrapy.spiders.Spider
#2、scrapy.spiders.CrawlSpider
#3、scrapy.spiders.XMLFeedSpider
#4、scrapy.spiders.CSVFeedSpider
#5、scrapy.spiders.SitemapSpider

4、导入使用

# -*- coding: utf-8 -*-
import scrapy
from scrapy.spiders import Spider,CrawlSpider,XMLFeedSpider,CSVFeedSpider,SitemapSpider

class AmazonSpider(scrapy.Spider): #自定义类,继承Spiders提供的基类
    name = \'amazon\'
    allowed_domains = [\'www.amazon.cn\']
    start_urls = [\'http://www.amazon.cn/\']
    
    def parse(self, response):
        pass

5、class scrapy.spiders.Spider

这是最简单的spider类,任何其他的spider类都需要继承它(包含你自己定义的)。

该类不提供任何特殊的功能,它仅提供了一个默认的start_requests方法默认从start_urls中读取url地址发送requests请求,并且默认parse作为回调函数

class AmazonSpider(scrapy.Spider):
    name = \'amazon\' 
    
    allowed_domains = [\'www.amazon.cn\'] 
    
    start_urls = [\'http://www.amazon.cn/\']
    
    custom_settings = {
        \'BOT_NAME\' : \'Egon_Spider_Amazon\',
        \'REQUEST_HEADERS\' : {
          \'Accept\': \'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\',
          \'Accept-Language\': \'en\',
        }
    }
    
    def parse(self, response):
        pass

 

#1、name = \'amazon\' 
定义爬虫名,scrapy会根据该值定位爬虫程序
所以它必须要有且必须唯一(In Python 2 this must be ASCII only.)

#2、allowed_domains = [\'www.amazon.cn\'] 
定义允许爬取的域名,如果OffsiteMiddleware启动(默认就启动),
那么不属于该列表的域名及其子域名都不允许爬取
如果爬取的网址为:https://www.example.com/1.html,那就添加\'example.com\'到列表.

#3、start_urls = [\'http://www.amazon.cn/\']
如果没有指定url,就从该列表中读取url来生成第一个请求

#4、custom_settings
值为一个字典,定义一些配置信息,在运行爬虫程序时,这些配置会覆盖项目级别的配置
所以custom_settings必须被定义成一个类属性,由于settings会在类实例化前被加载

#5、settings
通过self.settings[\'配置项的名字\']可以访问settings.py中的配置,如果自己定义了custom_settings还是以自己的为准

#6、logger
日志名默认为spider的名字
self.logger.debug(\'=============>%s\' %self.settings[\'BOT_NAME\'])

#5、crawler:了解
该属性必须被定义到类方法from_crawler中

#6、from_crawler(crawler, *args, **kwargs):了解
You probably won’t need to override this directly  because the default implementation acts as a proxy to the __init__() method, calling it with the given arguments args and named arguments kwargs.

#7、start_requests()
该方法用来发起第一个Requests请求,且必须返回一个可迭代的对象。它在爬虫程序打开时就被Scrapy调用,Scrapy只调用它一次。
默认从start_urls里取出每个url来生成Request(url, dont_filter=True)

#针对参数dont_filter,请看自定义去重规则

如果你想要改变起始爬取的Requests,你就需要覆盖这个方法,例如你想要起始发送一个POST请求,如下
class MySpider(scrapy.Spider):
    name = \'myspider\'

    def start_requests(self):
        return [scrapy.FormRequest("http://www.example.com/login",
                                   formdata={\'user\': \'john\', \'pass\': \'secret\'},
                                   callback=self.logged_in)]

    def logged_in(self, response):
        # here you would extract links to follow and return Requests for
        # each of them, with another callback
        pass
        
#8、parse(response)
这是默认的回调函数,所有的回调函数必须返回an iterable of Request and/or dicts or Item objects.

#9、log(message[, level, component]):了解
Wrapper that sends a log message through the Spider’s logger, kept for backwards compatibility. For more information see Logging from Spiders.

#10、closed(reason)
爬虫程序结束时自动触发
定制scrapy.spider属性与方法详解
去重规则应该多个爬虫共享的,但凡一个爬虫爬取了,其他都不要爬了,实现方式如下

#方法一:
1、新增类属性
visited=set() #类属性

2、回调函数parse方法内:
def parse(self, response):
    if response.url in self.visited:
        return None
    .......

    self.visited.add(response.url) 

#方法一改进:针对url可能过长,所以我们存放url的hash值
def parse(self, response):
        url=md5(response.request.url)
    if url in self.visited:
        return None
    .......

    self.visited.add(url) 

#方法二:Scrapy自带去重功能
配置文件:
DUPEFILTER_CLASS = \'scrapy.dupefilter.RFPDupeFilter\' #默认的去重规则帮我们去重,去重规则在内存中
DUPEFILTER_DEBUG = False
JOBDIR = "保存范文记录的日志路径,如:/root/"  # 最终路径为 /root/requests.seen,去重规则放文件中

scrapy自带去重规则默认为RFPDupeFilter,只需要我们指定
Request(...,dont_filter=False) ,如果dont_filter=True则告诉Scrapy这个URL不参与去重。

#方法三:
我们也可以仿照RFPDupeFilter自定义去重规则,

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

#步骤二:配置文件settings.py:
DUPEFILTER_CLASS = \'项目名.dup.UrlFilter\'


# 源码分析:
from scrapy.core.scheduler import Scheduler
见Scheduler下的enqueue_request方法:self.df.request_seen(request)
去重规则:去除重复的url
#例一:
import scrapy

class MySpider(scrapy.Spider):
    name = \'example.com\'
    allowed_domains = [\'example.com\']
    start_urls = [
        \'http://www.example.com/1.html\',
        \'http://www.example.com/2.html\',
        \'http://www.example.com/3.html\',
    ]

    def parse(self, response):
        self.logger.info(\'A response from %s just arrived!\', response.url)
        
    
#例二:一个回调函数返回多个Requests和Items
import scrapy

class MySpider(scrapy.Spider):
    name = \'example.com\'
    allowed_domains = [\'example.com\']
    start_urls = [
        \'http://www.example.com/1.html\',
        \'http://www.example.com/2.html\',
        \'http://www.example.com/3.html\',
    ]

    def parse(self, response):
        for h3 in response.xpath(\'//h3\').extract():
            yield {"title": h3}

        for url in response.xpath(\'//a/@href\').extract():
            yield scrapy.Request(url, callback=self.parse)
            
            
#例三:在start_requests()内直接指定起始爬取的urls,start_urls就没有用了,

import scrapy
from myproject.items import MyItem

class MySpider(scrapy.Spider):
    name = \'example.com\'
    allowed_domains = [\'example.com\']

    def start_requests(self):
        yield scrapy.Request(\'http://www.example.com/1.html\', self.parse)
        yield scrapy.Request(\'http://www.example.com/2.html\', self.parse)
        yield scrapy.Request(\'http://www.example.com/3.html\', self.parse)

    def parse(self, response):
        for h3 in response.xpath(\'//h3\').extract():
            yield MyItem(title=h3)

        for url in response.xpath(\'//a/@href\').extract():
            yield scrapy.Request(url, callback=self.parse)
例子
我们可能需要在命令行为爬虫程序传递参数,比如传递初始的url,像这样
#命令行执行
scrapy crawl myspider -a category=electronics

#在__init__方法中可以接收外部传进来的参数
import scrapy

class MySpider(scrapy.Spider):
    name = \'myspider\'

    def __init__(self, category=None, *args, **kwargs):
        super(MySpider, self).__init__(*args, **kwargs)
        self.start_urls = [\'http://www.example.com/categories/%s\' % category]
        #...

        
#注意接收的参数全都是字符串,如果想要结构化的数据,你需要用类似json.loads的方法
参数传递

6、其他通用Spiders:https://docs.scrapy.org/en/latest/topics/spiders.html#generic-spiders

六 Selectors

#1 //与/
#2 text
#3、extract与extract_first:从selector对象中解出内容
#4、属性:xpath的属性加前缀@
#4、嵌套查找
#5、设置默认值
#4、按照属性查找
#5、按照属性模糊查找
#6、正则表达式
#7、xpath相对路径
#8、带变量的xpath
response.selector.css()
response.selector.xpath()
可简写为
response.css()
response.xpath()

#1 //与/
response.xpath(\'//body/a/\')#
response.css(\'div a::text\')

>>> response.xpath(\'//body/a\') #开头的//代表从整篇文档中寻找,body之后的/代表body的儿子
[]
>>> response.xpath(\'//body//a\') #开头的//代表从整篇文档中寻找,body之后的//代表body的子子孙孙
[<Selector xpath=\'//body//a\' data=\'<a href="image1.html">Name: My image 1 <\'>, <Selector xpath=\'//body//a\' data=\'<a href="image2.html">Name: My image 2 <\'>, <Selector xpath=\'//body//a\' data=\'<a href="
image3.html">Name: My image 3 <\'>, <Selector xpath=\'//body//a\' data=\'<a href="image4.html">Name: My image 4 <\'>, <Selector xpath=\'//body//a\' data=\'<a href="image5.html">Name: My image 5 <\'>]

#2 text
>>> response.xpath(\'//body//a/text()\')
>>> response.css(\'body a::text\')

#3、extract与extract_first:从selector对象中解出内容
>>> response.xpath(\'//div/a/text()\').extract()
[\'Name: My image 1 \', \'Name: My image 2 \', \'Name: My image 3 \', \'Name: My image 4 \', \'Name: My image 5 \']
>>> response.css(\'div a::text\').extract()
[\'Name: My image 1 \', \'Name: My image 2 \', \'Name: My image 3 \', \'Name: My image 4 \', \'Name: My image 5 \']

>>> response.xpath(\'//div/a/text()\').extract_first()
\'Name: My image 1 \'
>>> response.css(\'div a::text\').extract_first()
\'Name: My image 1 \'

#4、属性:xpath的属性加前缀@
>>> response.xpath(\'//div/a/@href\').extract_first()
\'image1.html\'
>>> response.css(\'div a::attr(href)\').extract_first()
\'image1.html\'

#4、嵌套查找
>>> response.xpath(\'//div\').css(\'a\').xpath(\'@href\').extract_first()
\'image1.html\'

#5、设置默认值
>>> response.xpath(\'//div[@id="xxx"]\').extract_first(default="not found")
\'not found\'

#4、按照属性查找
response.xpath(\'//div[@id="images"]/a[@href="image3.html"]/text()\').extract()
response.css(\'#images a[@href="image3.html"]/text()\').extract()

#5、按照属性模糊查找
response.xpath(\'//a[contains(@href,"image")]/@href\').extract()
response.css(\'a[href*="image"]::attr(href)\').extract()

response.xpath(\'//a[contains(@href,"image")]/img/@src\').extract()
response.css(\'a[href*="imag"] img::attr(src)\').extract()

response.xpath(\'//*[@href="image1.html"]\')
response.css(\'*[href="image1.html"]\')

#6、正则表达式
response.xpath(\'//a/text()\').re(r\'Name: (.*)\')
response.xpath(\'//a/text()\').re_first(r\'Name: (.*)\')

#7、xpath相对路径
>>> res=response.xpath(\'//a[contains(@href,"3")]\')[0]
>>> res.xpath(\'img\')
[<Selector xpath=\'img\' data=\'<img src="image3_thumb.jpg">\'>]
>>> res.xpath(\'./img\')
[<Selector xpath=\'./img\' data=\'<img src="image3_thumb.jpg">\'>]
>>> res.xpath(\'.//img\')
[<Selector xpath=\'.//img\' data=\'<img src="image3_thumb.jpg">\'>]
>>> res.xpath(\'//img\') #这就是从头开始扫描
[<Selector xpath=\'//img\' data=\'<img src="image1_thumb.jpg">\'>, <Selector xpath=\'//img\' data=\'<img src="image2_thumb.jpg">\'>, <Selector xpath=\'//img\' data=\'<img src="image3_thumb.jpg">\'>, <Selector xpa
th=\'//img\' data=\'<img src="image4_thumb.jpg">\'>, <Selector xpath=\'//img\' data=\'<img src="image5_thumb.jpg">\'>]

#8、带变量的xpath
>>> response.xpath(\'//div[@id=$xxx]/a/text()\',xxx=\'images\').extract_first()
\'Name: My image 1 \'
>>> response.xpath(\'//div[count(a)=$yyy]/@id\',yyy=5).extract_first() #求有5个a标签的div的id
\'images\'

https://docs.scrapy.org/en/latest/topics/selectors.html

七 Items

https://docs.scrapy.org/en/latest/topics/items.html 

八 Item Pipeline

 
#一:可以写多个Pipeline类
#1、如果优先级高的Pipeline的process_item返回一个值或者None,会自动传给下一个pipline的process_item,
#2、如果只想让第一个Pipeline执行,那得让第一个pipline的process_item抛出异常raise DropItem()

#3、可以用spider.name == \'爬虫名\' 来控制哪些爬虫用哪些pipeline

二:示范
from scrapy.exceptions import DropItem

class CustomPipeline(object):
    def __init__(self,v):
        self.value = v

    @classmethod
    def from_crawler(cls, crawler):
        """
        Scrapy会先通过getattr判断我们是否自定义了from_crawler,有则调它来完
        成实例化
        """
        val = crawler.settings.getint(\'MMMM\')
        return cls(val)

    def open_spider(self,spider):
        """
        爬虫刚启动时执行一次
        """
        print(\'000000\')

    def close_spider(self,spider):
        """
        爬虫关闭时执行一次
        """
        print(\'111111\')


    def process_item(self, item, spider):
        # 操作并进行持久化

        # return表示会被后续的pipeline继续处理
        return item

        # 表示将item丢弃,不会被后续pipeline处理
        # raise DropItem()
自定义pipeline
#1、settings.py
HOST="127.0.0.1"
PORT=27017
USER="root"
PWD="123"
DB="amazon"
TABLE="goods"



ITEM_PIPELINES = {
   \'Amazon.pipelines.CustomPipeline\': 200,
}

#2、pipelines.py
class CustomPipeline(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,有则调它来完
        成实例化
        """
        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))

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


    def process_item(self, item, spider):
        # 操作并进行持久化

        self.client[self.db][self.table].save(dict(item))
示范

https://docs.scrapy.org/en/latest/topics/item-pipeline.html

九 Dowloader Middeware

下载中间件的用途
    1、在process——request内,自定义下载,不用scrapy的下载
    2、对请求进行二次加工,比如
        设置请求头
        设置cookie
        添加代理
            scrapy自带的代理组件:
                from scrapy.downloadermiddlewares.httpproxy import HttpProxyMiddleware
                from urllib.request import getproxies
class DownMiddleware1(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
下载器中间件
#1、与middlewares.py同级目录下新建proxy_handle.py
import requests

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

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

#2、middlewares.py
from Amazon.proxy_handle import get_proxy,delete_proxy

class DownMiddleware1(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
        """
        proxy="http://" + get_proxy()
        request.meta[\'download_timeout\']=20
        request.meta["proxy"] = proxy
        print(\'为%s 添加代理%s \' % (request.url, proxy),end=\'\')
        print(\'元数据为\',request.meta)

    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(\'返回状态吗\',response.status)
        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将会被重新调用下载
        """
        print(\'代理%s,访问%s出现异常:%s\' %(request.meta[\'proxy\'],request.url,exception))
        import time
        time.sleep(5)
        delete_proxy(request.meta[\'proxy\'].split("//")[-1])
        request.meta[\'proxy\']=\'http://\'+get_proxy()

        return request
配置代理

十 Spider Middleware

class SpiderMiddleware(object):

    def process_spider_input(self,response, spider):
        """
        下载完成,执行,然后交给parse处理
        :param response: 
        :param spider: 
        :return: 
        """
        pass

    def process_spider_output(self,response, result, spider):
        """
        spider处理完成,返回时调用
        :param response:
        :param result:
        :param spider:
        :return: 必须返回包含 Request 或 Item 对象的可迭代对象(iterable)
        """
        return result

    def process_spider_exception(self,response, exception, spider):
        """
        异常调用:在process_spider_input抛出异常时调用
        :param response:
        :param exception:
        :param spider:
        :return: None,继续交给后续中间件处理异常;含 Response 或 Item 的可迭代对象(iterable),交给调度器或pipeline
        """
        return None


    def process_start_requests(self,start_requests, spider):
        """
        爬虫启动时调用
        :param start_requests:
        :param spider:
        :return: 包含 Request 对象的可迭代对象
        """
        return start_requests
爬虫中间件

十一 自定义扩展

自定义扩展(与django的信号类似)
    1、django的信号是django是预留的扩展,信号一旦被触发,相应的功能就会执行
    2、scrapy自定义扩展的好处是可以在任意我们想要的位置添加功能,而其他组件中提供的功能只能在规定的位置执行
#1、在与settings同级目录下新建一个文件,文件名可以为extentions.py,内容如下
from scrapy import signals


class MyExtension(object):
    def __init__(self, value):
        self.value = value

    @classmethod
    def from_crawler(cls, crawler):
        val = crawler.settings.getint(\'MMMM\')
        obj = cls(val)

        crawler.signals.connect(obj.spider_opened, signal=signals.spider_opened)
        crawler.signals.connect(obj.spider_closed, signal=signals.spider_closed)

        return obj

    def spider_opened(self, spider):
        print(\'=============>open\')

    def spider_closed(self, spider):
        print(\'=============>close\')

#2、配置生效
EXTENSIONS = {
    "Amazon.extentions.MyExtension":200
}
View Code

十二 settings.py

#==>第一部分:基本配置<===
#1、项目名称,默认的USER_AGENT由它来构成,也作为日志记录的日志名
BOT_NAME = \'Amazon\'

#2、爬虫应用路径
SPIDER_MODULES = [\'Amazon.spiders\']
NEWSPIDER_MODULE = \'Amazon.spiders\'

#3、客户端User-Agent请求头
#USER_AGENT = \'Amazon (+http://www.yourdomain.com)\'

#4、是否遵循爬虫协议
# Obey robots.txt rules
ROBOTSTXT_OBEY = False

#5、是否支持cookie,cookiejar进行操作cookie,默认开启
#COOKIES_ENABLED = False

#6、Telnet用于查看当前爬虫的信息,操作爬虫等...使用telnet ip port ,然后通过命令操作
#TELNETCONSOLE_ENABLED = False
#TELNETCONSOLE_HOST = \'127.0.0.1\'
#TELNETCONSOLE_PORT = [6023,]

#7、Scrapy发送HTTP请求默认使用的请求头
#DEFAULT_REQUEST_HEADERS = {
#   \'Accept\': \'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\',
#   \'Accept-Language\': \'en\',
#}



#===>第二部分:并发与延迟<===
#1、下载器总共最大处理的并发请求数,默认值16
#CONCURRENT_REQUESTS = 32

#2、每个域名能够被执行的最大并发请求数目,默认值8
#CONCURRENT_REQUESTS_PER_DOMAIN = 16

#3、能够被单个IP处理的并发请求数,默认值0,代表无限制,需要注意两点
#I、如果不为零,那CONCURRENT_REQUESTS_PER_DOMAIN将被忽略,即并发数的限制是按照每个IP来计算,而不是每个域名
#II、该设置也影响DOWNLOAD_DELAY,如果该值不为零,那么DOWNLOAD_DELAY下载延迟是限制每个IP而不是每个域
#CONCURRENT_REQUESTS_PER_IP = 16

#4、如果没有开启智能限速,这个值就代表一个规定死的值,代表对同一网址延迟请求的秒数
#DOWNLOAD_DELAY = 3


#===>第三部分:智能限速/自动节流:AutoThrottle extension<===
#一:介绍
from scrapy.contrib.throttle import AutoThrottle #http://scrapy.readthedocs.io/en/latest/topics/autothrottle.html#topics-autothrottle
Python之Scrapy安装

走近代码之Python--爬虫框架Scrapy

Python编程基础之(五)Scrapy爬虫框架

爬虫框架Scrapy 之 --- scrapy文件

初识scrapy爬虫框架

Python爬虫教程-30-Scrapy 爬虫框架介绍