基于 Scrapy-redis 两种形式的分布式爬虫
基于 Scrapy-redis 两种形式的分布式爬虫
redis 分布式部署
1、scrapy 框架是否可以自己实现分布式?
- 答:不可以,原因有二:
- 其一: 因为多台机器上部署的 Scrapy 会各自拥有各自的调度器,这样就使得多台机器无法分配 start_url 列表中的url, (多台机器无法共享同一个调度器
- 其二: 多台机器爬取到的数据无法通过同一个管道对数据进行统一的数据持久化存储。(多台机器无法共享同一个管道)
2、基于 Scrapy-redis 组件的分布式爬虫
- scrapy-redis 组件中为我们封装好了可以被多台机器共享的调度器和管道,我们可以直接使用并实现分布式数据取。
- 实现方式:
- 2.1、基于该组件的 RedisSpider 类
- 2.2、基于该组件的 RedisCrawlSpider 类
3、分布式实现流程: 上述两种不同方式的分布式实现流程是统一的
- 3.1、pip下载安装 scrapy-redis 模块:
In [ ]:
pip3 install scrapy-redis
- 3.2 创建爬虫 项目 和 爬虫程序文件 命令:
In [ ]:
scrapy startproject redisChoutiPro # 创建 爬虫 工程
scrapy genspider -t crawl www.xxx.com # 创建 爬虫 程序文件
- 3.2.1 项目配置文件 settings.py 基本配置:
In [ ]:
#1、项目名称,默认的USER_AGENT由它来构成,也作为日志记录的日志名 默认
BOT_NAME = 'redisChoutiPro'
# 2、爬虫应用路径 默认
SPIDER_MODULES = ['redisChoutiPro.spiders']
NEWSPIDER_MODULE = 'redisChoutiPro.spiders'
#3、客户端User-Agent请求头 # 手动添加
USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36'
#4、是否遵循爬虫协议
ROBOTSTXT_OBEY = False # 由 True 该为 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',
#}
- 3.3、对爬虫程序 文件中的相关属性进行修改
In [ ]:
# 1. 导包:from scrapy_redis.spiders import RedisCrawlSpider
# 将爬虫类的父类修改成基于RedisSpider或者RedisCrawlSpider。
# 注意:如果原始爬虫文件是基于Spider的,则应该将父类修改成RedisSpider,
# 如果原始爬虫文件是基于CrawlSpider的,则应该将其父类修改成RedisCrawlSpider
# 2.将当前爬虫文件的父类设置成RedisCrawlSpider
# 3.注释或者删除 start_urls 列表属性 和 allowed_domains 列表属性。
# 4.将起始url列表替换成 redis_key = 'xxx'(调度器队列的名称)属性值为scrpy-redis组件中调度器队列的名称
- 3.4、redis 配置文件的配置
In [ ]:
- 取消保护模式:protected-mode no , 将 yes 改为 no<br>
- 注释 bind 绑定: #bind 127.0.0.1 # 表示 可以让其他 IP 访问 Redis
- 3.5、在配置文件中进行相关配置,开启使用scrapy-redis组件中封装好的管道
In [4]:
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 400
}
- 3.6 在配置文件中进行相关配置,开启使用scrapy-redis组件中封装好的调度器
In [ ]:
# 配置调度器(使用组件中封装好的可以被共享的调度器)
# 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 使用scrapy-redis组件自己的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据
SCHEDULER_PERSIST = True # 数据指纹
- 3.7 在配置文件中进行爬虫程序链接redis的配置:
In [ ]:
REDIS_HOST = 'redis服务的ip地址'
REDIS_PORT = 6379
REDIS_ENCODING = 'utf-8'
REDIS_PARAMS = {'password': '123456'}
- 3.8 开启redis服务器端:redis-server 配置文件: 命令:
In [ ]:
redis-server.exe redis.windows.conf
- 3.9 开启redis客户端:redis-cli: 命令:
In [ ]:
# 客户端: redis-cli.exe:
"""
keys * : 显示 db 中 所有的 数据
flushall : 清空所有 key
"""
- 3.10 cd 爬虫程序目录: 运行爬虫文件:
In [ ]:
scrapy runspider 爬虫程序文件名
- 3.11 向调度器队列中扔入一个起始url(在redis客户端中操作):
In [ ]:
lpush redis_key属性值 起始页面 url
项目案例 一、¶
基于该组件的RedisCrawlSpider类爬虫
需求: 爬取<抽屉新热榜>中的和作者数据
爬虫文件:¶
In [ ]:
# -*- coding: utf-8 -*-
# chouti.py
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from scrapy_redis.spiders import RedisCrawlSpider
from redisChoutiPro.items import RedischoutiproItem
class ChoutiSpider(RedisCrawlSpider): # 将继承类 CrawlSpider 改为 RedisCrawlSpider
name = 'chouti' # 爬虫名
# allowed_domains = ['www.xxx.com'] # 允许爬取的 域名, 分布式注释此属性
# start_urls = ['https://dig.chouti.com//'] # 向 redis 的列表中扔入的 URL
redis_key = 'chouti' # 共享 调度器 队列 的名称
# 规则解析器
allow = LinkExtractor(allow=r'/all/hot/recent/d+')
"""
link_extractor: 分页的 URL 的 正则匹配 提取规则
callback: 回调函数
follow: 是否让 链接解析器 迭代提取链接
"""
# 链接解析器
rules = (
Rule(link_extractor=allow, callback='parse_item', follow=True),
)
def parse_item(self, response):
div_list = response.xpath('//div[@class="item"]')
for div in div_list:
item = RedischoutiproItem()
content = div.xpath('./div[4]/div[1]/a/text()').extract_first()
author = div.xpath('./div[4]/div[2]/a[4]/b/text()').extract_first()
item['content'] = content
item['author'] = author
yield item # 默认是 原生的 Scrapy 管道,
管道文件¶
In [ ]:
# -*- coding: utf-8 -*-
import scrapy
class IncrementDataItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
content = scrapy.Field()
author = scrapy.Field()
爬虫项目 配置文件 settings.py¶
In [ ]:
# -*- coding: utf-8 -*-
#==>第一部分: 基本配置<===
#1、项目名称,默认的USER_AGENT由它来构成,也作为日志记录的日志名
BOT_NAME = 'redisChoutiPro'
# 2、爬虫应用路径
SPIDER_MODULES = ['redisChoutiPro.spiders']
NEWSPIDER_MODULE = 'redisChoutiPro.spiders'
#3、客户端User-Agent请求头
USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36'
#4、是否遵循爬虫协议
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',
#}
####第二部分 分布式 配置
# 添加 开启使用scrapy-redis组件中封装好的 共享管道
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 400
}
# 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # 指定过滤器
# 使用scrapy-redis组件自己的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据
SCHEDULER_PERSIST = True #可有可无, 数据指纹 ,对数据的唯一标识,涉及到增量式,
# 若 分布式集群 荡机 了,当再次重启继续执行分布式爬虫代码时,接着从荡机之前的位置开始爬,
# 只会爬 没有经过爬取的数据和最近更新的数据,
# 如何区分 数据是否 爬过:
# 把 爬过的数据进行数据指纹 的设置
# 指定存储数据的 redis
REDIS_HOST = "127.0.0.1"
REDIS_PORT = 6379
执行代码:¶
In [ ]:
scrapy startproject redisChoutiPro # 创建 爬虫 工程
scrapy genspider -t crawl www.xxx.com # 创建 爬虫 程序文件
redis-server.exe redis.windows.conf # 运行 redis 服务端
redis-cli.exe # 运行 redis 客户端
scrapy runspider chouti.py # 运行 爬虫程序
lpush chouti https://dig.chouti.com/ # 向 共享调度器队列 中添加 起始 URL
项目案例 二、
基于该组件的RedisSpider类爬虫
需求: 爬取 <医药魔方>数据
爬虫文件
In [ ]:
# -*- coding: utf-8 -*-
import scrapy
from scrapy_redis.spiders import RedisSpider
from redisSpider_Pro.items import RedisspiderProItem
class RedisspidertestSpider(RedisSpider):
name = 'redisSpiderTest'
# allowed_domains = ['www.xxx.com']
# start_urls = ['http://www.xxx.com/']
redis_key = 'data' # 调度器队列的名称
url = 'http://db.pharmcube.com/'
pageNum = 1
def parse(self, response):
num = response.xpath('/html/body/div/table/tbody/tr[1]/td[2]/text()').extract_first()
name = response.xpath('/html/body/div/table/tbody/tr[2]/td[2]/text()').extract_first()
item = RedisspiderProItem()
item['num'] = num
item['name'] = name
yield item
if self.pageNum <= 10000:
self.pageNum += 1
new_url = self.url + str(self.pageNum)
yield scrapy.Request(url=new_url, callback=self.parse)
项目配置文件: settings.py
In [ ]:
# -*- coding: utf-8 -*-
BOT_NAME = 'redisSpider_Pro'
SPIDER_MODULES = ['redisSpider_Pro.spiders']
NEWSPIDER_MODULE = 'redisSpider_Pro.spiders'
# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'redisSpider_Pro (+http://www.yourdomain.com)'
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36'
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
#管道
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 400
}
# 使用scrapy-redis组件的去重队列
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 使用scrapy-redis组件自己的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 是否允许暂停
SCHEDULER_PERSIST = True
#redis连接数据库的参数
REDIS_HOST = '127.0.0.1'
REDIS_PORT = 6379
#编码格式
# REDIS_ENCODING = ‘utf-8’
#用户名密码
# REDIS_PARAMS = {‘password’:’123456’}