爬虫 — Scrapy-Redis

news2024/11/27 1:31:07

目录

  • 一、背景
    • 1、数据库的发展历史
    • 2、NoSQL 和 SQL 数据库的比较
  • 二、Redis
    • 1、特性
    • 2、作用
    • 3、应用场景
    • 4、用法
    • 5、安装及启动
    • 6、Redis 数据库简单使用
    • 7、Redis 常用五大数据类型
      • 7.1 Redis-String
      • 7.2 Redis-List (单值多value)
      • 7.3 Redis-Hash
      • 7.4 Redis-Set (不重复的)
      • 7.5 Redis-Zset (有序集合)
    • 8、Python 操作 Redis
      • 8.1、安装
      • 8.2、导入 Redis 模块和创建连接
      • 8.3、字符串相关操作
      • 8.4、列表相关操作
      • 8.5、集合相关操作
      • 8.6、哈希相关操作
      • 8.7、有序集合相关操作
      • 8.8、连接 Redis 数据库
  • 三、Scrapy-分布式
    • 1、启动 Redis
    • 2、Scrapy-Redis 简介
    • 3、Scrapy 工作流程
    • 4、Scrapy-Redis 工作流程
    • 5、github 示例代码
    • 6、Scrapy-Redis 中的 settings 文件
    • 7、Scrapy-Redis 运行
  • 四、案例
    • 1、页面分析
    • 2、使用 Scrapy 框架实现
    • 3、改写成 Scrapy-Redis
    • 4、改写分布式总结

一、背景

随着互联网+大数据时代的来临,传统的关系型数据库已经不能满足中大型网站日益增长的访问量和数据量。这个时候就需要⼀种能够快速存取数据的组件来缓解数据库服务 I/O 的压力,来解决系统性能上的瓶颈。

1、数据库的发展历史

1、在互联网+大数据时代来临之前,企业的一些内部信息管理系统,一个单个数据库实例就能满足系统的需求。

2、随着系统访问用户的增多,数据量的增大,单个数据库实例已经无法满足系统的读取需求,采用缓存(memcache)+单数据库实例

3、缓存可以缓解系统的读取压力,但是数据量的写入压力持续增大,使用缓存+主从数据库+读写分离

4、数据量再次增大,读写分离以后,主数据库的写库压力出现瓶颈,使用缓存+主从数据库集群+读写分离+分库分表

5、互联网+大数据时代来临,关系型数据库不能很好地存取一些并发性高、实时性高,并且数据格式不固定的数据,采用 NoSQL +主从数据库集群+读写分离+分库分表

2、NoSQL 和 SQL 数据库的比较

1、适用场景不同:SQL 数据库适合用于关系特别复杂的数据查询场景,NoSQL 则相反。

2、事务:SQL 对事务的支持非常完善,而 NoSQL 基本不支持事务。

3、两者在不断的取长补短。

二、Redis

是⼀个高性能的,开源的,C 语言开发的,键值对存储数据的 nosql(not only sql,泛指非关系型数据库

) 数据库。

点击阅读官方文档

1、特性

Redis 支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。

Redis 不仅仅支持简单的 key-value 类型的数据,同时还提供 List、Set 等数据类型。

Redis 支持数据的备份。

2、作用

快速存取。

3、应用场景

点赞、秒杀、直播平台的在线好友列表、商品排行榜等。

4、用法

官方地址:https://redis.io/

命令地址:http://doc.redisfans.com/

5、安装及启动

查看帮助命令:

redis-server --help

启动服务:

redis-server.exe

连接客户端:

redis-cli.exe

6、Redis 数据库简单使用

DBSIZE 查看当前数据库的 key 数量
KEYS * 查看所有 key 的内容
FLUSHDB 清空当前数据库的所有 key
FLUSHALL 清空所有库的所有 key(谨慎使用)
EXISTS key 判断 key 是否存在

7、Redis 常用五大数据类型

7.1 Redis-String

Redis 中的 String最基本的数据类型,一个 key 对应一个 value。String 可以包含任何数据,但最大不能超过 512M。

常用操作

  • SET:设置值
  • GET:获取值
  • MSET:设置多个值
  • MGET:获取多个值
  • APPEND:添加字段
  • DEL:删除
  • STRLEN:返回字符串长度

数值操作

  • INCR:增加
  • DECR:减少
  • INCRBY:指定增加多少
  • DECRBY:指定减少多少

范围操作

  • GETRANGE:获取指定区间范围内的值,类似 SQL 中的 BETWEEN … AND …
  • SETRANGE:从指定位置开始替换,索引从 0 开始,从 0 到 -1 表示全部

7.2 Redis-List (单值多value)

Redis 的 List 类型是一个简单的字符串列表,按照插入顺序排序,可以在列表的头部(左边)或尾部(右边)添加元素。它的底层实际上是一个链表

常用操作

  • LPUSH/RPUSH/LRANGE:从左/从右/获取指定长度
  • LPOP/RPOP:移除最左/最右的元素
  • LINDEX:按照索引下标获取元素
  • LLEN:求列表长度
  • LREM:删除指定数量的元素
  • LTRIM:截取指定范围的值
  • RPOPLPUSH:将最后一个元素从一个列表弹出并推入另一个列表的头部
  • LSET:设置指定位置的元素值
  • LINSERT:在指定元素前或后插入新元素

7.3 Redis-Hash

Redis 的 Hash 是一种键值对集合。它是一个字符串类型的字段和值的映射表,特别适合存储对象。

常用操作

  • HSET/HGET/HMSET/HMGET/HGETALL/HDEL:设值/取值/设值多个值/取多个值/取全部值/删除值
  • HLEN:求哈希长度
  • HEXISTS:检查某个值是否存在
  • HKEYS/HVALS:获取所有字段名/获取所有字段值

