Scrapy-redis的两种分布式爬虫

思考:
  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

image

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

image

这里需要着重注意:请求的协议头有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"
]

作者:SlashBoyMr_wang
链接:https://www.jianshu.com/p/5baa1d5eb6d9
來源:简书

推荐阅读更多精彩内容