下載緩存:假設我們對同一個網站進行了多次下載,在百萬個網頁的情況下是不明智的,所以我們需要緩存,下過一次的不再重復下載,這樣我們能夠節省很多時間
為鏈接爬蟲添加緩存支持:
在下載之前進行判斷,該鏈接是否含有緩存,只在沒有緩存發生下載是觸發限速功能,
完整代碼地址
為 了支持緩存功能, 鏈接爬蟲的代碼也需要進行一些微調 , 包括添
加 cache 參數、 移除限速以及將 download 函數替換為新的類等, 如下面的
代碼所示。
def link_crawler(seed_url, link_regex=None, delay=5, max_depth=-1, max_urls=-1, user_agent='wswp', proxies=None, num_retries=1,scrape_callback=None, cache=None):
'''crawl from the given seed URL following link matched by link_regex'''
crawl_quene = [seed_url]
seen = {seed_url: 0}
num_urls = 0
rp = get_robots(seed_url)
D = Downloader(delay=delay, user_agent=user_agent, proxies=proxies, num_retries=num_retries, cache=cache)
while crawl_quene:
url = crawl_quene.pop()
depth = seen[url]
if rp.can_fetch(user_agent, url):
html = D(url)
links = []
磁盤緩存
將下載到的網頁存儲到文件系統中,實現該功能,需要將URL安全的映射為跨平臺的文件名
為了保證在不同的文件系統中文件路徑都是安全,我們需要限制其只能包含數字字母和基本符號,并將其他字符轉換為下劃線,
代碼實現:
import re
url = 'http://example.webscraping.com/default/view/Australia-1'
re.sub('[^/0-9a-zA-Z\-.,;_]', '_', url)
需要文件名及其父目錄的長度不超過255個字符
filename = '/'.join(segment[:255] for segment in filename.split('/'))
還有一種邊界情況,就是URL以斜杠結尾。這樣分割URL后就會造成一個非法的文件名。例如:
- http://example.webscraping.com/index/
-
http://example.webscraping.com/index/1
對于第一個URL可以在后面添加index.html作為文件名,所以可以把index作為目錄名,1為子目錄名,index.html為文件名
import urlparse
components=urlparse.urlsplit ('http://example.webscraping.com/index/')
print componts
print components.path
comonents = urlparse.urlsplit(url)
path = comonents.path
if not path:
path = '/index.html'
elif path.endswith('/'):
path += 'index.html'
filename = comonents.netloc+path+comonents.query
實現將URL到文件名的這些映射邏輯結合起來,
def url_to_path(self, url):
comonents = urlparse.urlsplit(url)
path = comonents.path
if not path:
path = '/index.html'
elif path.endswith('/'):
path += 'index.html'
filename = comonents.netloc+path+comonents.query
filename = re.sub('[^/0-9a-zA-z\-.]', '_', filename)
filename = '/'.join(segment[:255] for segment in filename.split('/'))
return os.path.join(self.cache_dir, filename)
然后在
url to path 方法中應用 了前面討論的文件名 限制。 現在, 我們還缺少根
據文件名存取數據的方法, 下面的代碼實現了這兩個缺失的方法。
def __getitem__(self, url):
path = self.url_to_path(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.has_expired(timestamp):
raise KeyError(url + 'has expired')
return result
else:
raise KeyError(url + 'doesnot exist')
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)
data = pickle.dumps((result, datetime.utcnow()))
if self.compress:
data = zlib.compress(data)
with open(path, 'wb') as fp:
fp.write(data)
通過測試發現有緩存(第二次)所需要的時間遠遠少于沒有使用緩存(第一次)的時間
節省磁盤空間:
為了節省磁盤空間我們對下載得到的html進行壓縮處理,使用zlib模塊進行壓縮
fp.write(zlib.compress(pickle.dumps(result)))
解壓
return pickle.loads(zlib.decompress(fp.read()))
清理過期數據:
我們將為緩存數據添加過期時間 , 以便爬蟲知道何時需要重新下載網頁,。在構造方法中,我們使用timedelta對象將默認過期時間設置為30天,在set方法中把當前時間戳保存在序列化數據中,在get方法中對比當前時間和緩存時間,檢查是否過期。完整代碼
class DiskCache:
def __init__(self, cache_dir='cache', expires=timedelta(days=30), compress=True):
self.cache_dir = cache_dir
self.expires = expires
self.compress = compress
def __getitem__(self, url):
path = self.url_to_path(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.has_expired(timestamp):
raise KeyError(url + 'has expired')
return result
else:
raise KeyError(url + 'doesnot exist')
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)
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):
path = self._key_path(url)
try:
os.remove(path)
os.removedirs(os.path.dirname(path))
except OSError:
pass
def url_to_path(self, url):
comonents = urlparse.urlsplit(url)
path = comonents.path
if not path:
path = '/index.html'
elif path.endswith('/'):
path += 'index.html'
filename = comonents.netloc+path+comonents.query
filename = re.sub('[^/0-9a-zA-z\-.]', '_', filename)
filename = '/'.join(segment[:255] for segment in filename.split('/'))
return os.path.join(self.cache_dir, filename)
def has_expired(self, timestamp):
return datetime.utcnow() > timestamp+self.expires
def clear(self):
if os.path.exists(self.cache_dir):
shutil.rmtree(self.cache_dir)
if __name__ == '__main__': link_crawler('http://example.webscraping.com/', '/(index|view)', cache=DiskCache())
用磁盤緩存的缺點
由于受制于文件系統的限制,之前我們將URL映射為安全文件名,然而這樣又會引發一些問題:- 有些URL會被映射為相同的文件名。比如URL:.../count.asp?a+b
,.../count.asp?a*b
。- URL截斷255個字符的文件名也可能相同。因為URL可以超過2000下字符。
使用URL哈希值為文件名可以帶來一定的改善。這樣也有一些問題:- 每個卷和每個目錄下的文件數量是有限制的。FAT32文件系統每個目錄的最大文件數65535,但可以分割到不同目錄下。- 文件系統可存儲的文件總數也是有限的。ext4分區目前支持略多于1500萬個文件,而一個大型網站往往擁有超過1億個網頁。
要想避免這些問題,我們需要把多個緩存網頁合并到一個文件中,并使用類似B+樹的算法進行索引。但我們不會自己實現這種算法,而是在下一節中介紹已實現這類算法的數據庫。