7.4 Redis-Set (不重复的)

Redis 的 Set 类型是一个无序集合,存储的值是唯一的。

常用操作

  • SADD/SMEMBERS/SISMEMBER:添加/查看集合/查看是否存在
  • SCARD:获取集合里的元素个数
  • SREM:删除集合中的元素
  • SPOP:随机移除集合中的一个元素
  • SMOVE:将一个元素从一个集合移到另一个集合
  • SDIFF/SINTER/SUNION:求差集/交集/并集

7.5 Redis-Zset (有序集合)

Redis 的 Zset 是一种有序集合,每个元素都关联着一个分数,根据分数的排名来排序。

常用操作

  • ZADD/ZRANGE:添加元素/获取范围内的元素
  • ZRANGEBYSCORE:根据分数范围获取元素
  • ZREM:删除元素
  • ZCARD:获取有序集合的总数
  • ZCOUNT:获取分数范围内的元素个数
  • ZRANK:获取元素在有序集合中的排名

8、Python 操作 Redis

8.1、安装

pip install redis

8.2、导入 Redis 模块和创建连接

# 导入 Redis 模块
import redis

# 创建一个用于字符串操作的测试类
class TestString(object):
    def __init__(self):
        # 连接 Redis 服务器
        self.r = redis.StrictRedis(host='192.168.75.130', port=6379, db=0)

# 创建一个用于列表操作的测试类
class TestList(object):
    def __init__(self):
        # 连接 Redis 服务器
        self.r = redis.StrictRedis(host='192.168.75.130', port=6379)

# 创建一个用于集合操作的测试类
class TestSet(object):
    def __init__(self):
        # 连接 Redis 服务器
        self.r = redis.StrictRedis(host='192.168.75.130', port=6379)

# 创建一个用于哈希操作的测试类
class TestHash(object):
    def __init__(self):
        # 连接 Redis 服务器
        self.r = redis.StrictRedis(host='192.168.75.130', port=6379)

8.3、字符串相关操作

# 设置值
def test_set(self):
    res = self.r.set('user1', 'juran-1')  # 设置 key 'user1' 的值为 'juran-1'
    print(res)

# 取值
def test_get(self):
    res = self.r.get('user1')  # 获取 key 'user1' 的值
    print(res)

# 设置多个值
def test_mset(self):
    d = {
        'user2': 'juran-2',
        'user3': 'juran-3'
    }
    res = self.r.mset(d)  # 批量设置多个键值对
    print(res)

# 取多个值
def test_mget(self):
    l = ['user2', 'user3']
    res = self.r.mget(l)  # 批量获取多个键的值
    print(res)

# 删除
def test_del(self):
    self.r.delete('user2')  # 删除 key 'user2'

8.4、列表相关操作

# 插入记录
def test_push(self):
    res = self.r.lpush('common', '1')  # 将元素 '1' 插入到列表 'common' 的左边
    res = self.r.rpush('common', '2')  # 将元素 '2' 插入到列表 'common' 的右边

# 弹出记录
def test_pop(self):
    res = self.r.lpop('common')  # 从列表 'common' 的左边弹出一个元素
    res = self.r.rpop('common')  # 从列表 'common' 的右边弹出一个元素

# 范围取值
def test_range(self):
    res = self.r.lrange('common', 0, -1)  # 获取列表 'common' 中的所有元素
    print(res)

8.5、集合相关操作

# 添加数据
def test_sadd(self):
    res = self.r.sadd('set01', '1', '2')  # 向集合 'set01' 中添加元素 '1' 和 '2'
    lis = ['Cat', 'Dog']
    res = self.r.sadd('set02', *lis)  # 向集合 'set02' 中添加多个元素

# 删除数据
def test_del(self):
    res = self.r.srem('set01', '1')  # 从集合 'set01' 中删除元素 '1'

# 随机删除数据
def test_pop(self):
    res = self.r.spop('set02')  # 随机从集合 'set02' 中弹出一个元素

8.6、哈希相关操作

# 批量设值
def test_hset(self):
    dic = {
        'id': 1,
        'name': 'huawei'
    }
    res = self.r.hmset('mobile', dic)  # 批量设置哈希表 'mobile' 的字段及对应的值

# 批量取值
def test_hgetall(self):
    res = self.r.hgetall('mobile')  # 获取哈希表 'mobile' 中的所有字段及对应的值

# 判断是否存在 存在返回1 不存在返回0
def test_hexists(self):
    res = self.r.hexists('mobile', 'id')  # 检查字段 'id' 是否存在于哈希表 'mobile'
    print(res)

8.7、有序集合相关操作

class TestSortedSet(object):
    def __init__(self):
        # 连接 Redis 服务器
        self.r = redis.StrictRedis(host='192.168.75.130', port=6379)

# 批量设值
def test_zadd(self):
    score_members = {
        'v1': 60,
        'v2': 70,
        'v3': 80,
        'v4': 90,
        'v5': 100
    }
    res = self.r.zadd('zset01', score_members)  # 向有序集合 'zset01' 添加带有分数的成员

# 批量取值
def test_zrange(self):
    res = self.r.zrange('zset01', 0, -1)  # 获取有序集合 'zset01' 中的所有成员
    print(res)

8.8、连接 Redis 数据库

