Scrapy-Redis之RedisSpider与RedisCrawlSpider详解


Posted in Python onNovember 18, 2020

在上一章《Scrapy-Redis入门实战》中我们利用scrapy-redis实现了京东图书爬虫的分布式部署和数据爬取。但存在以下问题:

每个爬虫实例在启动的时候,都必须从start_urls开始爬取,即每个爬虫实例都会请求start_urls中的地址,属重复请求,浪费系统资源。

为了解决这一问题,Scrapy-Redis提供了RedisSpider与RedisCrawlSpider两个爬虫类,继承自这两个类的Spider在启动的时候能够从指定的Redis列表中去获取start_urls;任意爬虫实例从Redis列表中获取某一 url 时会将其从列表中弹出,因此其他爬虫实例将不能重复读取该 url ;对于那些未从Redis列表获取到初始 url 的爬虫实例将一直处于阻塞状态,直到 start_urls列表中被插入新的起始地址或者Redis的Requests列表中出现待处理的请求。

在这里,我们以爬取当当网图书信息为例对这两个Spider的用法进行简单示例。

settings.py 配置如下:

# -*- coding: utf-8 -*-

BOT_NAME = 'dang_dang'

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


# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'

# Obey robots.txt rules
ROBOTSTXT_OBEY = False


######################################################
##############下面是Scrapy-Redis相关配置################
######################################################

# 指定Redis的主机名和端口
REDIS_HOST = 'localhost'
REDIS_PORT = 6379

# 调度器启用Redis存储Requests队列
SCHEDULER = "scrapy_redis.scheduler.Scheduler"

# 确保所有的爬虫实例使用Redis进行重复过滤
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

# 将Requests队列持久化到Redis,可支持暂停或重启爬虫
SCHEDULER_PERSIST = True

# Requests的调度策略,默认优先级队列
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue'

# 将爬取到的items保存到Redis 以便进行后续处理
ITEM_PIPELINES = {
  'scrapy_redis.pipelines.RedisPipeline': 300
}

RedisSpider代码示例

# -*- coding: utf-8 -*-
import scrapy
import re
import urllib
from copy import deepcopy
from scrapy_redis.spiders import RedisSpider


class DangdangSpider(RedisSpider):
  name = 'dangdang'
  allowed_domains = ['dangdang.com']
  redis_key = 'dangdang:book'
  pattern = re.compile(r"(http|https)://category.dangdang.com/cp(.*?).html", re.I)

  # def __init__(self, *args, **kwargs):
  #   # 动态定义可爬取的域范围
  #   domain = kwargs.pop('domain', '')
  #   self.allowed_domains = filter(None, domain.split(','))
  #   super(DangdangSpider, self).__init__(*args, **kwargs)

  def parse(self, response): # 从首页提取图书分类信息
    # 提取一级分类元素
    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()
      item["b_cate"] = [i.strip() for i in item["b_cate"] if len(i.strip()) > 0]
      # 提取二级分类元素
      dl_list = div.xpath("./div//dl[@class='inner_dl']")
      for dl in dl_list:
        item["m_cate"] = dl.xpath(".//dt/a/@title").extract_first()
        # 提取三级分类元素
        a_list = dl.xpath("./dd/a")
        for a in a_list:
          item["s_cate"] = a.xpath("./text()").extract_first()
          item["s_href"] = a.xpath("./@href").extract_first()
          if item["s_href"] is not None and self.pattern.match(item["s_href"]) is not None:
            yield scrapy.Request(item["s_href"], callback=self.parse_book_list,
                       meta={"item": deepcopy(item)})

  def parse_book_list(self, response): # 从图书列表页提取数据
    item = response.meta['item']
    li_list = response.xpath("//ul[@class='bigimg']/li")
    for li in li_list:
      item["book_img"] = li.xpath("./a[@class='pic']/img/@src").extract_first()
      if item["book_img"] == "images/model/guan/url_none.png":
        item["book_img"] = li.xpath("./a[@class='pic']/img/@data-original").extract_first()
      item["book_name"] = li.xpath("./p[@class='name']/a/@title").extract_first()
      item["book_desc"] = li.xpath("./p[@class='detail']/text()").extract_first()
      item["book_price"] = li.xpath(".//span[@class='search_now_price']/text()").extract_first()
      item["book_author"] = li.xpath("./p[@class='search_book_author']/span[1]/a/text()").extract_first()
      item["book_publish_date"] = li.xpath("./p[@class='search_book_author']/span[2]/text()").extract_first()
      if item["book_publish_date"] is not None:
        item["book_publish_date"] = item["book_publish_date"].replace('/', '')
      item["book_press"] = li.xpath("./p[@class='search_book_author']/span[3]/a/text()").extract_first()
      yield deepcopy(item)

    # 提取下一页地址
    next_url = response.xpath("//li[@class='next']/a/@href").extract_first()
    if next_url is not None:
      next_url = urllib.parse.urljoin(response.url, next_url)
      yield scrapy.Request(next_url, callback=self.parse_book_list, meta={"item": item})

