用python寫網絡爬蟲三:磁盤下載緩存

下載緩存:假設我們對同一個網站進行了多次下載,在百萬個網頁的情況下是不明智的,所以我們需要緩存,下過一次的不再重復下載,這樣我們能夠節省很多時間
為鏈接爬蟲添加緩存支持:
在下載之前進行判斷,該鏈接是否含有緩存,只在沒有緩存發生下載是觸發限速功能,

支持緩存功能的代碼實現.png

完整代碼地址
為 了支持緩存功能, 鏈接爬蟲的代碼也需要進行一些微調 , 包括添
加 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安全的映射為跨平臺的文件名


主流文件系統的限制.png

為了保證在不同的文件系統中文件路徑都是安全,我們需要限制其只能包含數字字母和基本符號,并將其他字符轉換為下劃線,
代碼實現:

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后就會造成一個非法的文件名。例如:

       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+樹的算法進行索引。但我們不會自己實現這種算法,而是在下一節中介紹已實現這類算法的數據庫

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容