def main():
    # 创建字符串操作测试类的实例
    test_string = TestString()
    
    # 调用字符串相关操作
    test_string.test_set()
    test_string.test_get()
    test_string.test_mset()
    test_string.test_mget()
    test_string.test_del()
    
    # 创建列表操作测试类的实例
    test_list = TestList()
    
    # 调用列表相关操作
    test_list.test_push()
    test_list.test_pop()
    test_list.test_range()
    
    # 创建集合操作测试类的实例
    test_set = TestSet()
    
    # 调用集合相关操作
    test_set.test_sadd()
    test_set.test_del()
    test_set.test_pop()
    
    # 创建哈希操作测试类的实例
    test_hash = TestHash()
    
    # 调用哈希相关操作
    test_hash.test_hset()
    test_hash.test_hgetall()
    test_hash.test_hexists()
    
    # 创建有序集合操作测试类的实例
    test_sorted_set = TestSortedSet()
    
    # 调用有序集合相关操作
    test_sorted_set.test_zadd()
    test_sorted_set.test_zrange()

if __name__ == "__main__":
    main()

三、Scrapy-分布式

1、启动 Redis

点击下载 Redis

1、下载后解压到文件夹,进入解压后的目录下,在地址栏输入 cmd 后按回车。

2、启动 Redis 服务,运行命令:

redis-server

3、新开一个窗口,启动 Redis 客户端,运行命令:

redis-cli

2、Scrapy-Redis 简介

Scrapy-Redis 是 Scrapy 框架的一个扩展,用于实现分布式爬虫。它将 Scrapy 与 Redis 数据库集成,允许多个爬虫实例共享数据并协同工作,以提高爬取效率和可扩展性。

Scrapy-Redis 使用 Redis 的集合来进行 URL 的去重处理。每个爬虫实例都会在将 URL 添加到队列之前检查它是否已经存在于集合中,以避免重复爬取。

查看 GitHub 源代码

3、Scrapy 工作流程

在这里插入图片描述

4、Scrapy-Redis 工作流程

在这里插入图片描述

5、github 示例代码

安装 scrapy-redis,终端运行命令:

pip install scrapy-redis

在 github 上拉取示例代码,终端运行命令:

git clone https://github.com/rolando/scrapy-redis.git

6、Scrapy-Redis 中的 settings 文件

# Scrapy settings for example project
#
# For simplicity, this file contains only the most important settings by
# default. All the other settings are documented here:
#
# http://doc.scrapy.org/topics/settings.html
#
SPIDER_MODULES = ['example.spiders']
NEWSPIDER_MODULE = 'example.spiders'
USER_AGENT = 'scrapy-redis (+https://github.com/rolando/scrapy-redis)'
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"  # 指定那个去重⽅法给 request 对象去重
SCHEDULER = "scrapy_redis.scheduler.Scheduler"  # 指定 Scheduler 队列
SCHEDULER_PERSIST = True  # 队列中的内容是否持久保存,为 false 的时候在关闭 Redis 的时候,清空 Redis
#SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderPriorityQueue"
#SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderQueue"
#SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderStack"
ITEM_PIPELINES = {
 'example.pipelines.ExamplePipeline': 300,
 'scrapy_redis.pipelines.RedisPipeline': 400,  # scrapy_redis 实现的 items 保存到 Redis 的 pipeline
}
LOG_LEVEL = 'DEBUG'
# Introduce an artificial delay to make use of parallelism. to speed up the crawl.
DOWNLOAD_DELAY = 1

7、Scrapy-Redis 运行

allowed_domains = ['dmoztools.net']
start_urls = ['http://www.dmoztools.net/']
scrapy crawl dmoz

运行结束后,Redis 中会多出三个键:

  • “dmoz:requests”:存放待爬取的 requests 对象。
  • “dmoz:item”:存放爬取到的信息。
  • “dmoz:dupefilter”:存放爬取的 requests 的指纹信息。

在这里插入图片描述

四、案例

目标网站:https://www.daomubiji.com/

需求:

爬取整套小说,分为1级、2级、3级页面。

将爬取内容分门别类存放在文件夹中,有一个小说文件夹,里面新建文件夹,每一个文件夹存放每一套盗墓笔记。

小说具体内容保存为 txt 文件。

1、页面分析

1级页面:获取标题以及2级页面的 URL。

1级标题:‘//li[contains(@id, “menu-item”)]/a/text()’

2级 URL:‘//li[contains(@id, “menu-item”)]/a/@href’

2级页面:获取章节标题以及3级页面的 URL。

2级标题:a_lst = ‘//article/a/text()’

3级的 URL:a_url = ‘//article/a/@href’

3级页面:获取的文本内容。

content = response.xpath(‘//article/p/text()’)

2、使用 Scrapy 框架实现

1、在终端输入命令创建一个 Scrapy 框架。

scrapy startproject dmbj

cd dmbj

scrapy genspider dm daomubiji.com

2、修改 setting.py 文件。

# setting.py
LOG_LEVEL = 'WARNING'
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
# Override the default request headers:
DEFAULT_REQUEST_HEADERS = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'en',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36'
}
# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
   'dmbj.pipelines.DmbjPipeline': 300,
}

3、在 dmbj 文件夹下新建一个 start.py 文件。

# start.py
# 使用 cmdline 模块来执行命令行命令
from scrapy import cmdline

# 使用 Scrapy 执行名为 dm 的爬虫
cmdline.execute('scrapy crawl dm'.split())

4、编写 spiders 文件夹下的 dm.py 文件的代码,获取一级标题和二级 url。

# spiders/dm.py
import scrapy  # 导入 Scrapy 库,用于构建爬虫

