Python中的爬取缓存

本文介绍了Python爬虫中缓存的重要性,何时使用缓存以及如何实现磁盘和Redis数据库的缓存策略。通过示例展示了磁盘缓存类`DiskCache`和Redis缓存类`RedisCache`的实现,强调了Redis的键值对存储特性及过期时间设置,从而提高爬取效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


  想象这么一个情况(当然可能真实发生过😏),你部署了一个爬虫项目,运行了三五天,获得了你想要的数据,这个时候一个"讨人厌"的人出现😱,让你爬取的时候增加一些数据,会不会很崩溃?是不是前面几天的工作白做了? 😇爬虫避免此类问题的方式之一是从开始时就缓存被爬取的网页,这样就可以让每个网页只下载一次,但可以多次使用。
  但是,并不是所有的爬取需求都适用缓存,那具体要不要支持缓存、如何在爬虫项目中增加缓存支持呢?请看下文。

一、何时使用缓存

  缓存,还是不缓存?是一个问题。

  如果你需要执行一个大型爬取工作,那么它可能会由于错误或异常被中断,缓存可以帮助你无须重新爬取那些可能已经抓取过的页面。缓存还可以让你在离线时访问这些页面(出于数据分析或开发的目的)。
  不过,如果你的最高优先级是获得网站最新和当前的信息,那此时缓存就没有意义。此外,如果你没有计划实现大型或可重复的爬虫,那么可能只需要每次去抓取页面即可。
  那如果支持了缓存,多久清空缓存并抓取新页面?后面都会讲解,首先让我们学习如何使用缓存!

二、为爬虫添加缓存支持

三、磁盘缓存

import os
import re
from urllib.parse import urlsplit
import json
from Link_Crawler import link_crawler


class DiskCache:
    def __init__(self, cache_dir='cache', max_len=255):
        self.cache_dir = cache_dir
        self.max_len = max_len

    def url_to_path(self, url):
        """ Return file system path string for given URL"""
        components = urlsplit(url)
        # append index.html to empty paths
        path = components.path
        if not path:
            path = '/index.html'
        elif path.endswith('/'):
            path += 'index.html'

        filename = components.netloc + path + components.query
        # replace invalid characters
        filename = re.sub('[^/0-9a-zA-Z\-.,;_ ]', '_', filename)
        # restrict maximum number of characters
        filename = '/'.join(seg[:self.max_len] for seg in filename.split('/'))

        return os.path.join(self.cache_dir, filename)

    def __getitem__(self, url):
        """Load data from disk for given URL"""
        path = self.url_to_path(url)
        if os.path.exists(path):
            return json.load(path)
        else:
            # URL has not yet been cached
            raise KeyError(url + ' does not exist')

    def __setitem__(self, url, result):
        """Save data to disk for given url"""

        path = self.url_to_path(url)
        folder = os.path.dirname(path)
        if not os.path.exists(folder):
            os.makedirs(folder)
        json.dump(result, path)


if __name__ == '__main__':
    import time
    start = time.time()
    link_crawler('http://example.python-scraping.com/', '/view', cache=DiskCache())
    print("runing:%s" % (time.time()-start))

  从运行时间和运行结果来看,第二次没有去下载,而是从磁盘重读取出来,可以看出效率提高了许多.

缺点

  • 受限于本地文件系统的限制。
  • 一些URl会被映射为相同的文件名。
  • 如果文件数量过多的话,会很难实现

四、数据库存储缓存

  介于磁盘缓存的限制,爬取到的数据量比较大,但又无任何复杂的连接,所以选用NoSQL数据库,这种数据库相比常用的关系型数据库更容易扩展。我们将使用非常流行的键值对存储 Redis 作为我们的缓存。
  键值对存储类似于 Python 字典,存储中的每个元素都有一个键和一个值。在设计 DiskCache 时,键值对模型可以很好地解决该问题。Redis 实际上表示 REmote DIctionary Server(远程字典服务器)。

1. 安装Redis

  Redis官方不支持Windows系统,只能用微软开源部门移植的版本,笔者下载版本Windows Redis下载,下载解压之后,在其文件夹直接打开cmd窗口,输入redis-server.exe redis.windows.conf,然后你就可以看到redis的信息了。(已经安装完成)

  Python程序想使用,也需要单独安装库pip install redis,完成之后通过下面代码测试是否安装成功。

import redis

r = redis.StrictRedis(host='localhost', port=6379, db=0) 
r.set('test', 'answer')
True

r.get('test')

b'answer'

  像上面,可以正常导入,正常存取,即说明安装成功,服务也启动,就可以进行后续的操作了。

2. Redis的最基本操作

set命令:存数据
  只是简单地覆盖了之前的值,这对于类似网络爬虫这样的简单存储来说非常合适。

get命令:取数据
  从 Redis 存储中接收到的是 bytes 类型,即使我们插入的是字典或字符串。

keys():
  keys 方法返回了所有可用键的列表。

delete():
  delete 方法可以让我们传递一个(或多个)键并从存储中删除它们

flushdb():
  删除所有的键

3. Redis缓存实现

import json
from datetime import timedelta
from redis import StrictRedis
from Link_Crawler import link_crawler


