美文网首页scrapy数据分析人生苦短-python
Scrapy-redis的两种分布式爬虫的实现

Scrapy-redis的两种分布式爬虫的实现

作者: SlashBoyMr_wang | 来源:发表于2018-11-26 16:10 被阅读0次

    前言:原生的Scrapy框架为什么做不了分布式?

    思考:
      1. Scrapy分布式爬虫意味着几台机器通过某种方式共同执行一套爬取任务,这就首先要求每台机器都要有Scrapy框架,一套Scrapy框架就有一套Scrapy五大核心组件,引擎--调度器--下载器--爬虫--项目管道,各自独有的调度器没有办法实现任务的共享,所以不能实现分布式爬取。
      2. 假设可以实现Scrapy框架的调度器共享,那么就能实现分布式爬取了吗?答案是不能,因为我们实现了任务的共享,但是框架之间的项目管道是单独的,我们的任务下载完之后,我们爬取的有效信息还是不能全部存放在某个指定的位置,所以要想实现分布式爬虫,需要同时满足调度器和项目管道的共享才可以达到分布式的效果。

    实现:基于Scrapy-redis实现分布式爬虫:
      scrapy-redis内部实现了调度器和项目管道共享,可以实现分布式爬虫

    一、redis数据库实现RedisCrawlSpider分布式操作

    案例简述:分布式爬虫爬取抽屉网全栈主题文本数据

    redis的准备工作:
      1.对redis配置文件进行配置:
        - 注释该行:bind 127.0.0.1,表示可以让其他ip访问redis
        - 将yes该为no:protected-mode no,表示可以让其他ip操作redis
      2.启动redis:
        mac/linux: redis-server redis.conf
        windows: redis-server.exe redis-windows.conf

    实现分布式爬虫的操作步骤:
      1. 将redis数据库的配置文件进行改动: .修改值 protected-mode no .注释 bind 127.0.0.1
      2. 下载scrapy-redis
      pip3 install scraps-redis
      3. 创建工程 scrapy startproject 工程名
      scrapy startproject 工程名
      4. 创建基于scrawlSpider的爬虫文件
      cd 工程名
      scrapy genspider -t crawl 项目名
      5. 导入RedisCrawlSpider类
      from scrapy_redis.spiders import RedisCrawlSpider
      6. 在现有代码的基础上进行连接提取和解析操作
      class RidesdemoSpider(RedisCrawlSpider):
      redis_key = "redisQueue"
      7. 将解析的数据值封装到item中,然后将item对象提交到scrapy-redis组件中的管道里(自建项目的管道没什么用了,可以直接删除了,用的是组件封装好的scrapy_redis.pipelines中)
      ITEM_PIPELINES = {
       'scrapy_redis.pipelines.RedisPipeline': 400,
      }
    .  8. 管道会将数据值写入到指定的redis数据库中(在配置文件中进行指定redis数据库ip的编写)
      REDIS_HOST = '192.168.137.76'
      REDIS_PORT = 6379
      REDIS_ENCODING = ‘utf-8’
      # REDIS_PARAMS = {‘password’:’123456’}
      9. 在当前工程中使用scrapy-redis封装好的调度器(在配置文件中进行配置)
      # 使用scrapy-redis组件的去重队列(过滤)
      DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
      # 使用scrapy-redis组件自己的调度器(核心代码共享调度器)
      SCHEDULER = "scrapy_redis.scheduler.Scheduler"
      # 是否允许暂停
      SCHEDULER_PERSIST = True
      11. 启动redis服务器:
      redis-server redis.windows.conf windows系统
      redis-server redis.conf mac系统
      12. 启动redis-cli
      redis-cli
      13. 执行当前爬虫文件:
      scrapy runspider 爬虫文件.py
      14. 向队列中扔一个起始url>>>在redis-cli执行扔的操作:
      lpush redis_key的value值 起始url

    spider.py文件:

    # -*- coding: utf-8 -*-
    from scrapy.linkextractors import LinkExtractor
    from scrapy.spiders import Rule
    from scrapy_redis.spiders import RedisCrawlSpider
    from redisScrapyPro.items import RedisscrapyproItem
    
    class RidesdemoSpider(RedisCrawlSpider):
        name = 'redisDemo'
    
        # scrapy_redis的调度器队列的名称,最终我们会根据该队列的名称向调度器队列中扔一个起始url
        redis_key = "redisQueue"
    
        link = LinkExtractor(allow=r'https://dig.chouti.com/.*?/.*?/.*?/\d+')
        link1 = LinkExtractor(allow=r'https://dig.chouti.com/all/hot/recent/1')
        rules = (
            Rule(link, callback='parse_item', follow=True),
            Rule(link1, callback='parse_item', follow=True),
        )
    
        def parse_item(self, response):
            div_list = response.xpath('//*[@id="content-list"]/div')
            for div in div_list:
                content = div.xpath('string(./div[@class="news-content"]/div[1]/a[1])').extract_first().strip().replace("\t","")
                print(content)
                item = RedisscrapyproItem()
                item['content'] = content
                yield item
    

    settings.py

    BOT_NAME = 'redisScrapyPro'
    
    SPIDER_MODULES = ['redisScrapyPro.spiders']
    NEWSPIDER_MODULE = 'redisScrapyPro.spiders'
    
    # Crawl responsibly by identifying yourself (and your website) on the user-agent
    USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36'
    
    # Obey robots.txt rules
    ROBOTSTXT_OBEY = False
    
    ITEM_PIPELINES = {
        'scrapy_redis.pipelines.RedisPipeline': 400,
    }
    REDIS_HOST = '192.168.137.76'
    REDIS_PORT = 6379
    REDIS_ENCODING = 'utf-8'
    # REDIS_PARAMS = {‘password’:’123456’}
    
    
    # 使用scrapy-redis组件的去重队列
    DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
    # 使用scrapy-redis组件自己的调度器
    SCHEDULER = "scrapy_redis.scheduler.Scheduler"
    # 是否允许暂停
    SCHEDULER_PERSIST = True
    

    更多项目代码
    https://github.com/wangjifei121/FB-RedisCrawlSpider

    二、redis数据库实现RedisSpider分布式操作

    案例简述:分布式爬虫爬取网易新闻(国内,国际,军事,航空四个板块)

    扩展知识点使用:

    • selenium如何被应用到scrapy框架
    • UA池的使用
    • 代理IP池的使用

    RedisSpider分布式操作的步骤和RedisCrawlSpider分布式的搭建步骤是相同的,参照以上步骤来学习搭建就可以。

    接下来主要讲解一下拓展知识点的使用:

    一、selenium如何被应用到scrapy框架

    首先看spider类中增加的两个方法:
    def __init__(self):
      pass
    def closed(self, spider):
      pass
      通过对网易新闻网页分析,我们可以看出网页的数据采取了动态加载数据的反爬措施,这样我们要想获取更多数据就需要使用到selenium的webdriver类了。
      使用webdriver的第一步就是实例化一个webdriver对象,而实例化的对象只需要实例化一次就能在爬取的过程中使用,所以就应该想到类的实例化方法_init_。
      实例化的webdriver对象在结束使用后需要关闭,正好spider类给我们提供了closed方法来做关闭操作。这样我们就可以通过这两个方法来实现我们的想法了。
    .
      那我们实例化好了webdriver对象该怎么用它呢?在哪里用?
      首先我们在不用selenium的时候我们发现页面的数据我们是获取不到的,也可以换个角度来说,我们获取到了数据但不是我们想要的。这样我们的需求就要求我们重新来获取想要的response,可以肯定的是,每次请求我们都要做相应的处理,这时候经验丰富的你就应该想到了三个字《中间件》,那在哪个中间件来做对应的处理呢?
      很显然要在process_response中间件中来执行我们的selenium的相关操作,这个中间件的作用就是拦截到响应对象(下载器传递给Spider的响应对象),通过处理、伪装response从而得到我们想要的数据。
    .
    process_response中间件中参数解释:
      request:响应对象对应的请求对象
      response:拦截到的响应对象
      spider:爬虫文件中对应的爬虫类的实例
    .
    在中间件中我们主要做了哪些操作呢?
    通过实例化好的浏览器对象发动请求-->执行相应的js代码-->获取对应的页面数据-->篡改相应对象-->返回response

    二、UA池代码的编写
    和上面的方法一样,UA池代码也需要在中间件中编写,需要导入一个类来继承,然后构建我们的UA类:
    from scrapy.contrib.downloadermiddleware.useragent import UserAgentMiddleware

    屏幕快照 2018-11-26 下午3.50.31.png

    三、IP代理池的编写
    问题的思考方式是一样的,这里就略过了

    屏幕快照 2018-11-26 下午3.53.21.png
    这里需要着重注意:请求的协议头有http 和 https两种,我们需要做相应的判断

    最后中间件一定要在settings中进行注册!!!注册!!!注册!!!

    最后附上spider.py和middlewares.py的代码:
    spider.py

    # -*- coding: utf-8 -*-
    import scrapy
    from selenium import webdriver
    from wangyiPro.items import WangyiproItem
    from scrapy_redis.spiders import RedisSpider
    
    
    class WangyiSpider(RedisSpider):
        name = 'wangyi'
        # allowed_domains = ['www.xxxx.com']
        # start_urls = ['https://news.163.com']
        redis_key = 'wangyi'
    
        def __init__(self):
            # 实例化一个浏览器对象(实例化一次)
            self.bro = webdriver.Chrome(executable_path='/Users/wangjifei/my_useful_file/chromedriver')
    
        # 必须在整个爬虫结束后,关闭浏览器
        def closed(self, spider):
            print('爬虫结束')
            self.bro.quit()
    
        def parse(self, response):
            lis = response.xpath('//div[@class="ns_area list"]/ul/li')
            indexs = [3, 4, 6, 7]
            li_list = []  # 存储的就是国内,国际,军事,航空四个板块对应的li标签对象
            for index in indexs:
                li_list.append(lis[index])
            # 获取四个板块中的链接和文字标题
            for li in li_list:
                url = li.xpath('./a/@href').extract_first()
                title = li.xpath('./a/text()').extract_first()
    
                # 对每一个板块对应的url发起请求,获取页面数据(标题,缩略图,关键字,发布时间,url)
                yield scrapy.Request(url=url, callback=self.parseSecond, meta={'title': title})
    
        def parseSecond(self, response):
            div_list = response.xpath('//div[@class="data_row news_article clearfix "]')
            # print(len(div_list))
            for div in div_list:
                head = div.xpath('.//div[@class="news_title"]/h3/a/text()').extract_first()
                url = div.xpath('.//div[@class="news_title"]/h3/a/@href').extract_first()
                imgUrl = div.xpath('./a/img/@src').extract_first()
                tag = div.xpath('.//div[@class="news_tag"]//text()').extract()
                tags = []
                for t in tag:
                    t = t.strip(' \n \t')
                    tags.append(t)
                tag = "".join(tags)
    
                # 获取meta传递过来的数据值title
                title = response.meta['title']
    
                # 实例化item对象,将解析到的数据值存储到item对象中
                item = WangyiproItem()
                item['head'] = head
                item['url'] = url
                item['imgUrl'] = imgUrl
                item['tag'] = tag
                item['title'] = title
    
                # 对url发起请求,获取对应页面中存储的新闻内容数据
                yield scrapy.Request(url=url, callback=self.getContent, meta={'item': item})
                # print(head+":"+url+":"+imgUrl+":"+tag)
    
        def getContent(self, response):
            # 获取传递过来的item
            item = response.meta['item']
    
            # 解析当前页面中存储的新闻数据
            content_list = response.xpath('//div[@class="post_text"]/p/text()').extract()
            content = "".join(content_list)
            item['content'] = content
    
            yield item
    

    middlewares.py

    from scrapy.http import HtmlResponse
    import time
    from scrapy.contrib.downloadermiddleware.useragent import UserAgentMiddleware
    import random
    #UA池代码的编写(单独给UA池封装一个下载中间件的一个类)
    #1,导包UserAgentMiddlware类
    class RandomUserAgent(UserAgentMiddleware):
    
        def process_request(self, request, spider):
            #从列表中随机抽选出一个ua值
            ua = random.choice(user_agent_list)
            #ua值进行当前拦截到请求的ua的写入操作
            request.headers.setdefault('User-Agent',ua)
    
    #批量对拦截到的请求进行ip更换
    class Proxy(object):
        def process_request(self, request, spider):
            #对拦截到请求的url进行判断(协议头到底是http还是https)
            #request.url返回值:http://www.xxx.com
            h = request.url.split(':')[0]  #请求的协议头
            if h == 'https':
                ip = random.choice(PROXY_https)
                request.meta['proxy'] = 'https://'+ip
            else:
                ip = random.choice(PROXY_http)
                request.meta['proxy'] = 'http://' + ip
    
    class WangyiproDownloaderMiddleware(object):
        #拦截到响应对象(下载器传递给Spider的响应对象)
        #request:响应对象对应的请求对象
        #response:拦截到的响应对象
        #spider:爬虫文件中对应的爬虫类的实例
        def process_response(self, request, response, spider):
            #响应对象中存储页面数据的篡改
            if request.url in['http://news.163.com/domestic/','http://news.163.com/world/','http://news.163.com/air/','http://war.163.com/']:
                spider.bro.get(url=request.url)
                js = 'window.scrollTo(0,document.body.scrollHeight)'
                spider.bro.execute_script(js)
                time.sleep(2)  #一定要给与浏览器一定的缓冲加载数据的时间
                #页面数据就是包含了动态加载出来的新闻数据对应的页面数据
                page_text = spider.bro.page_source
                #篡改响应对象
                return HtmlResponse(url=spider.bro.current_url,body=page_text,encoding='utf-8',request=request)
            else:
                return response
    
    PROXY_http = [
        '153.180.102.104:80',
        '195.208.131.189:56055',
    ]
    PROXY_https = [
        '120.83.49.90:9000',
        '95.189.112.214:35508',
    ]
    
    user_agent_list = [
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 "
            "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
            "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 "
            "(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 "
            "(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
            "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 "
            "(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
            "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 "
            "(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 "
            "(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
            "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 "
            "(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
            "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 "
            "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 "
            "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
            "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
            "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
            "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
            "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 "
            "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
            "(KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 "
            "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
            "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 "
            "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
    ]
    

    更多项目代码
    https://github.com/wangjifei121/FB-RedisSpider

    相关文章

      网友评论

        本文标题:Scrapy-redis的两种分布式爬虫的实现

        本文链接:https://www.haomeiwen.com/subject/dxjbqqtx.html