# 创建一个名为 DmSpider 的 Scrapy 爬虫类
class DmSpider(scrapy.Spider):
    name = 'dm'  # 爬虫的名称
    allowed_domains = ['daomubiji.com']  # 允许爬取的域名
    start_urls = ['http://daomubiji.com/']  # 起始 URL 列表

    # 解析函数,用于处理响应并提取数据
    def parse(self, response):
        a_list = response.xpath('//li[contains(@id, "menu-item")]/a')
        for a in a_list:
            # 一级标题
            first_title = a.xpath('./text()').get()
            # 二级 url
            second_url = a.xpath('./@href').get()
            # 打印一级标题和二级 url
            print(first_title, second_url)

5、编写 item.py 文件的代码,保存一级标题。

# item.py
import scrapy  # 导入 Scrapy 库,用于构建爬虫

class DmbjItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    # 一级标题
    first_title = scrapy.Field()
    pass

6、编写 spiders 文件夹下的 dm.py 文件的代码,构造二级页面。

# spiders/dm.py
import scrapy  # 导入 Scrapy 库,用于构建爬虫
from dmbj.items import DmbjItem  # 导入自定义的 Item 类

# 创建一个名为 DmSpider 的 Scrapy 爬虫类
class DmSpider(scrapy.Spider):
    name = 'dm'  # 爬虫的名称
    allowed_domains = ['daomubiji.com']  # 允许爬取的域名
    start_urls = ['http://daomubiji.com/']  # 起始 URL 列表

    # 解析函数,用于处理响应并提取数据
    def parse(self, response):
        a_list = response.xpath('//li[contains(@id, "menu-item")]/a')
        for a in a_list:
            # 创建一个 DmbjItem 实例
            item = DmbjItem()
            # 一级标题
            item['first_title'] = a.xpath('./text()').get()
            # 二级 url
            second_url = a.xpath('./@href').get()
            # 构造二级页面,使用 parse_article 方法处理
            yield scrapy.Request(
                url=second_url,
                meta={'item': item},  # 将 item 传递给下一个回调函数
                callback=self.parse_article
            )

    # 解析二级页面
    def parse_article(self, response):
        # 从响应的 meta 中获取之前传递的 item 对象
        item = response.meta.get('item')
        # 三级 url,章节标题
        a_lst = response.xpath('//article/a')
        for a in a_lst:
            # 存储章节标题到 item 对象
            item['second_title'] = a.xpath('./text()').get()
            # 三级 url
            third_url = a.xpath('./@href').get()
            print(item)

7、编写 item.py 文件的代码,保存二级标题。

# item.py
import scrapy  # 导入 Scrapy 库,用于构建爬虫

class DmbjItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    # 一级标题
    first_title = scrapy.Field()
    # 二级标题
    second_title = scrapy.Field()
    pass

8、编写 spiders 文件夹下的 dm.py 文件的代码,获取文章内容的段落。

# spiders/dm.py
import scrapy  # 导入 Scrapy 库,用于构建爬虫
from dmbj.items import DmbjItem  # 导入自定义的 Item 类

# 创建一个名为 DmSpider 的 Scrapy 爬虫类
class DmSpider(scrapy.Spider):
    name = 'dm'  # 爬虫的名称
    allowed_domains = ['daomubiji.com']  # 允许爬取的域名
    start_urls = ['http://daomubiji.com/']  # 起始 URL 列表

    # 解析函数,用于处理响应并提取数据
    def parse(self, response):
        a_list = response.xpath('//li[contains(@id, "menu-item")]/a')
        for a in a_list:
            # 创建一个 DmbjItem 实例
            item = DmbjItem()
            # 一级标题
            item['first_title'] = a.xpath('./text()').get()
            # 二级 url
            second_url = a.xpath('./@href').get()
            # 构造二级页面,使用 parse_article 方法处理
            yield scrapy.Request(
                url=second_url,
                meta={'item': item},  # 将 item 传递给下一个回调函数
                callback=self.parse_article
            )

    # 解析二级页面
    def parse_article(self, response):
        # 从响应的 meta 中获取之前传递的 item 对象
        item = response.meta.get('item')
        # 三级 url,章节标题
        a_lst = response.xpath('//article/a')
        for a in a_lst:
            # 存储章节标题到 item 对象
            item['second_title'] = a.xpath('./text()').get()
            # 三级 url
            third_url = a.xpath('./@href').get()
            # print(item)
            # 向三级页面发请求
            yield scrapy.Request(
                url=third_url,
                meta={'item': item},  # 将 item 传递给下一个回调函数
                callback=self.parse_content
            )

    # 解析获取数据内容
    def parse_content(self, response):
        # 从响应的 meta 中获取之前传递的 item 对象
        item = response.meta.get('item')
        # 使用 XPath 选择器提取文章内容的段落
        content_lst = response.xpath('//article[@class="article-content"]/p/text()').getall()
        # 将段落文本连接成一个字符串,并存储到 item 对象
        item['content'] = '\n'.join(content_lst)
        print(item)
        # 将 item 传递给下一个处理管道
        yield item

9、编写 item.py 文件的代码,保存内容。

# item.py
import scrapy  # 导入 Scrapy 库,用于构建爬虫

class DmbjItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    # 一级标题
    first_title = scrapy.Field()
    # 二级标题
    second_title = scrapy.Field()
    # 内容
    content = scrapy.Field()
    pass

10、创建文件夹,保存获取的数据。

方式一:在 spiders 文件夹下的 dm.py 文件里编写代码。

# spiders/dm.py
import scrapy  # 导入 Scrapy 库,用于构建爬虫
from dmbj.items import DmbjItem  # 导入自定义的 Item 类
import re  # 导入正则表达式模块
import os  # 导入操作系统模块