class RedisCache:
    def __init__(self, client=None, expires=timedelta(days=30), encoding='utf-8'):
        # if a client object is not passed then try
        # connecting to redis at the default localhost port
        self.client = StrictRedis(host='localhost', port=6379, db=0) if client is None else client
        self.expires = expires
        self.encoding = encoding

    def __getitem__(self, url):

        """Load value from Redis for the given URL"""
        record = self.client.get(url)
        if record:
            return json.loads(record.decode(self.encoding))
        else:
            raise KeyError(url + ' does not exist')

    def __setitem__(self, url, result):
        """Save value in Redis for the given URL"""
        data = bytes(json.dumps(result), self.encoding)
        self.client.setex(url, self.expires, data)


if __name__ == '__main__':
    import time
    start = time.time()
    link_crawler('http://example.python-scraping.com/', '/view', cache=RedisCache())
    print("runing:%s" % (time.time()-start))

  setex 方法,能够使我们在设置键值时附带过期时间。setex 既可以接受 datetime.timedelta,也可以接受以秒为单位的数值。这是一个非常方便的 Redis 功能,可以在指定秒数后自动删除记录。这就意味着我们不再需要像 DiskCache 类那样手工检查记录是否在我们的过期规则内。

  通过测试发现,无缓存时,爬取页面需要2.057s,当存在缓存时,爬取相同页面只需要0.3640s,缓存的效果显而易见。

### 使用Python编写爬虫抓取名人名言 要实现通过Python编写的爬虫程序来抓取名人名言网站的数据,可以采用`Scrapy`框架或者简单的`requests`库配合`lxml`解析HTML结构。以下是两种方法的具体实现方式。 --- #### 方法一:使用 Scrapy 框架 Scrapy 是一个功能强大的 Python 爬虫框架,适合处理复杂的网页抓取任务。以下是如何配置并运行一个基本的 Scrapy 项目的步骤: 1. **安装依赖** 首先需要确保已安装 `Scrapy` 库: ```bash pip install scrapy ``` 2. **创建 Scrapy 项目** 在命令行中执行以下命令以初始化一个新的 Scrapy 项目: ```bash scrapy startproject quotes_spider cd quotes_spider ``` 3. **定义 Spider** 编辑 `spiders` 文件夹下的爬虫脚本(假设命名为 `quotes.py`),代码如下: ```python import scrapy class QuotesSpider(scrapy.Spider): name = "quotes" allowed_domains = ["quotes.toscrape.com"] start_urls = [ 'http://quotes.toscrape.com/', ] def parse(self, response): for quote in response.css('div.quote'): yield { 'text': quote.css('span.text::text').get(), 'author': quote.css('small.author::text').get(), 'tags': quote.css('div.tags a.tag::text').getall() } next_page = response.css('li.next a::attr(href)').get() if next_page is not None: yield response.follow(next_page, self.parse) ``` 4. **运行爬虫** 创建一个调试文件 `debug.py` 来启动爬虫: ```python from scrapy.cmdline import execute execute(['scrapy', 'crawl', 'quotes']) ``` 运行此文件即可开始抓取数据[^1]。 --- #### 方法二:使用 Requests 和 LXML 解析 HTML 如果不想引入额外的框架,也可以利用基础的 `requests` 和 `lxml` 实现同样的目标。 1. **安装依赖** 安装必要的库: ```bash pip install requests lxml csv ``` 2. **编写爬虫代码** 下面是一份完整的代码示例,用于抓取 `quotes.toscrape.com` 的名言及其相关信息,并将其存储到 CSV 文件中: ```python import requests from lxml import etree import csv url = "https://quotes.toscrape.com/" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36' } data = [] while True: try: res = requests.get(url, headers=headers).text html = etree.HTML(res) # 提取页面中的所有名言信息 quotes = html.xpath('//div[@class="quote"]') for q in quotes: text = q.xpath('.//span[@class="text"]/text()')[0].strip('"') # 去掉首尾双引号 author = q.xpath('.//small[@class="author"]/text()')[0] tags = ', '.join(q.xpath('.//div[@class="tags"]/a/text()')) data.append([text, author, tags]) # 获取下一页链接 next_url = html.xpath('//li[@class="next"]/a/@href') if next_url: url = f"https://quotes.toscrape.com{next_url[0]}" else: break except Exception as e: print(f"Error occurred: {e}") break # 存储到CSV文件 with open("famous_quotes.csv", "w", encoding="utf-8", newline='') as csvfile: writer = csv.writer(csvfile) writer.writerow(["Text", "Author", "Tags"]) # 表头 writer.writerows(data) ``` 此代码会遍历多个分页并将结果保存至本地 CSV 文件中[^3]。 --- #### 注意事项 - 如果 URL 地址输入错误,可能会引发请求失败或返回异常响应。因此,在设置初始 URL 时需格外小心[^2]。 - 对于动态加载的内容(如 JavaScript 渲染后的 DOM 结构),可能需要借助 Selenium 或 Playwright 工具模拟浏览器行为。 --- ### 性能优化建议 为了提高效率和稳定性,可考虑以下几点改进措施: 1. 设置合理的下载延迟时间 (`DOWNLOAD_DELAY`),防止因访问频率过高而被封禁 IP。 2. 添加代理池支持,降低单一 IP 被检测的风险。 3. 利用缓存机制减少重复请求次数,加快整体速度。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值