内存缓存
将下载的网页缓存到内存,以避免碰到相同的网页重新下载,同时提供时间限速功能。定义一个下载类
class Downloader:
def __init__(self,decay = 5,user_agent = 'wswp',proxies = None,
num_retries = 1,cache = None):
self.throttle = Throttle(decay)
self.user_agent = user_agent
self.proxies = proxies
self.num_retries = num_retries
self.cache = cache
def __call__(self,url):
result = None
if self.cache:
try:
result = self.cache[url] #直接尝试从缓存中获取
except KeyError:
pass
else:
if self.num_retries > 0 and 500 <= result['code'] < 600:
result = None #设置为 None 重新下载
if result is None:
self.throttle.wait(url)
proxy = random.choice(self.proxies) if self.proxies else None
headers = {"User-agent":self.user_agent}
result = self.download(url,headers,proxy,self.num_retries)
if self.cache:
# save result to cache
self.cache[url] = result
return result["html"]
def download(self,url,headers,proxy,num_retries,data = None):
...
return {"html":html,"code":code}
上面的代码使用 __call__ 的特殊方法实现了下载前检查缓存的功能。该方法首先检查缓存是否定义,如果已经定义,则检查之前是否缓存了改 URL。如果该 URL 已经被缓存,则检查之前的下载中是否遇到了服务端错误。最后,如果也没有发生过服务端错误,则表明该缓存结果可用。相应的链接爬虫调整如下:
def link_crawler(...,cache = None):
crawl_queue = [seed_url]
seen = {seed_url:0}
num_urls = 0
rp = get_robots(seed_url)
D = Downloader(decay = decay,user_agent = user_agent,proxies = proxies,num_retries = num_retries,cache = cache)
while crawl_queue:
url = crawl_queue.pop()
depth = seen[url]
if rp.can_fetch(user_agent,url):
html = D(url) # 调用的是特殊方法 __call__
links = []
...
磁盘缓存
实现磁盘缓存,我们得为每一个 url 准备一个目录,可以用 url 作为目录,例如:
import re
url = "http://example.webscraping.com/default/view/Australia-1"
dir = re.sub('[^/0-9a-zA-Z\-.,;_]','_',url)
print dir # http_//example.webscraping.com/default/view/Australia-1
但有一种边界情况需要考虑,那就是 URL 路径可能会以 / 结尾。比如
http://www.baidu.com/
http://www.baidu.com/1
这样的话 http://www.baidu.com/ 就是一个目录名而不是文件名,可行的解决方案是添加 index.html 作为文件名
import urlparse
url = "http://www.baidu.com/ss/"
url = urlparse.urlsplit(url) # SplitResult(scheme = 'http',netloc = 'www.baidu.com',path = '/ss/',query = '',fragment = '')
if url.path.endswith("/"):
path = url.path + "index.html"
filename = url.netloc + path + url.query
print filename # www.baidu.com/ss/index.html
讨论了 url 和文件名的映射关系后,这里开始实现将 url 缓存至硬盘的类
import os
import re
import urlparse
class DiskCache(object):
def __init__(self,cache_dir = 'cache'):
self.cache_dir = cache_dir
self.max_length = max_length
def url_to_path(self,url):
components = urlparse.urlsplit(url)
path = components.path
if not path:
path = '/index.html'
elif path.endswith('/'):
path += 'index.html'
filename = components.netloc + path + components.query
filename = re.sub('[^/0-9a-zA-Z\-.,;_ ]','_',filename)
return os.path.join(self.cache_dir,filename)
def __getitem__(self,url):
path = self.url_to_path(url)
if os.path.exists(path):
with open(path) as fp:
return pickle.load(fp)
else:
raise KeyError(url + " does not exists")
def __setitem__(self,url,result):
path = self.url_to_path(url)
folder = os.path.dirname(path)
if not os.path.exists(folder):
os.makedirs(folder)
with open(path,"wb") as fp:
fp.write(pickle.dumps(result))
在 __setitem__ 中,使用 url_to_path( ) 方法将 URL 映射为安全文件名,在必要的情况下还需要创建父目录。pickle 模块会把输入转化成字符串,然后保存到磁盘中。而在 __getitem__ 中,首先将 URL 映射为文件名,如果文件存在,则加载其内容并反序列化,不存在则跑出 KeyError 异常。
数据库缓存
在磁盘缓存的基础上,只不过是将数据写入数据库而已(比如 MongoDB),不需要将 url 映射成路径,只需要将 url 设成 id 保存到数据库即可,不再过多介绍。因为就一个写入的方式差别。python 完全可以实现 MongoDB 的插入操作。
数据压缩
数据压缩就跟爬虫关系不大了,毕竟数据压缩什么时候都可以压缩,只不过应用在爬虫上减少存储空间。实现方法很简单,只需要在保存之前使用 zlib 压缩即可。
import cPickle as pickle
import zlib
data_compress = zlib.compress(pickle.dumps(data)) # 压缩 data
zlib.decompress(data_compress) = pickle.dumps(data) # 解压缩