# 创建一个名为 DmSpider 的 Scrapy 爬虫类
class DmSpider(scrapy.Spider):
    name = 'dm'  # 爬虫的名称
    allowed_domains = ['daomubiji.com']  # 允许爬取的域名
    start_urls = ['http://daomubiji.com/']  # 起始 URL 列表

    # 解析函数,用于处理响应并提取数据
    def parse(self, response):
        a_list = response.xpath('//li[contains(@id, "menu-item")]/a')
        for a in a_list:
            # 创建一个 DmbjItem 实例
            item = DmbjItem()
            # 一级标题
            s = a.xpath('./text()').get()
            # 二级 url
            second_url = a.xpath('./@href').get()
            # 使用正则表达式替换一级标题中的特殊字符为下划线
            item['first_title'] = re.sub(r'[\\:<>*? ]', "_", s)
            # 构造一级标题的文件夹路径
            dir_path = r"小说/{}".format(item['first_title'])
            # 创建之前一定要做判断,如果文件夹不存在,则创建
            if not os.path.exists(dir_path):
                os.makedirs(dir_path)
            # 构造二级页面,使用 parse_article 方法处理
            yield scrapy.Request(
                url=second_url,
                meta={'item': item},  # 将 item 传递给下一个回调函数
                callback=self.parse_article
            )

    # 解析二级页面
    def parse_article(self, response):
        # 从响应的 meta 中获取之前传递的 item 对象
        item = response.meta.get('item')
        # 三级 url,章节标题
        a_lst = response.xpath('//article/a')
        for a in a_lst:
            # 存储章节标题到 item 对象
            item['second_title'] = a.xpath('./text()').get()
            # 三级 url
            third_url = a.xpath('./@href').get()
            # print(item)
            # 向三级页面发请求
            yield scrapy.Request(
                url=third_url,
                meta={'item': item},  # 将 item 传递给下一个回调函数
                callback=self.parse_content
            )

    # 解析获取数据内容
    def parse_content(self, response):
        # 从响应的 meta 中获取之前传递的 item 对象
        item = response.meta.get('item')
        # 使用 XPath 选择器提取文章内容的段落
        content_lst = response.xpath('//article[@class="article-content"]/p/text()').getall()
        # 将段落文本连接成一个字符串,并存储到 item 对象
        item['content'] = '\n'.join(content_lst)
        print(item)
        # 将 item 传递给下一个处理管道
        yield item

方式二:在管道文件 pipelines.py 中创建。

# pipelines.py
import re  # 导入 re 模块,用于正则表达式操作

# 定义 DmbjPipeline 类
class DmbjPipeline:
    # 处理爬取到的数据项
    def process_item(self, item, spider):
        # 构建保存小说文件的文件夹路径,使用一级标题和二级标题,将特殊字符替换为下划线
        dir_path = r'小说/{}/{}'.format(
            item['first_title'],
            re.sub(r'[\\:<>*? ]', "_", item['second_title'])
        )
        # 构建保存小说内容的文件路径,加上 .txt 后缀
        filename = dir_path + '.txt'
        # 将小说内容写入文件中,使用 UTF-8 编码
        with open(filename, 'w', encoding='utf-8') as f:
            f.write(item['content'])
        # 返回 item 对象,用于后续的处理或保存
        return item

11、对数据做一个深拷贝处理,使用 Scrapy 框架实现就完成了。

# spiders/dm.py
import scrapy  # 导入 Scrapy 库,用于构建爬虫
from dmbj.items import DmbjItem  # 导入自定义的 Item 类
import re  # 导入正则表达式模块
import os  # 导入操作系统模块
from copy import deepcopy  # 导入深拷贝函数

# 创建一个名为 DmSpider 的 Scrapy 爬虫类
class DmSpider(scrapy.Spider):
    name = 'dm'  # 爬虫的名称
    allowed_domains = ['daomubiji.com']  # 允许爬取的域名
    start_urls = ['http://daomubiji.com/']  # 起始 URL 列表

    # 解析函数,用于处理响应并提取数据
    def parse(self, response):
        a_list = response.xpath('//li[contains(@id, "menu-item")]/a')
        for a in a_list:
            # 创建一个 DmbjItem 实例
            item = DmbjItem()
            # 一级标题
            s = a.xpath('./text()').get()
            # 二级 url
            second_url = a.xpath('./@href').get()
            # 使用正则表达式替换一级标题中的特殊字符为下划线
            item['first_title'] = re.sub(r'[\\:<>*? ]', "_", s)
            # 构造一级标题的文件夹路径
            dir_path = r"小说/{}".format(item['first_title'])
            # 创建之前一定要做判断,如果文件夹不存在,则创建
            if not os.path.exists(dir_path):
                os.makedirs(dir_path)
            # 构造二级页面,使用 parse_article 方法处理
            yield scrapy.Request(
                url=second_url,
                meta={'item': item},  # 将 item 传递给下一个回调函数
                callback=self.parse_article
            )

    # 解析二级页面
    def parse_article(self, response):
        # 从响应的 meta 中获取之前传递的 item 对象
        item = response.meta.get('item')
        # 三级 url,章节标题
        a_lst = response.xpath('//article/a')
        for a in a_lst:
            # 存储章节标题到 item 对象
            item['second_title'] = a.xpath('./text()').get()
            # 三级 url
            third_url = a.xpath('./@href').get()
            # print(item)
            # 向三级页面发请求
            yield scrapy.Request(
                url=third_url,
                meta={'item': deepcopy(item)},  # 将 item 传递给下一个回调函数
                callback=self.parse_content
            )

    # 解析获取数据内容
    def parse_content(self, response):
        # 从响应的 meta 中获取之前传递的 item 对象
        item = response.meta.get('item')
        # 使用 XPath 选择器提取文章内容的段落
        content_lst = response.xpath('//article[@class="article-content"]/p/text()').getall()
        # 将段落文本连接成一个字符串,并存储到 item 对象
        item['content'] = '\n'.join(content_lst)
        print(item)
        # 将 item 传递给下一个处理管道
        yield item

