WSWP(用 python写爬虫) 笔记四:实现缓存功能
来源:互联网 发布:揭阳有没有学淘宝培训 编辑:程序博客网 时间:2024/06/05 16:08
前面已经重构好了链接获取、数据获取模块,现在开始实现具体的缓存功能。
磁盘缓存
缓存下载结果,先来尝试最容易想到的方案,将下载到的网页内容存储到文件系统中。为了实现该功能,需要将url安全的映射为跨平台的文件名。三大主流文件系统的文件名限制如下:
为了保证在不同的操作系统中,文件路径都是合法的,需要限制其只能包含数字、字母和基本符号,其他的非法字符替换为下划线,完整实现代码如下:
# diskCache.py def urlToPath(self, url): """ Create file system path for this url :param url: :return: """ components = urllib.parse.urlparse(url) # when empty path set to /index.html 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 maxinum number of characters filename = '/'.join(segment[:255] for segment in filename.split('/')) return os.path.join(self.cacheDir, filename)
上面的实现代码中,考虑了一种边界情况,url可能会以斜(’/’)结尾,此时通过urlib.parse.urlparse解析后, path为 ‘/’,属于非法字符串,解决的办法是添加index.html作为其文件名。同样地,当path为空时也进行相同的操作。同时为了节省磁盘空间,先使用pickle对数据进行序列化,然后在存储数据之前先对数据使用zlib进行压缩处理。从此盘中加载时反过来先解压再通过反序列化读取即可。完整代码如下:
# diskCache.pyimport osimport reimport urllib.parseimport shutilimport zlibimport hashlibfrom datetime import datetime, timedeltatry: import cPickle as pickleexcept ImportError: import pickleclass DiskCache: def __init__(self, cacheDir='cache', expires=timedelta(days=30),compress=True): """ :param cacheDir: 缓存存放目录 :param expires: 缓存过期时间 :param compress: 是否压缩 """ self.cacheDir = cacheDir self.expires = expires self.compress = compress def __getitem__(self,url): """ Load data from disk for this URL :param url: :return: """ path = self.urlToPath(url) if os.path.exists(path): with open(path, 'rb') as fp: data = fp.read() if self.compress: data = zlib.decompress(data) result, timestamp = pickle.loads(data) if self.hasExpired(timestamp): raise KeyError(url + ' has expired') return result else: # url has not yet been cached raise KeyError(url + ' does not exist') def __setitem__(self, url, result): """ Save data to disk for this url :param url: :param result: :return: """ path = self.urlToPath(url) folder = os.path.dirname(path) if not os.path.exists(folder): os.makedirs(folder) data = pickle.dumps((result, datetime.utcnow())) if self.compress: data = zlib.compress(data) with open(path, 'wb') as fp: fp.write(data) def __delitem__(self, url): """ Remove the value at this key and any empty parent sub-directories :param url: :return: """ path = self._keyPath(url) try: os.remove(path) os.removedirs(os.path.dirname(path)) except OSError: pass def urlToPath(self, url): """ Create file system path for this url :param url: :return: """ components = urllib.parse.urlparse(url) # when empty path set to /index.html 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 maxinum number of characters filename = '/'.join(segment[:255] for segment in filename.split('/')) return os.path.join(self.cacheDir, filename) def hasExpired(self, timestamp): """ Return whether this timestamp has expired :param timestamp: :return: """ return datetime.utcnow() > timestamp + self.expires def clear(self): """ Remove all the cached values :return: """ if os.path.exists(self.cacheDir): shutil.rmtree(self.cacheDir)
上面的代码中,不仅为数据添加了过期时间,还实现清理全部数据或者是根据url来清理特定的数据的功能。
缺点:
1. 存在一些url会被映射为相同的文件名。比如:
- http://example.com/?a+b
- http://example.com/?a*b
http://example.com/?a=b
避免这种缺陷的方式是通过使用url的哈希值作为文件名。
- 但是文件系统中每个卷和每个目录下的文件数量是有限,如果下载的文件过多,则会出现存储错误。
更优的方式:数据库缓存
为了避免磁盘缓存方案的已知限制,接下来将会在现有的数据库系统上创建缓存。爬取时可能需要缓存大量数据,但又不需要任何复杂的连接操作,因此将选用NoSQL数据库,这种数据库比传统的关系型数据库更易于扩展。在此我选用MongoDB作为缓存数据库。
先从官网https://www.mongodb.org/downloads下载安装mongodb客户端,然后通过pip下载额外的Python封装库。
pip install pymonogo
MongoDB教程可以看这里mongoDB 入门。
为了避免在对相同的url插入时出现多条记录,将ID设置为url,并执行upsert操作,当记录存在时更新记录,否则插入新纪录。同时也需要对数据进行压缩处理以减少数据大小。
实现代码如下:
try: import cPickle as pickleexcept ImportError: import pickleimport zlibfrom datetime import datetime, timedeltafrom pymongo import MongoClientfrom bson.binary import Binaryclass MongoCache: def __init__(self, client=None, expires=timedelta(days=30)): self.client = MongoClient('localhost', 27017) if client is None else client self.db = self.client.cache self.db.webpage.create_index('timestamp', expireAfterSeconds=expires.total_seconds()) def __contains__(self, url): try: self[url] except KeyError: return False else: return True def __getitem__(self, url): """ Load value at this url :param url: :return: """ record = self.db.webpage.find_one({'id':url}) if record: return pickle.loads(zlib.decompress(record['result'])) else: raise KeyError(url + ' does not exist') def __setitem__(self, url, result): """ Save value for this url :param url: :param result: :return: """ record = {'result':Binary(zlib.compress(pickle.dumps(result))), 'timestamp': datetime.utcnow()} self.db.webpage.update({'_id':url}, {'$set':record}, upsert=True) def clear(self): self.db.webpage.drop()
到此文件数据缓存的功能已经实现了。
- WSWP(用 python写爬虫) 笔记四:实现缓存功能
- WSWP(用 python写爬虫) 笔记三:为爬虫添加缓存网页内容功能
- WSWP(用 python写爬虫) 笔记五:并发爬虫
- WSWP(用python写网络爬虫)笔记 一:实现简单爬虫
- WSWP(用python写爬虫)笔记二:实现链接获取和数据存储爬虫
- 学用python写爬虫笔记(1)
- 用python 写网络爬虫 学习笔记
- 《用python写网络爬虫》笔记1
- 《用python写网络爬虫》笔记2
- 用python写网络爬虫笔记
- 《用python写网络爬虫》笔记3
- Python爬虫实践笔记(四)
- 用python写一个简单的爬虫功能
- 用Python写一个简单的爬虫功能
- 用python写一个简单的爬虫功能
- 用python写爬虫
- python实现简单爬虫功能
- python实现简单爬虫功能
- 网络学习(1)
- 设计模式——工厂方法模式
- c++11多线程编程(四):数据共享和竞争条件
- 有一个测试2(day3下)
- Spring AOP 详解【转载】
- WSWP(用 python写爬虫) 笔记四:实现缓存功能
- canvg处理svg转png
- Noip 1998 洛谷P1011 车站
- Java代码获取菜单列表的树形结构
- SPI初始化LCD以及参数设置问题
- TensorFlow学习笔记5——TensorFlow高效读取数据的方法
- JZOJ 5438. 【NOIP2017提高A组集训10.31】Tree
- Hashcode的作用
- openshift/origin工作记录(2)——RESTful编程接口使用