当Redis 的dangdang:book键所对应的start_urls列表为空时,启动DangdangSpider爬虫会进入到阻塞状态等待列表中被插入数据,控制台提示内容类似下面这样:

2019-05-08 14:02:53 [scrapy.core.engine] INFO: Spider opened
2019-05-08 14:02:53 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2019-05-08 14:02:53 [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6023

此时需要向start_urls列表中插入爬虫的初始爬取地址,向Redis列表中插入数据可使用如下命令:

lpush dangdang:book http://book.dangdang.com/

命令执行完后稍等片刻DangdangSpider便会开始爬取数据,爬取到的数据结构如下图所示:

Scrapy-Redis之RedisSpider与RedisCrawlSpider详解

RedisCrawlSpider代码示例

# -*- coding: utf-8 -*-
import scrapy
import re
import urllib
from copy import deepcopy
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
from scrapy_redis.spiders import RedisCrawlSpider


class DangdangCrawler(RedisCrawlSpider):
  name = 'dangdang2'
  allowed_domains = ['dangdang.com']
  redis_key = 'dangdang:book'
  pattern = re.compile(r"(http|https)://category.dangdang.com/cp(.*?).html", re.I)

  rules = (
    Rule(LinkExtractor(allow=r'(http|https)://category.dangdang.com/cp(.*?).html'), callback='parse_book_list',
       follow=False),
  )

  def parse_book_list(self, response): # 从图书列表页提取数据
    item = {}
    item['book_list_page'] = response._url
    li_list = response.xpath("//ul[@class='bigimg']/li")
    for li in li_list:
      item["book_img"] = li.xpath("./a[@class='pic']/img/@src").extract_first()
      if item["book_img"] == "images/model/guan/url_none.png":
        item["book_img"] = li.xpath("./a[@class='pic']/img/@data-original").extract_first()
      item["book_name"] = li.xpath("./p[@class='name']/a/@title").extract_first()
      item["book_desc"] = li.xpath("./p[@class='detail']/text()").extract_first()
      item["book_price"] = li.xpath(".//span[@class='search_now_price']/text()").extract_first()
      item["book_author"] = li.xpath("./p[@class='search_book_author']/span[1]/a/text()").extract_first()
      item["book_publish_date"] = li.xpath("./p[@class='search_book_author']/span[2]/text()").extract_first()
      if item["book_publish_date"] is not None:
        item["book_publish_date"] = item["book_publish_date"].replace('/', '')
      item["book_press"] = li.xpath("./p[@class='search_book_author']/span[3]/a/text()").extract_first()
      yield deepcopy(item)

    # 提取下一页地址
    next_url = response.xpath("//li[@class='next']/a/@href").extract_first()
    if next_url is not None:
      next_url = urllib.parse.urljoin(response.url, next_url)
      yield scrapy.Request(next_url, callback=self.parse_book_list)

 与DangdangSpider爬虫类似,DangdangCrawler在获取不到初始爬取地址时也会阻塞在等待状态,当start_urls列表中有地址即开始爬取,爬取到的数据结构如下图所示:

Scrapy-Redis之RedisSpider与RedisCrawlSpider详解

到此这篇关于Scrapy-Redis之RedisSpider与RedisCrawlSpider详解的文章就介绍到这了,更多相关Scrapy-Redis之RedisSpider与RedisCrawlSpider内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
Python 列表list使用介绍
Nov 30 Python
Python中AND、OR的一个使用小技巧
Feb 18 Python
Python浅拷贝与深拷贝用法实例
May 09 Python
Python3中的2to3转换工具使用示例
Jun 12 Python
python实现贪吃蛇游戏
Mar 21 Python
python字典setdefault方法和get方法使用实例
Dec 25 Python
Python基础之列表常见操作经典实例详解
Feb 26 Python
PyTorch中model.zero_grad()和optimizer.zero_grad()用法
Jun 24 Python
Python OpenCV读取中文路径图像的方法
Jul 02 Python
利用scikitlearn画ROC曲线实例
Jul 02 Python
Python3中小括号()、中括号[]、花括号{}的区别详解
Nov 15 Python
Python中非常使用的6种基本变量的操作与技巧
Mar 22 Python
详解Scrapy Redis入门实战
Nov 18 #Python
如何在scrapy中集成selenium爬取网页的方法
Nov 18 #Python
Python 实现键盘鼠标按键模拟
Nov 18 #Python
如何向scrapy中的spider传递参数的几种方法
Nov 18 #Python
python更新数据库中某个字段的数据(方法详解)
Nov 18 #Python
Python下载的11种姿势(小结)
Nov 18 #Python
Python监听键盘和鼠标事件的示例代码
Nov 18 #Python
You might like
codeigniter集成ucenter1.6双向通信的解决办法
2014/06/12 PHP
PHP中的访问修饰符简单比较
2019/02/02 PHP
laravel 修改记住我功能的cookie保存时间的方法
2019/10/14 PHP
jquery 问答知识整理
2010/02/11 Javascript
JS分页控件 可用于无刷新分页
2013/07/23 Javascript
浅谈Javascript 执行顺序
2013/12/18 Javascript
Javascript基础知识(二)事件
2014/09/29 Javascript
浅谈Node.js中的定时器
2015/06/18 Javascript
JS实现的倒计时效果实例(2则实例)
2015/12/23 Javascript
jQuery 常用代码集锦(必看篇)
2016/05/16 Javascript
textarea 在浏览器中固定大小和禁止拖动的实现方法
2016/12/03 Javascript
探究JavaScript中的五种事件处理程序方式
2016/12/07 Javascript
详解nodejs微信公众号开发——5.素材管理接口
2017/04/11 NodeJs
vue axios基于常见业务场景的二次封装的实现
2018/09/21 Javascript
原生JS实现相邻月份日历
2020/10/13 Javascript
Vue router安装及使用方法解析
2020/12/02 Vue.js
[02:00]DOTA2英雄COSPLAY闹市街头巡游助威2015国际邀请赛
2015/08/02 DOTA
[33:33]完美世界DOTA2联赛PWL S2 FTD.C vs SZ 第二场 11.27
2020/11/30 DOTA
Python实现身份证号码解析
2015/09/01 Python
python中is与双等于号“==”的区别示例详解
2017/11/21 Python
在Keras中实现保存和加载权重及模型结构
2020/06/15 Python
python Matplotlib数据可视化(2):详解三大容器对象与常用设置
2020/09/30 Python
基于Python的身份证验证识别和数据处理详解
2020/11/14 Python
美国奢侈品在线团购网站:Gilt City
2017/11/16 全球购物
CK巴西官方网站:Calvin Klein巴西
2019/07/19 全球购物
采购主管的岗位职责
2013/12/17 职场文书
优秀的计算机专业求职信范文
2013/12/27 职场文书
常务副总经理岗位职责
2014/04/12 职场文书
2014年班组长工作总结
2014/11/20 职场文书
2015年秘书个人工作总结
2015/04/25 职场文书
计划生育目标责任书
2015/05/09 职场文书
少先队工作总结2015
2015/05/13 职场文书
2016元旦晚会主持词
2015/07/01 职场文书
全家福照片寄语怎么写?
2019/04/02 职场文书
2019朋友新婚祝福语精选
2019/10/10 职场文书
Python实现Matplotlib,Seaborn动态数据图
2022/05/06 Python