3、改写成 Scrapy-Redis

1、导入 Scrapy-Redis,修改继承类。

# spiders/dm.py
import scrapy  # 导入 Scrapy 库,用于构建爬虫
from dmbj.items import DmbjItem  # 导入自定义的 Item 类
import re  # 导入正则表达式模块
import os  # 导入操作系统模块
from copy import deepcopy  # 导入深拷贝函数
from scrapy_redis.spiders import RedisSpider  # 导入 Scrapy-Redis 中的 RedisSpider 类


# 创建一个名为 DmSpider 的 Scrapy 爬虫类,继承自 RedisSpider
class DmSpider(RedisSpider):
    name = 'dm'  # 爬虫的名称
    redis_key = 'daomu_key'  # 指定 Redis 的键名,从中读取起始 URL
    # allowed_domains = ['daomubiji.com']  # 允许爬取的域名
    # start_urls = ['http://daomubiji.com/']  # 起始 URL 列表

    # 解析函数,用于处理响应并提取数据
    def parse(self, response):
        a_list = response.xpath('//li[contains(@id, "menu-item")]/a')
        for a in a_list:
            # 创建一个 DmbjItem 实例
            item = DmbjItem()
            # 一级标题
            s = a.xpath('./text()').get()
            # 二级 url
            second_url = a.xpath('./@href').get()
            # 使用正则表达式替换一级标题中的特殊字符为下划线
            item['first_title'] = re.sub(r'[\\:<>*? ]', "_", s)
            # 构造一级标题的文件夹路径
            dir_path = r"小说/{}".format(item['first_title'])
            # 创建之前一定要做判断,如果文件夹不存在,则创建
            if not os.path.exists(dir_path):
                os.makedirs(dir_path)
            # 构造二级页面,使用 parse_article 方法处理
            yield scrapy.Request(
                url=second_url,
                meta={'item': item},  # 将 item 传递给下一个回调函数
                callback=self.parse_article
            )

    # 解析二级页面
    def parse_article(self, response):
        # 从响应的 meta 中获取之前传递的 item 对象
        item = response.meta.get('item')
        # 三级 url,章节标题
        a_lst = response.xpath('//article/a')
        for a in a_lst:
            # 存储章节标题到 item 对象
            item['second_title'] = a.xpath('./text()').get()
            # 三级 url
            third_url = a.xpath('./@href').get()
            # print(item)
            # 向三级页面发请求
            yield scrapy.Request(
                url=third_url,
                meta={'item': deepcopy(item)},  # 将 item 传递给下一个回调函数
                callback=self.parse_content
            )

    # 解析获取数据内容
    def parse_content(self, response):
        # 从响应的 meta 中获取之前传递的 item 对象
        item = response.meta.get('item')
        # 使用 XPath 选择器提取文章内容的段落
        content_lst = response.xpath('//article[@class="article-content"]/p/text()').getall()
        # 将段落文本连接成一个字符串,并存储到 item 对象
        item['content'] = '\n'.join(content_lst)
        print(item)
        # 将 item 传递给下一个处理管道
        yield item

2、修改 setting.py 文件。

# setting.py
# Scrapy settings for dmbj project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
#     https://docs.scrapy.org/en/latest/topics/settings.html
#     https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
#     https://docs.scrapy.org/en/latest/topics/spider-middleware.html

BOT_NAME = 'dmbj'
LOG_LEVEL = 'WARNING'
SPIDER_MODULES = ['dmbj.spiders']
NEWSPIDER_MODULE = 'dmbj.spiders'


# 设置用户代理信息
USER_AGENT = 'scrapy-redis (+https://github.com/rolando/scrapy-redis)'
# 指定去重方式,给请求对象去重
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 设置调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 队列中的内容是否进行持久保留
# True redis 关闭的时候数据会保留
# False 不会保留
SCHEDULER_PERSIST = True
# SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderPriorityQueue"
# SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderQueue"
# SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderStack"

# LOG_LEVEL = 'DEBUG'

# Introduce an artifical delay to make use of parallelism. to speed up the
# crawl.
DOWNLOAD_DELAY = 1

# 配置 Redis 连接信息
REDIS_URL = "redis://127.0.0.1:6379"


# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'dmbj (+http://www.yourdomain.com)'

# Obey robots.txt rules
ROBOTSTXT_OBEY = False

# Configure maximum concurrent requests performed by Scrapy (default: 16)
#CONCURRENT_REQUESTS = 32

# Configure a delay for requests for the same website (default: 0)
# See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
#DOWNLOAD_DELAY = 3
# The download delay setting will honor only one of:
#CONCURRENT_REQUESTS_PER_DOMAIN = 16
#CONCURRENT_REQUESTS_PER_IP = 16

# Disable cookies (enabled by default)
#COOKIES_ENABLED = False

# Disable Telnet Console (enabled by default)
#TELNETCONSOLE_ENABLED = False

# Override the default request headers:
DEFAULT_REQUEST_HEADERS = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'en',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36'
}

# Enable or disable spider middlewares
# See https://docs.scrapy.org/en/latest/topics/spider-middleware.html
#SPIDER_MIDDLEWARES = {
#    'dmbj.middlewares.dmbjSpiderMiddleware': 543,
#}

# Enable or disable downloader middlewares
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
#DOWNLOADER_MIDDLEWARES = {
#    'dmbj.middlewares.dmbjDownloaderMiddleware': 543,
#}

