Python中的爬取缓存

Python中的爬取缓存

  • 一、何时使用缓存
  • 二、为爬虫添加缓存支持
  • 三、磁盘缓存
  • 四、数据库存储缓存
    • 1. 安装Redis
    • 2. Redis的最基本操作
    • 3. Redis缓存实现

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

一、何时使用缓存

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

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

二、为爬虫添加缓存支持

三、磁盘缓存

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,缓存,爬虫)