zoukankan      html  css  js  c++  java
  • 爬虫之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
  • 相关阅读:
    什么叫“全力以赴”?
    Hibernate 异常 —— Unable to instantiate default tuplize
    也许用得着的英文短语(持续整理)
    也许用得着的英文句子(持续更新)
    iPhone(iOS设备) 无法更新或恢复时, 如何进入恢复模式
    poj 2778 DNA Sequence(AC自动机 + 矩阵快速幂)
    hdu 3974 线段树 将树弄到区间上
    hdu 2865 Polya计数+(矩阵 or 找规律 求C)
    Polya计数
    poj 2888 Magic Bracelet(Polya+矩阵快速幂)
  • 原文地址:https://www.cnblogs.com/Zzbj/p/10380983.html
Copyright © 2011-2022 走看看