# Enable or disable extensions
# See https://docs.scrapy.org/en/latest/topics/extensions.html
#EXTENSIONS = {
#    'scrapy.extensions.telnet.TelnetConsole': None,
#}

# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
   'dmbj.pipelines.DmbjPipeline': 300,
   'scrapy_redis.pipelines.RedisPipeline': 400,  # 保存数据到 Redis 数据库
}

# Enable and configure the AutoThrottle extension (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/autothrottle.html
#AUTOTHROTTLE_ENABLED = True
# The initial download delay
#AUTOTHROTTLE_START_DELAY = 5
# The maximum download delay to be set in case of high latencies
#AUTOTHROTTLE_MAX_DELAY = 60
# The average number of requests Scrapy should be sending in parallel to
# each remote server
#AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
# Enable showing throttling stats for every response received:
#AUTOTHROTTLE_DEBUG = False

# Enable and configure HTTP caching (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
#HTTPCACHE_ENABLED = True
#HTTPCACHE_EXPIRATION_SECS = 0
#HTTPCACHE_DIR = 'httpcache'
#HTTPCACHE_IGNORE_HTTP_CODES = []
#HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'

3、启动 Redis

进入 Redis 目录,在地址栏输入 cmd 后按回车两次,分别启动 Redis 服务端和 Redis 客户端。

启动 Redis 服务端,运行命令:

redis-server

启动 Redis 客户端,运行命令:

redis-cli

在 Redis 客户端输入命令:

清空:

flushal

查询:

keys *

4、启动程序

在 Redis 客户端输入命令:

lpush Redis键名 网址

lpush daomu_key http://daomubiji.com/

在 Redis 服务端输入命令:

cd 项目的绝对路径

scrapy crawl dm

可以开多个终端运行以上两条命令,会同时爬取数据。

4、改写分布式总结

1、导入类,修改继承类。

from scrapy_redis.spiders import RedisSpider
class DmSpider(RedisSpider):

2、修改配置文件。

# 设置用户代理信息
USER_AGENT = 'scrapy-redis (+https://github.com/rolando/scrapy-redis)'
# 指定去重方式,给请求对象去重
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 设置调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 队列中的内容是否进行持久保留
# True redis 关闭的时候数据会保留
# False 不会保留
SCHEDULER_PERSIST = True
# SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderPriorityQueue"
# SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderQueue"
# SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderStack"

# LOG_LEVEL = 'DEBUG'

# Introduce an artifical delay to make use of parallelism. to speed up the
# crawl.
DOWNLOAD_DELAY = 1

# 配置 Redis 连接信息
REDIS_URL = "redis://127.0.0.1:6379"

# 管道
ITEM_PIPELINES = {
   'dmbj.pipelines.DmbjPipeline': 300,
   'scrapy_redis.pipelines.RedisPipeline': 400,  # 保存数据到 Redis 数据库
}

3、注意爬取数据前要在 Redis 客户端输入命令。

lpush Redis键名 网址

lpush daomu_key http://daomubiji.com/

记录学习过程,欢迎讨论交流,尊重原创,转载请注明出处~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1023509.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【python基础】—函数def()的定义与调用、参数、return返回值及变量作用域

文章目录 定义函数&#xff1a;def()语句调用函数&#xff1a;输入函数名和参数对应的值参数return 返回值变量作用域 定义函数&#xff1a;def()语句 语法&#xff1a; def 函数名(参数1,参数2,.....,参数n): 函数体 return 语句举例&#xff1a; def hello(name):print(n…

【MySQL系列】- MySQL自动备份详解

【MySQL系列】- MySQL自动备份详解 文章目录 【MySQL系列】- MySQL自动备份详解一、需求背景二、Windows mysql自动备份方法2.1 复制date文件夹备份实验备份环境创建bat直接备份脚本 2 .2 mysqldump备份成sql文件创建mysqldump备份脚本 2 .3 利用WinRAR对MySQL数据库进行定时备…

Android:报错“Transform‘s input file does not exist”

一、前言&#xff1a; android运行的时候出现如下错误&#xff1a;Transform output file C:\android\Enjoy\app\libs\xxxx.jar does not exist.刚开始我也很懵&#xff0c;我理解的事什么文件没有。上网查完资料&#xff0c;发现是添加依赖的时候有问题。 二、解决方案&…

Redis实战:Redis在Java中的基本使用

本片将介绍 Redis 在 Java 中的基本使用 文章目录 1、使用jedis操作redis1.1、Jedis简介1.2、引入jedis的Maven依赖1.2、获取连接1.3、使用实例 2、对于JedisPooled的使用2.1、使用JedisPooled2.2、关于连接池 3、SpringBoot下使用Redis3.1、引入Maven依赖3.2、配置Redis连接3.…

C语言入门log03

2023.9.19 周二 江苏 软件&#xff1a;visual studio 2017 &#xff1b;注释快捷键 ctrlkc&#xff1b;取消ctrlku;运行快捷键 ctrlf5 B站视频 P4 按位取反~ int main() {//int arr[] {1,2,3,4,5,6};//4*624//printf("%d\n",sizeof(arr));//24 数组大小//print…

第14章 结构和其他数据形式

本章介绍以下内容&#xff1a; 关键字&#xff1a;struct、union、typedef 运算符&#xff1a;.、-> 什么是C结构&#xff0c;如何创建结构模板和结构变量 如何访问结构的成员&#xff0c;如何编写处理结构的函数 联合和指向函数的指针 设计程序时&#xff0c;最重要的步骤之…

面向面试知识--Lottery项目

