Scrapy-Redis的学习与使用
1. Scrapy-Redis入门
1.1 基本概念
为什么要学习Scrapy-Redis?
第一个:了解工作流程(面试);
第二个:要求会把普通的爬虫改写成分布式爬虫。
集群:多个人在做同一件事;
分布式:多个人在做不同的事,协同完成任务,拥有更高的效率。
scrapy和scrapy-redis有什么区别?
- scrapy是爬虫的一个框架,爬取效率非常高,具有高度的可定制性,不支持分布式;
- scrapy-redis是基于redis数据库,运行在scrapy框架之上的一个组件,可以让scrapy支持分布式策略,支持主从同步。
scrapy-redis的工作流程:
scrapy-redis主要解决以下两个问题:
- 如何保证爬取数据不重复;
- 怎么把不同机器上的数据保存到统一的地方。
scrapy-redis在工作流程上与普通scrapy的不同:
- 引擎拿到爬取的url后给调度器,但是url的入列和去重校验交由redis执行;
- 爬虫文件处理完response后,原本交给管道的数据现存储到redis中。
综上,redis中存储爬取的数据、待爬取的request对象和爬取过的request对象,后两项保证爬取数据的不重复性。
1.2 安装Scrapy-Redis
在python中安装scrapy-redis:
pip install scrapy_redis -i https://pypi.douban.com/simple/
关于scrapy-redis的安装细节,比如python版本、redis版本等都有说明,这里不再赘述。
1.3 example-redis解析
github上有基于scrapy-redis框架的示例项目。
1 clone github scrapy_redis源码⽂件
2 git clone https://github.com/rolando/scrapy-redis.git
example-redis的文件目录结构:
example-redis中的爬虫文件与普通爬虫文件的不同点:
关于settings文件的变化:
运行example-redis项目,具体方法和普通scrapy项目一致。
在终端中验证保存的数据:
127.0.0.1:6379> keys *
1) "dmoz:requests" # 存放的是待爬取的requests对象,zset类型
2) "dmoz:items" # 爬取到的信息
3) "dmoz:dupefilter" # 爬取过的requests对象
127.0.0.1:6379> type dmoz:requests
zset
127.0.0.1:6379> type dmoz:dupefilter
set
1.4 分布式爬虫
观察myspider-redis.py文件:
如何把普通的爬虫文件改写成分布式爬虫文件?
普通的爬虫:
- scrapy项目/爬虫项目;
- 明确爬取的目标;
- 保存数据。
改写成分布式:
- 改写爬虫文件:
1.1 导入模块;
1.2 继承类;
1.3 把start_urls --> redis_key。 - 改写配置文件。
domz爬虫的数据是怎么存储到数据库?
连接数据库在StrictRedis类的init函数中实现(源码中的defaults.py)。我们只需在settings文件中打开管道,在爬虫文件中yield对应的数据即可保存到本地数据库。
2. 案例演示
案例链接:http://book.dangdang.com/
爬取当当网图书分类,假定“特色书单”为大分类,里面的“童书”、“文艺”等为中分类,童书里面的“儿童国际大奖书单来了!”、“中关村三小推荐阅读书单”等为小分类。
需求:获取大中小三级分类以及图片的名字以及图片的地址。
2.1 页面分析
大分类在 div class="con flq_body"
下面class="level_one"/dl/dt/span
文本:
注意:span标签在网页源码中没有,有的大标题是多个,通过//
找到所有的需要用extract()
。
中分类的内容都在div class="inner_dl"/dt
,小分类的内容都在div class="inner_dl"/dd/a
:
注意:中分类在网页源码中的标签与element中一致。
小分类详情页面的结构:
想要爬取的是图书的名字以及对应的图片,所有的数据都是在ul
标签下面的li
中。
注意,在网页的源码当中src = 'images/model/guan/url_none.png'
与我们希望的图片url不符,这里只能找data-original
.
注意:每次爬取数据之前,要检查网页源码和elements选项当中的数据是否一致。
2.2 编写代码
改写程序
- 改写爬虫文件
1.1 导入模块
1.2 继承类
1.3 把start_urs --> redis_key - 改写配置文件
创建相应的scrapy项目,爬虫文件如下:
import scrapy
from copy import deepcopy
from scrapy_redis.spiders import RedisSpider
class DdSpider(RedisSpider):
name = 'dd'
allowed_domains = ['dangdang.com']
# start_urls = ['http://book.dangdang.com/']
redis_key = 'dd'
def parse(self, response):
# 获取所有div标签
div_list = response.xpath('//div[@class="con flq_body"]/div')
for div in div_list:
item = {}
# 获取大分类名字
item['b_cate'] = div.xpath('./dl/dt//text()').extract()
if item['b_cate']:
item['b_cate'] = [i.strip() for i in item['b_cate'] if len(i.strip()) > 0]
item['b_cate'] = ''.join(item['b_cate'])
# print(item)
dl_list = div.xpath('.//dl[@class="inner_dl"]')
for dl in dl_list:
# 获取中分类名字
item['m_cate'] = dl.xpath('./dt//text()').extract()
if item['m_cate']:
item['m_cate'] = [i.strip() for i in item['m_cate'] if len(i.strip()) > 0]
item['m_cate'] = item['m_cate'][0]
# print(item['m_cate'])
# 获取下分类
a_list = dl.xpath('./dd/a')
for a in a_list:
item['s_cate'] = a.xpath('./text()').extract_first()
# 获取列表页面url
item['s_href'] = a.xpath('./@href').extract_first()
if item['s_href']:
yield scrapy.Request(
url=item['s_href'],
callback=self.parse_book_list,
meta={'item': deepcopy(item)}
)
# print(item)
def parse_book_list(self, response):
item = response.meta.get('item')
# 获取小分类详情页中的li标签
li_list = response.xpath('//ul[@class="list_aa "]/li')
for li in li_list:
# 获取图片的url
item['book_img'] = li.xpath('./a[@class="img"]/img/@data-original').extract_first()
# 获取图片的名字
item['book_name'] = li.xpath('./p[@class="name"]/a/@title').extract_first()
print(item)
yield item
settings文件:
BOT_NAME = 'dangdang'
SPIDER_MODULES = ['dangdang.spiders']
NEWSPIDER_MODULE = 'dangdang.spiders'
# LOG_LEVEL = 'WARNING'
# 去重过滤
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 指定Scheduler队列
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
SCHEDULER_PERSIST = True
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
DOWNLOAD_DELAY = 1
# Override the default request headers:
DEFAULT_REQUEST_HEADERS = {
'User_Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
}
ITEM_PIPELINES = {
'book.pipelines.BookPipeline': 300,
'scrapy_redis.pipelines.RedisPipeline': 400,
}
此时爬虫程序进入一个在阻塞状态,我们需要在redis中将start_url添加到相应的key;
在爬虫文件里面千万不要忘记yield数据。