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脚本批量下载DesktopNexus壁纸的教程
May 06 Python
Python中map和列表推导效率比较实例分析
Jun 17 Python
利用python爬取斗鱼app中照片方法实例
Dec 03 Python
Python3一行代码实现图片文字识别的示例
Jan 15 Python
详解Python之unittest单元测试代码
Jan 24 Python
详解Python使用tensorflow入门指南
Feb 09 Python
完美解决Python 2.7不能正常使用pip install的问题
Jun 12 Python
Python实现随机漫步功能
Jul 09 Python
PyCharm搭建Spark开发环境实现第一个pyspark程序
Jun 13 Python
PyCharm中代码字体大小调整方法
Jul 29 Python
python isinstance函数用法详解
Feb 13 Python
python 实现压缩和解压缩的示例
Sep 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
php使用高斯算法实现图片的模糊处理功能示例
2016/11/11 PHP
JS backgroundImage控制
2009/05/19 Javascript
基于jQuery的倒计时实现代码
2012/05/30 Javascript
解析瀑布流布局:JS+绝对定位的实现
2013/05/08 Javascript
jQuery阻止事件冒泡具体实现
2013/10/11 Javascript
Javascript MVC框架Backbone.js详解
2014/09/18 Javascript
简述AngularJS相关的一些编程思想
2015/06/23 Javascript
JavaScript判断undefined类型的正确方法
2015/06/30 Javascript
JavaScript简单下拉菜单实例代码
2015/09/07 Javascript
用瀑布流的方式在网页上插入图片的简单实现方法
2016/09/23 Javascript
js实现倒计时关键代码
2017/05/05 Javascript
JS实现批量上传文件并显示进度功能
2017/06/27 Javascript
解决vue2.0路由跳转未匹配相应用路由避免出现空白页面的问题
2018/08/24 Javascript
小程序图片长按识别功能的实现方法
2018/08/30 Javascript
JS使用栈判断给定字符串是否是回文算法示例
2019/03/04 Javascript
elementUI 动态生成几行几列的方法示例
2019/07/11 Javascript
简单了解vue中的v-if和v-show的区别
2019/10/08 Javascript
JavaScript字符串转数字的简单实现方法
2020/11/27 Javascript
如何在JavaScript中使用localStorage详情
2021/02/04 Javascript
[03:33]TI9战队采访 - Infamous
2019/08/20 DOTA
在Lighttpd服务器中运行Django应用的方法
2015/07/22 Python
python实现生命游戏的示例代码(Game of Life)
2018/01/24 Python
Python使用numpy实现BP神经网络
2018/03/10 Python
Python redis操作实例分析【连接、管道、发布和订阅等】
2019/05/16 Python
在Pandas中处理NaN值的方法
2019/06/25 Python
python 画图 图例自由定义方式
2020/04/17 Python
python logging 重复写日志问题解决办法详解
2020/08/04 Python
洲际酒店集团英国官网:IHG英国
2019/07/10 全球购物
材料物理专业大学毕业生求职信
2013/10/15 职场文书
寄语十八大感言
2014/02/07 职场文书
党的群众路线教育实践活动宣传方案
2014/02/23 职场文书
竞选大队委员演讲稿
2014/04/28 职场文书
演讲比赛的活动方案
2014/08/28 职场文书
防灾减灾活动总结
2014/08/30 职场文书
2014年最新学校运动会广播稿
2014/09/17 职场文书
Jedis操作Redis实现模拟验证码发送功能
2021/09/25 Redis