在做爬蟲的時候,我們往往可能這些情況:
還有諸如此類的問題。
那怎麼解決這些重複爬取的問題呢?大家很可能都想到了「緩存」,也就是說,爬取過一遍就直接跳過爬取。
那一般怎麼做呢?
比如我寫一個邏輯,把已經爬取過的 URL 保存到文件或者數據庫裡面,每次爬取之前檢查一下是不是在列表或數據庫裡面就好了。
是的,這個思路沒問題,但有沒有想過這些問題:
的確是些問題,實現起來確實要考慮很多問題。
不過不用擔心,今天給大家介紹一個神器,可以幫助我們通通解決如上的問題。
介紹它就是 requests-cache,是 requests 庫的一個擴展包,利用它我們可以非常方便地實現請求的緩存,直接得到對應的爬取結果。
下面我們來介紹下它的使用。
安裝安裝非常簡單,使用 pip3 即可:
pip3installrequests-cache安裝完畢之後我們來了解下它的基本用法。
基本用法下面我們首先來看一個基礎實例:
importrequestsimporttimestart=time.time()session=requests.Session()foriinrange(10):session.get('http://httpbin.org/delay/1')print(f'Finished{i+1}requests')end=time.time()print('Costtime',end-start)這裡我們請求了一個網站,是 http://httpbin.org/delay/1,這個網站模擬了一秒延遲,也就是請求之後它會在 1 秒之後才會返迴響應。
這裡請求了 10 次,那就至少得需要 10 秒才能完全運行完畢。
運行結果如下:
Finished1requestsFinished2requestsFinished3requestsFinished4requestsFinished5requestsFinished6requestsFinished7requestsFinished8requestsFinished9requestsFinished10requestsCosttime13.17966604232788可以看到,這裡一共用了13 秒。
那如果我們用上 requests-cache 呢?結果會怎樣?
代碼改寫如下:
importrequests_cacheimporttimestart=time.time()session=requests_cache.CachedSession('demo_cache')foriinrange(10):session.get('http://httpbin.org/delay/1')print(f'Finished{i+1}requests')end=time.time()print('Costtime',end-start)這裡我們聲明了一個 CachedSession,將原本的 Session 對象進行了替換,還是請求了 10 次。
運行結果如下:
Finished1requestsFinished2requestsFinished3requestsFinished4requestsFinished5requestsFinished6requestsFinished7requestsFinished8requestsFinished9requestsFinished10requestsCosttime1.6248838901519775可以看到,一秒多就爬取完畢了!
發生了什麼?
這時候我們可以發現,在本地生成了一個 demo_cache.sqlite 的數據庫。
我們打開之後可以發現裡面有個 responses 表,裡面多了一個 key-value 記錄,如圖所示:

我們可以可以看到,這個 key-value 記錄中的 key 是一個 hash 值,value 是一個 Blob 對象,裡面的內容就是 Response 的結果。
可以猜到,每次請求都會有一個對應的 key 生成,然後 requests-cache 把對應的結果存儲到了 SQLite 數據庫中了,後續的請求和第一次請求的 URL 是一樣的,經過一些計算它們的 key 也都是一樣的,所以後續 2-10 請求就立馬返回了。
是的,利用這個機制,我們就可以跳過很多重複請求了,大大節省爬取時間。
Patch 寫法但是,剛才我們在寫的時候把 requests 的 session 對象直接替換了。有沒有別的寫法呢?比如我不影響當前代碼,只在代碼前面加幾行初始化代碼就完成 requests-cache 的配置呢?
當然是可以的,代碼如下:
importtimeimportrequestsimportrequests_cacherequests_cache.install_cache('demo_cache')start=time.time()session=requests.Session()foriinrange(10):session.get('http://httpbin.org/delay/1')print(f'Finished{i+1}requests')end=time.time()print('Costtime',end-start)這次我們直接調用了 requests-cache 庫的 install_cache 方法就好了,其他的 requests 的 Session 照常使用即可。
我們再運行一遍:
Finished1requestsFinished2requestsFinished3requestsFinished4requestsFinished5requestsFinished6requestsFinished7requestsFinished8requestsFinished9requestsFinished10requestsCosttime0.018644094467163086這次比上次更快了,為什麼呢?因為這次所有的請求都命中了 Cache,所以很快返回了結果。
後端配置剛才我們知道了,requests-cache 默認使用了 SQLite 作為緩存對象,那這個能不能換啊?比如用文件,或者其他的數據庫呢?
自然是可以的。
比如我們可以把後端換成本地文件,那可以這麼做:
importtimeimportrequestsimportrequests_cacherequests_cache.install_cache('demo_cache',backend='filesystem')start=time.time()session=requests.Session()foriinrange(10):session.get('http://httpbin.org/delay/1')print(f'Finished{i+1}requests')end=time.time()print('Costtime',end-start)這裡我們添加了一個 backend 參數,然後指定為 filesystem,這樣運行之後本地就會生成一個 demo_cache 的文件夾用作緩存,如果不想用緩存的話把這個文件夾刪了就好了。
當然我們還可以更改緩存文件夾的位置,比如:
requests_cache.install_cache('demo_cache',backend='filesystem',use_temp=True)這裡添加一個 use_temp 參數,緩存文件夾便會使用系統的臨時目錄,而不會在代碼區創建緩存文件夾。
當然也可以這樣:
requests_cache.install_cache('demo_cache',backend='filesystem',use_cache_dir=True)這裡添加一個 use_cache_dir 參數,緩存文件夾便會使用系統的專用緩存文件夾,而不會在代碼區創建緩存文件夾。
另外除了文件系統,requests-cache 也支持其他的後端,比如 Redis、MongoDB、GridFS 甚至內存,但也需要對應的依賴庫支持,具體可以參見下表:
比如使用 Redis 就可以改寫如下:
backend=requests_cache.RedisCache(host='localhost',port=6379)requests_cache.install_cache('demo_cache',backend=backend)更多詳細配置可以參考官方文檔:https://requests-cache.readthedocs.io/en/stable/user_guide/backends.html#backends。
Filter當然,我們有時候也想指定有些請求不緩存,比如只緩存 POST 請求,不緩存 GET 請求,那可以這樣來配置:
importtimeimportrequestsimportrequests_cacherequests_cache.install_cache('demo_cache2',allowable_methods=['POST'])start=time.time()session=requests.Session()foriinrange(10):session.get('http://httpbin.org/delay/1')print(f'Finished{i+1}requests')end=time.time()print('Costtimeforget',end-start)start=time.time()foriinrange(10):session.post('http://httpbin.org/delay/1')print(f'Finished{i+1}requests')end=time.time()print('Costtimeforpost',end-start)這裡我們添加了一個 allowable_methods 指定了一個過濾器,只有 POST 請求會被緩存,GET 請求就不會。
看下運行結果:
Finished1requestsFinished2requestsFinished3requestsFinished4requestsFinished5requestsFinished6requestsFinished7requestsFinished8requestsFinished9requestsFinished10requestsCosttimeforget12.916549682617188Finished1requestsFinished2requestsFinished3requestsFinished4requestsFinished5requestsFinished6requestsFinished7requestsFinished8requestsFinished9requestsFinished10requestsCosttimeforpost1.2473630905151367這時候就看到 GET 請求由於沒有緩存,就花了 12 多秒才結束,而 POST 由於使用了緩存,一秒多就結束了。
另外我們還可以針對 Response Status Code 進行過濾,比如只有 200 會緩存,則可以這樣寫:
importtimeimportrequestsimportrequests_cacherequests_cache.install_cache('demo_cache2',allowable_codes=(200,))當然我們還可以匹配 URL,比如針對哪種 Pattern 的 URL 緩存多久,則可以這樣寫:
urls_expire_after={'*.site_1.com':30,'site_2.com/static':-1}requests_cache.install_cache('demo_cache2',urls_expire_after=urls_expire_after)這樣的話,site_1.com 的內容就會緩存 30 秒,site_2.com/static 的內容就永遠不會過期。
當然,我們也可以自定義 Filter,具體可以參見:https://requests-cache.readthedocs.io/en/stable/user_guide/filtering.html#custom-cache-filtering。
Cache Headers除了我們自定義緩存,requests-cache 還支持解析 HTTP Request / Response Headers 並根據 Headers 的內容來緩存。
比如說,我們知道 HTTP 裡面有個 Cache-Control 的 Request / Response Header,它可以指定瀏覽器要不要對本次請求進行緩存,那 requests-cache 怎麼來支持呢?
實例如下:
importtimeimportrequestsimportrequests_cacherequests_cache.install_cache('demo_cache3')start=time.time()session=requests.Session()foriinrange(10):session.get('http://httpbin.org/delay/1',headers={'Cache-Control':'no-store'})print(f'Finished{i+1}requests')end=time.time()print('Costtimeforget',end-start)start=time.time()這裡我們在 Request Headers 裡面加上了 Cache-Control 為 no-store,這樣的話,即使我們聲明了緩存那也不會生效。
當然 Response Headers 的解析也是支持的,我們可以這樣開啟:
requests_cache.install_cache('demo_cache3',cache_control=True)如果我們配置了這個參數,那麼 expire_after 的配置就會被覆蓋而不會生效。
更多的用法可以參見:https://requests-cache.readthedocs.io/en/stable/user_guide/headers.html#cache-headers。
總結好了,到現在為止,一些基本配置、過期時間配置、後端配置、過濾器配置等基本常見的用法就介紹到這裡啦,更多詳細的用法大家可以參考官方文檔:https://requests-cache.readthedocs.io/en/stable/user_guide.html。
希望對大家有幫助。

End