scrapy总结(四)---Scrapy-redis分布式爬虫

329 阅读6分钟

概述

我们一般在使用Request-Html,Requests爬取数据的时候,为了提高爬虫的速度和效率,会选择开多线程和多进程,但是有的时候还是避免不了网络拥堵,资源没有合理化利用的时候,还有我们在为每一个请求配置Cookie和UA以及Proxy的时候也很麻烦,小型的爬虫还要,一旦涉及到大的项目,这些请求库就显得捉襟见肘了

这个时候我们看Scrapy,他是一个我们可以看做是协程的异步爬虫框架,请求之间都是异步而且并发的,在快速开发方面有很大的优势,适合大的爬虫项目,但是在开发过程中能明显感觉到,一旦碰到账号受到限制,以及带宽等限制之后,请求就会产生拥堵,他确实能一次性发送大量的请求,但是也会发生请求丢失的风险

那么我们面对一些数据量很大,请求次数较多,限制较高的项目的时候,为了提升爬取的效率,以及减少请求的丢失率,就会用到下面介绍的分布式爬虫框架

什么是分布式

举一个简单的例子,就是我们平时逛淘宝的时候,会去想淘宝不可能一台机器就能顶住那么高的并发量以及压力,后面肯定是将我们用户的请求进行了分发,相当于我们nginx中的反向代理,你看着是想一个ip地址发送得请求,但是你不知道到底是哪一台机器处理了你的请求

那这些处理我们请求的多台服务器组成的系统,就是分布式系统,我们称之为集群

在爬虫里面,我们完全可以将大量的请求分发到多台服务器进行处理,这样一来,加快了抓取数据的速度,减少了请求的丢失率,唯一的缺点就是需要一个集群

Scrapy-redis

安装

pip install scrapy-redis

Scrapy-redis是一个分布式的爬虫框架,这是该框架在github上的地址github.com/rmax/scrapy…

我们在该框架中能找到上述图片中的样例,在开发过程中我们可以适度的参考作者提供的代码样例

下面对各个文件展开说明

settings.py

详情请见注释

SPIDER_MODULES = ['example.spiders']
NEWSPIDER_MODULE = 'example.spiders'

# x需要改成自己的ua,或者使用UserAgent库中的ua
USER_AGENT = 'scrapy-redis (+https://github.com/rolando/scrapy-redis)'

# 下面这两行行是对请求进行过滤的代码,copy即可
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
SCHEDULER = "scrapy_redis.scheduler.Scheduler"

# 允许中途暂停,继续
SCHEDULER_PERSIST = True
#SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderPriorityQueue"
#SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderQueue"
#SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderStack"

# copy即可
ITEM_PIPELINES = {
    # 'example.pipelines.ExamplePipeline': 300,
    'scrapy_redis.pipelines.RedisPipeline': 400,
}

# 日志级别
LOG_LEVEL = 'DEBUG'

# Introduce an artifical delay to make use of parallelism. to speed up the
# crawl.
# 下载等待
DOWNLOAD_DELAY = 1

# redis的配置
REDIS_HOST = '182.92.178.111'
REDIS_PORT = 6379
REDIS_PARAMS = {
    'password': '*********',
    'db': 11
    }

# 或者在解析器中加入
"""
custom_settings = {
    'LOG_LEVEL': 'DEBUG',
    'DOWNLOAD_DELAY': 1,

    # 指定redis数据库的连接参数
    'REDIS_HOST': '182.92.178.111',
    'REDIS_PORT': 6379,

    # 指定 redis链接密码,和使用哪一个数据库
    'REDIS_PARAMS' : {
        'password': '***************',
        'db': 11
    },

"""

# 如果要用到数据库的话
MYSQL_DB= {
    "host": "182.92.178.111",
    "database": "duanzi",
    "user": "root",
    "password": "*********",
    "port": 3306
}

关于mysql和redis的linux环境的搭建可以看我往期博客

juejin.cn/post/689234…

items.py和pipelines.py勇哥哥我就不赘述了,跟scrapy中一样的,详情请看www.osgeo.cn/scrapy/topi…

