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读写excel的方法
Nov 18 Python
Python随手笔记第一篇(2)之初识列表和元组
Jan 23 Python
解决Tensorflow安装成功,但在导入时报错的问题
Jun 13 Python
python用pandas数据加载、存储与文件格式的实例
Dec 07 Python
对python中Json与object转化的方法详解
Dec 31 Python
python 堆和优先队列的使用详解
Mar 05 Python
Python3.5装饰器原理及应用实例详解
Apr 30 Python
浅谈pyqt5在QMainWindow中布局的问题
Jun 21 Python
django框架事务处理小结【ORM 事务及raw sql,customize sql 事务处理】
Jun 27 Python
解决python ThreadPoolExecutor 线程池中的异常捕获问题
Apr 08 Python
python利用appium实现手机APP自动化的示例
Jan 26 Python
使用Selenium实现微博爬虫(预登录、展开全文、翻页)
Apr 13 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
Linux下ZendOptimizer的安装与配置方法
2007/04/12 PHP
PHP判断文章里是否有图片的简单方法
2014/07/26 PHP
深入理解PHP中mt_rand()随机数的安全
2017/10/12 PHP
关于Laravel-admin的基础用法总结和自定义model详解
2019/10/08 PHP
Thinkphp5.0 框架Model模型简单用法分析
2019/10/11 PHP
Laravel实现通过blade模板引擎渲染视图
2019/10/25 PHP
禁止你的左键复制实用技巧
2013/01/04 Javascript
JS中数组Array的用法示例介绍
2014/02/20 Javascript
nodejs 实现模拟form表单上传文件
2014/07/14 NodeJs
JavaScript常用验证函数实例汇总
2014/11/25 Javascript
用svg制作富有动态的tooltip
2015/07/17 Javascript
jquery validate和jquery form 插件组合实现验证表单后AJAX提交
2015/08/26 Javascript
JavaScript学习笔记之检测客户端类型是(引擎、浏览器、平台、操作系统、移动设备)
2015/12/03 Javascript
JS实现设置ff与ie元素绝对位置的方法
2016/03/08 Javascript
JS遍历页面所有对象属性及实现方法
2016/08/01 Javascript
JS 拦截全局ajax请求实例解析
2016/11/29 Javascript
Vue.js进行查询操作的实例详解
2017/08/25 Javascript
详解Vue中watch的高级用法
2018/05/02 Javascript
如何用webpack4带你实现一个vue的打包的项目
2018/06/20 Javascript
解决小程序无法触发SESSION问题
2020/02/03 Javascript
解决vue-router路由拦截造成死循环问题
2020/08/05 Javascript
[39:11]DOTA2上海特级锦标赛C组资格赛#2 LGD VS Newbee第二局
2016/02/28 DOTA
详解MySQL数据类型int(M)中M的含义
2016/11/20 Python
Python 查找字符在字符串中的位置实例
2018/05/02 Python
python实现指定字符串补全空格、前面填充0的方法
2018/11/16 Python
python使用pipeline批量读写redis的方法
2019/02/18 Python
Django框架实现分页显示内容的方法详解
2019/05/10 Python
医学专业大学生求职信
2014/07/12 职场文书
入股协议书范本
2014/11/01 职场文书
工作岗位职责范本
2015/02/15 职场文书
销售会议开幕词
2016/03/04 职场文书
2016年社区创先争优活动总结
2016/04/05 职场文书
2016年百日安全生产活动总结
2016/04/06 职场文书
2019年年中职场激励人心语录30条
2019/08/07 职场文书
Python实现抖音热搜定时爬取功能
2022/03/16 Python
鸿蒙3.0体验感怎么样? 鸿蒙3.0系统评测向
2022/08/14 数码科技