面向面试知识–Lottery项目 1.设计模式 为什么需要设计模式&#xff1f; &#xff08;设计模式是什么&#xff1f;优点有哪些&#xff1f;&#xff09; 设计模式是一套经过验证的有效的软件开发指导思想/解决方案&#xff1b;提高代码的可重用性和可维护性&#xff1b;提高团…

DAQ高频量化平台:引领Ai高频量化交易模式变革

近年来&#xff0c;数字货币投资市场掀起了一股热潮&#xff0c;以&#xff08;BTC&#xff09;为代表的区块链技术带来了巨大的商业变革。数字资产的特点&#xff0c;如无国界、无阶级、无门槛、高流动性和高透明度&#xff0c;吸引了越来越多的人们的关注和认可&#xff0c;创…

linux内网渗透

一、信息收集 主机发现&#xff1a; nmap -sP 192.168.16.0/24 端口探测 masscan -p 1-65535 192.168.16.168 --rate1000 开放端口如下 nmap端口详细信息获取 nmap -sC -p 8888,3306,888,21,80 -A 192.168.16.168 -oA ddd4-port目录扫描 gobuster dir…

Windows安装GPU版本的pytorch详细教程

文章目录 chatGLM2-6B安装教程正式安装 chatGLM2-6B ChatGLM2-6B版本要装pytorch2.0&#xff0c;而且要2.0.1 &#xff0c;因此CUDA不能用12.0 &#xff0c;也不能用10.0&#xff0c;只能用11.x 版本。 安装教程 pip install直接下载安装 官网&#xff1a; https://pytorch.…

Python语言学习实战-内置函数all()和any()的使用(附源码和实现效果)

实现功能 all()和any()函数都是Python的内置函数&#xff0c;用于对布尔值进行操作。 all()函数接受一个可迭代对象作为参数&#xff0c;如果可迭代对象中所有元素都为真值&#xff08;即非零、非空、非None等&#xff09;&#xff0c;则返回True&#xff0c;否则返回False。…

VR全景需要加盟吗?简述VR全景加盟的意义

对于一个刚开始了解VR全景行业的新人来说&#xff0c;VR全景不是有软件、有设备、会拍摄就行了吗&#xff1f;为什么还要找全景平台进行加盟呢&#xff1f;VR全景加盟的作用又是什么呢&#xff1f;那么&#xff0c;我们就不得不多问几个问题了&#xff0c;例如不加盟的话&#…

企业级数据仓库-理论知识

D3 AM 大数据中间件 Hive&#xff1a;将SQL转化成分布式Map/Reduce进行运算&#xff0c;也支持转换成Spark,需要单独安装Hive集群才能访问Spark,支持60%的SQL&#xff0c;延迟比较大。SparkSQL:属于Spark生态圈&#xff0c;Hive on Sqark。HBase: NoSQL,高并发读&#xff0c;适…

霓虹灯效果

源码&#xff1a; void neon(Mat& src,Mat& dst) {for (int i 1; i < src.rows - 1; i) {for (int j 1; j < src.cols - 1; j){int r1, r2, r3, g1, g2, g3, b1, b2, b3;r1 src.at<Vec3b>(i, j)[2];r2 src.at<Vec3b>(i 1, j)[2];r3 src.at<…

Mac 错误zsh: command not found: brew解决方法

打开iterm或其他shell终端&#xff0c;执行命令&#xff1a; /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" 选择下载brew的源&#xff0c;输入1~6任意都行 根据提示输入Y及开机密码 最后执行&#xff1a;source ~/.z…

Jenkins自动化测试

学习 Jenkins 自动化测试的系列文章 Robot Framework 概念Robot Framework 安装Pycharm Robot Framework 环境搭建Robot Framework 介绍Jenkins 自动化测试 1. Robot Framework 概念 Robot Framework是一个基于Python的&#xff0c;可扩展的关键字驱动的自动化测试框架。 它…

应用(Application)部署容器化演进之路

目录 一、应用程序部署痛点 1.1 应用程序部署流程 1.2 应用程序扩缩容 1.3 应用程序多环境部署 二、 计算资源应用演进过程 2.1 使用物理服务器痛点 2.2 使用虚拟机优点与缺点 2.2.1 使用虚拟机优秀点 2.2.2 使用虚拟机缺点 2.3 使用容器的优点与缺点 2.3.1 使用容器…

中国提出FastSAM:在RTX3090上提升了ViT-H E(32×32) 50倍速度

文章目录 1. Abstract2. 背景介绍3. 框架详情 (Methodology)3.1 Overview3.2 All-instance Segmentation3.3 Prompt-guided Selection4. Experiments4.1 Run-time Efficiency Evaluation4.2 Zero-Shot Edge Detection4.2.1 BSDS5004.2.2 Sobel filtering4.2.3 NMS4.3 Zero-Shot…

最优化方法——Matlab实现黄金分割法一维搜索

文章目录 黄金分割法一维搜索原理算法流程&#xff1a; Matlab代码命令行窗口结果打印&#xff1a;《最优化方法》教材上写成表的答案&#xff1a;黄金分割法的一些性质 黄金分割法一维搜索原理 若保留区间为[x1,b],我们得到的结果是一致的. 该方法称为黄金分割法,实际计算取近…

编译工具:CMake(七) | cmake 常用变量和常用环境变量

编译工具&#xff1a;CMake&#xff08;七&#xff09; | cmake 常用变量和常用环境变量 cmake 变量引用方式cmake 自定义变量的方式cmake 常用变量总结 cmake 变量引用方式 cmake使用${}进行变量的引用。 在 IF 等语句中&#xff0c;是直接使用变量名而不通过${}取值 cmake…