dmoz.py

我们可以看到dmoz.py中的代码是使用的CrawlSpider创建的,具体的创建脚本如下

scrapy genspider -t crawl qiushi qiushi.com

这里没有用到分布式获取request,我们再接着看(其实我们实在setting中配置好redis之后,获取到的数据就可以到redis中了)

mycrawler_redis.py

这里就用到了分布式了,继承了RedisCrawlSpider详情看注释

from scrapy.spiders import Rule
# 规则
from scrapy.linkextractors import LinkExtractor

# 这里让爬虫类继承RedisCrawlSpider
from scrapy_redis.spiders import RedisCrawlSpider


class MyCrawler(RedisCrawlSpider):
    """Spider that reads urls from redis queue (myspider:start_urls)."""
    # 爬虫的名字
    name = 'mycrawler_redis'
    
    # 很重要啊,很快啊!!
    # 这里是redis请求列表中的key
    redis_key = 'mycrawler:start_urls'
	
    # 请求过来之后获取的元素,一般这里会去获取url,方便下面parse函数对页面进行处理
    rules = (
        # follow all links
        Rule(LinkExtractor(), callback='parse_page', follow=True),
    )
	
    # 这个我就感觉没必要,也没细看
    def __init__(self, *args, **kwargs):
        # Dynamically define the allowed domains list.
        domain = kwargs.pop('domain', '')
        self.allowed_domains = filter(None, domain.split(','))
        super(MyCrawler, self).__init__(*args, **kwargs)
	
    # 页面解析函数
    def parse_page(self, response):
        return {
            'name': response.css('title::text').extract_first(),
            'url': response.url,
        }

myspider_redis

这里也用到了分布式了,继承了RedisSpider详情看注释

# 大致看看,后面有项目示例
from scrapy_redis.spiders import RedisSpider


class MySpider(RedisSpider):
    """Spider that reads urls from redis queue (myspider:start_urls)."""
    name = 'myspider_redis'
    redis_key = 'myspider:start_urls'

    def __init__(self, *args, **kwargs):
        # Dynamically define the allowed domains list.
        domain = kwargs.pop('domain', '')
        self.allowed_domains = filter(None, domain.split(','))
        super(MySpider, self).__init__(*args, **kwargs)

    def parse(self, response):
        return {
            'name': response.css('title::text').extract_first(),
            'url': response.url,
        }

使用scrapy-redis抓取“求实百科”的数据

项目结构

settings.py

首先看settings.py的配置

# Scrapy settings for duanzi project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
#     https://docs.scrapy.org/en/latest/topics/settings.html
#     https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
#     https://docs.scrapy.org/en/latest/topics/spider-middleware.html
from fake_useragent import UserAgent

BOT_NAME = 'duanzi'

SPIDER_MODULES = ['duanzi.spiders']
NEWSPIDER_MODULE = 'duanzi.spiders'

USER_AGENT = UserAgent().random
ROBOTSTXT_OBEY = False

DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
SCHEDULER_PERSIST = True
# SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderPriorityQueue"
# SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderQueue"
# SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderStack"

ITEM_PIPELINES = {
    'scrapy_redis.pipelines.RedisPipeline': 400,
    # 'duanzi.pipelines.DuanziPipeline': 300,

}

# 日志级别
LOG_LEVEL = 'DEBUG'

# Introduce an artifical delay to make use of parallelism. to speed up the
# crawl.
# 下载等待
DOWNLOAD_DELAY = 1

# redis的配置
REDIS_HOST = '182.92.178.111'
REDIS_PORT = 6379
REDIS_PARAMS = {
    'password': '*********',
    'db': 11
    }

# 或者在解析器中加入
"""
custom_settings = {
    'LOG_LEVEL': 'DEBUG',
    'DOWNLOAD_DELAY': 1,

    # 指定redis数据库的连接参数
    'REDIS_HOST': '182.92.178.111',
    'REDIS_PORT': 6379,

    # 指定 redis链接密码,和使用哪一个数据库
    'REDIS_PARAMS' : {
        'password': '***************',
        'db': 11
    },

"""

