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输出PowerPoint(ppt)文件中全部文字信息的方法
Apr 28 Python
python生成IP段的方法
Jul 07 Python
Python备份目录及目录下的全部内容的实现方法
Jun 12 Python
Python正规则表达式学习指南
Aug 02 Python
Python中遇到的小问题及解决方法汇总
Jan 11 Python
Python基于递归实现电话号码映射功能示例
Apr 13 Python
使用OpenCV-python3实现滑动条更新图像的Canny边缘检测功能
Dec 12 Python
pytorch GAN伪造手写体mnist数据集方式
Jan 10 Python
django queryset相加和筛选教程
May 18 Python
Python操作Elasticsearch处理timeout超时
Jul 17 Python
Python中Permission denied的解决方案
Apr 02 Python
python实现简易名片管理系统
Apr 11 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
比较strtr, str_replace和preg_replace三个函数的效率
2013/06/26 PHP
PHP swfupload图片上传的实例代码
2013/09/30 PHP
php遍历CSV类实例
2015/04/14 PHP
PHP基本语法实例总结
2016/09/09 PHP
关于php中的json_encode()和json_decode()函数的一些说明
2016/11/20 PHP
ThinkPHP3.2.3框架邮件发送功能图文实例详解
2019/04/23 PHP
laravel框架语言包拓展实现方法分析
2019/11/22 PHP
JavaScript 设计模式学习 Factory
2009/07/29 Javascript
初识Node.js
2014/09/03 Javascript
jQuery中dom元素上绑定的事件详解
2015/04/24 Javascript
NodeJS实现不可逆加密与密码密文保存的方法
2018/03/16 NodeJs
微信小程序表单验证form提交错误提示效果
2020/06/19 Javascript
VUE中v-on:click事件中获取当前dom元素的代码
2018/08/22 Javascript
vue实现单一筛选、删除筛选条件
2020/10/26 Javascript
vue项目如何监听localStorage或sessionStorage的变化
2021/01/04 Vue.js
详解uniapp的全局变量实现方式
2021/01/11 Javascript
[48:12]Secret vs Optic Supermajor 胜者组 BO3 第三场 6.4
2018/06/05 DOTA
Python os模块中的isfile()和isdir()函数均返回false问题解决方法
2015/02/04 Python
Python实现PS图像抽象画风效果的方法
2018/01/23 Python
15行Python代码带你轻松理解令牌桶算法
2018/03/21 Python
pandas 数据实现行间计算的方法
2018/06/08 Python
python实现简单名片管理系统
2018/11/30 Python
把JSON数据格式转换为Python的类对象方法详解(两种方法)
2019/06/04 Python
QML使用Python的函数过程解析
2019/09/26 Python
python设置环境变量的作用整理
2020/02/17 Python
Pytorch 使用opnecv读入图像由HWC转为BCHW格式方式
2020/06/02 Python
python中count函数知识点浅析
2020/12/17 Python
python3.9.1环境安装的方法(图文)
2021/02/02 Python
使用HTML5原生对话框元素并轻松创建模态框组件
2019/03/06 HTML / CSS
html5小程序飞入购物车(抛物线绘制运动轨迹点)
2020/10/19 HTML / CSS
微软日本官方网站:Microsoft日本
2017/11/26 全球购物
阿迪达斯香港官网:adidas香港
2019/11/09 全球购物
The Outnet亚太地区:折扣设计师时装店
2019/12/05 全球购物
Prototype如何更新局部页面
2013/03/03 面试题
外语系毕业生自荐信范文
2013/12/16 职场文书
工作简历的自我评价
2019/05/16 职场文书