爬虫 — Scrapy-Redis

目录

一、背景

随着互联网+大数据时代的来临,传统的关系型数据库已经不能满足中大型网站日益增长的访问量和数据量。这个时候就需要⼀种能够快速存取数据的组件来缓解数据库服务 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 模块和创建连接

python 复制代码
# 导入 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、字符串相关操作

python 复制代码
# 设置值
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、列表相关操作

python 复制代码
# 插入记录
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、集合相关操作

python 复制代码
# 添加数据
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、哈希相关操作

python 复制代码
# 批量设值
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、有序集合相关操作

python 复制代码
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 数据库

python 复制代码
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 文件

python 复制代码
# 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 运行

python 复制代码
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 文件。

python 复制代码
# 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 文件。

python 复制代码
# start.py
# 使用 cmdline 模块来执行命令行命令
from scrapy import cmdline

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

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

python 复制代码
# 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 文件的代码,保存一级标题。

python 复制代码
# 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 文件的代码,构造二级页面。

python 复制代码
# 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 文件的代码,保存二级标题。

python 复制代码
# 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 文件的代码,获取文章内容的段落。

python 复制代码
# 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 文件的代码,保存内容。

python 复制代码
# 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 文件里编写代码。

python 复制代码
# 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 中创建。

python 复制代码
# 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 框架实现就完成了。

python 复制代码
# 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,修改继承类。

python 复制代码
# 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 文件。

python 复制代码
# 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、导入类,修改继承类。

python 复制代码
from scrapy_redis.spiders import RedisSpider
class DmSpider(RedisSpider):

2、修改配置文件。

python 复制代码
# 设置用户代理信息
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/

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

相关推荐
Chef_Chen5 分钟前
从0开始机器学习--Day17--神经网络反向传播作业
python·神经网络·机器学习
千澜空25 分钟前
celery在django项目中实现并发任务和定时任务
python·django·celery·定时任务·异步任务
斯凯利.瑞恩32 分钟前
Python决策树、随机森林、朴素贝叶斯、KNN(K-最近邻居)分类分析银行拉新活动挖掘潜在贷款客户附数据代码
python·决策树·随机森林
yannan201903131 小时前
【算法】(Python)动态规划
python·算法·动态规划
蒙娜丽宁1 小时前
《Python OpenCV从菜鸟到高手》——零基础进阶,开启图像处理与计算机视觉的大门!
python·opencv·计算机视觉
光芒再现dev1 小时前
已解决,部署GPTSoVITS报错‘AsyncRequest‘ object has no attribute ‘_json_response_data‘
运维·python·gpt·语言模型·自然语言处理
好喜欢吃红柚子1 小时前
万字长文解读空间、通道注意力机制机制和超详细代码逐行分析(SE,CBAM,SGE,CA,ECA,TA)
人工智能·pytorch·python·计算机视觉·cnn
小馒头学python1 小时前
机器学习是什么?AIGC又是什么?机器学习与AIGC未来科技的双引擎
人工智能·python·机器学习
神奇夜光杯2 小时前
Python酷库之旅-第三方库Pandas(202)
开发语言·人工智能·python·excel·pandas·标准库及第三方库·学习与成长
千天夜2 小时前
使用UDP协议传输视频流!(分片、缓存)
python·网络协议·udp·视频流