# 如果要用到数据库的话
MYSQL_DB= {
    "host": "182.92.178.111",
    "database": "duanzi",
    "user": "root",
    "password": "*********",
    "port": 3306
}

qiushi.py

from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from scrapy_redis.spiders import RedisCrawlSpider

from duanzi.items import DuanziItem


class QiushiSpider(RedisCrawlSpider):
    name = 'qiushi'
    allowed_domains = ['qiushibaike.com']
    start_urls = ['https://www.qiushibaike.com/text/']
    redis_key = 'qiushi:start_urls'

    rules = (
        Rule(LinkExtractor(allow=r'/article/\d+'), callback='parse_item', follow=True),
        Rule(LinkExtractor(allow=r'/text/page/\d+'), callback='parse_item', follow=True),
    )

    def parse_item(self, response):
        item = {}
        item['title'] = response.xpath('//h1[@class="article-title"]//text()').get().strip()
        item['content'] = response.xpath('//div[@class="content"]//text()').get().strip()
        print(item)
        yield item

start.py

from scrapy.cmdline import execute
execute('scrapy crawl qiushi'.split())

这样就完成了,是不是很简单,其实我们还有一个最重要的事情没有做,现在思考下人生

我是谁。。。。。。

我从何处来。。。。。。

要往何处去。。。。。。

其实数据也是这样,既然是分布式,我多台机器上部署项目之后,项目会一直停在那

程序会去我们在settings.py中配置的redis数据库中去找**key = 'qiushi:start_urls'**的这个列表

这里插一嘴,Redis五大数据类型(字符串,集合,有序集合,列表,hash)

往redis列表中推数据

import redis

REDIS_HOST = '182.92.178.111'
REDIS_PORT = 6379
REDIS_PARAMS = {
    'password': '*********',
    'db': 11
    }
REDIS_KEY = 'qiushi:start_urls'
# 这里可以写很多的元素,后面大家可以自己去该写下
START_URLS = ['https://www.qiushibaike.com/text/']
my_redis = redis.Redis(host=REDIS_HOST, port=REDIS_PORT,
                 db=REDIS_PARAMS['db'], password=REDIS_PARAMS['password'])
for i in START_URLS:
    my_redis.lpush(REDIS_KEY,i)

上面的步骤完成之后,我们分别开启项目,项目应该会一直等待,因为我们没有运行我们的push.py文件

运行完.py文件之后,应该在我们的redis中看着数据库中有一个qiushi的文件夹,下面有一个key为qiushi:start_urls的列表

当我们的多个项目启动起来之后,会将qiushi:start_urls管道中的元素进行分发,我们会在redis中看到这样的元素

image-20210112215629982

qiushi:dupefilter:被调度器使用的url去重规则

qiushi:item:就是我们在爬虫类中解析出数据之后yield出来的字典结构的数据,现在存到的redis中,最后我们是要转移出来的

qiushi:start_urls:存放请求的地方,就是因为有这个,我们才可以实现“断点续抓”

将redis中抓取到的数据落地

数据是拿到了,但是现在都在redis中,如何永久保存呢,且看下面

MYSQL_DB= {
    "host": "182.92.178.111",
    "database": "duanzi",
    "user": "root",
    "password": "********",
    "port": 3306
}
conn = pymysql.connect(charset='utf8', **MYSQL_DB)
cursor = conn.cursor()
REDIS_DB_WIN = {
    "host": "localhost",
    "password": "*******",
    "db": 10
}
import redis
while True:
    my_redis = redis.Redis(**REDIS_DB_WIN)
    # 这样,就把redis中存储的数据取了出来
    title,content = my_redis.blpop(['qiushi:item'])
    
    # 存到mysql数据库
    sql = "insert into duanzi_info('title','content') values {}".format(item.values())
    cursor.execute(sql)
    cursor.commit()
    

创作不易,点赞评论加关注^_^!!!!