日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

python爬虫实时更新数据_爬虫的增量式抓取和数据更新

發布時間:2023/12/31 python 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python爬虫实时更新数据_爬虫的增量式抓取和数据更新 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一些想法

頁面爬的多了,量上去了之后,就會遇到其他的問題,其實不管做什么技術量大了都會有問題。一般情況下,我認為解決"大量"問題的思路有兩個:一種是著力于優化系統的能力,讓原本只能一分鐘處理100條的系統提升到一分鐘1000條之類的,在我看來并行、分布式、集群都屬于這個范疇,這種思路下,系統處理的內容沒有變化只是單純的處理速度變快了;另一種是著力于提高系統的工作效率, 比如說降低某算法的復雜度。

爬蟲領域的增量式爬取屬于后者,每種網站都有每種網站的特點。比如說小說連載網站、新聞或者知乎首頁,這里拿知乎時間線舉例,我基本每天醒來和睡覺前都會刷一波知乎,從頭開始看直到看到上次載入的地方,假設我要抓取知乎的數據并保存到本地,不難發現最好的選擇其實是每次只抓取上次沒讀過的新內容,抓評論也是一樣,最優的選擇是每次只抓取在上次抓取之后出現的新評論,然后再進行保存。有的時候,還有另外一種情況,就是原本存在的網頁內容更新了,比如說有人在知乎上修改了他的回答。這時候,我們的爬蟲就需要有分辨這些區別變化的能力。但這幾個都是很簡單的例子,實際情況會復雜很多。

不管是產生新頁面,還是原本的頁面更新,這種變化都被稱為增量, 而爬取過程則被稱為增量爬取。那如何進行增量式的爬取工作呢?回想一下爬蟲的工作流程:

發送URL請求 ----- 獲得響應 ----- 解析內容 ----- 存儲內容

我們可以從幾種思路入手:

在發送請求之前判斷這個URL是不是之前爬取過

在解析內容后判斷這部分內容是不是之前爬取過

寫入存儲介質時判斷內容是不是已經在介質中存在

實現增量式爬取

不難發現,其實增量爬取的核心是去重, 至于去重的操作在哪個步驟起作用,只能說各有利弊,就像我說的,everything is tradeoff。

在我看來,前兩種思路需要根據實際情況取一個(也可能都用)。第一種思路適合不斷有新頁面出現的網站,比如說小說的新章節,每天的最新新聞等等;第二種思路則適合頁面內容會更新的網站。第三個思路是相當于是最后的一道防線。這樣做可以最大程度上達到去重的目的。

去重的方法

最簡單的去重方式自然是將所有訪問過的URL和其對應的內容保存下來,然后過一段時間重新爬取一次并進行比較,然后決定是否需要覆蓋。這顯然是不實際的,因為會消耗很多資源。目前比較實際的做法就是給URL或者其內容(取決于這個網站采用哪種更新方式)上一個標識,這個標識有個比較好聽的名字,叫數據指紋。

這里很容易想到的一種數據指紋就是哈希值,根據哈希函數的特性,我們可以為任意內容生成一個獨一無二的定長字符串,之后只要比較這個哈希值就行了。哈希值是一個很偉大的發明,幾乎在任何地方都有它的影子,它利用數學特性,計算機只要經過簡單的計算就可以得到唯一的特征值,這個計算過程的開銷基本可以忽略不計,當然這是題外話了。

不過即使用了哈希值,你仍需要一個地方存儲所有的哈希值,并且要能做到方便的取用。如果你的存儲介質是數據庫,一般的數據庫系統都能提供索引,如果把哈希值作為唯一索引呢,這應該是可行的。有些數據庫也提供查詢后再插入的操作,不過本質上應該也是索引。和哈希值類似的還有MD5校驗碼,殊途同歸。

除了自建指紋,其實在發送請求時還有一些技巧,比如說304狀態碼,Last-modified字段,文件大小和MD5簽名。具體參考[8],很好理解,就不細說了。

綜上所述,在數據量不大的時候,幾百個或者就幾千個的時候,簡單自己寫個小函數或者利用集合的特性去重就行了。如果數據量夠大,數據指紋的價值就體現出來了,它可以節省可觀的空間,同時可以引入BloomFilter作為去重的手段。另外,如果要對數據做持久化(簡單說就是去重操作不會被事故影響,比如說斷電),就需要用到Redis數據庫。

BloomFilter

布朗過濾器雖然不是因為爬蟲才出現的,但是卻在這種情況下顯得異常有用。布朗過濾器可以通過計算來判斷某項數據是否存在于集合中,它原理和概念可以參考1和英文版的維基百科Bloom filter, 里面有詳細的數學推理,它解釋了為什么布朗會有誤判情況出現,感興趣可以學習一下,并不難。這里只提幾點:

布朗過濾器是有誤判率的,它會把原本不屬于這個集合的數據誤判為屬于,但不會把原本屬于集合的數據誤判為不屬于。

它是一個典型且高效的空間換時間的例子。

它的誤判率是:

\left(1-\left[1-\frac{1}{m}\right]^{kn}\right)^k \approx \left( 1-e^{-kn/m} \right)^k

這里元素的數量n、 過濾容器的大小m(bits)和哈希函數的數量k存在的一定關系,它們三個共同確定了誤判率;同樣如果已知其中兩項,通過調整另外一項也可以達到降低誤判率的目的,具體參見Bloom Filters - the math。

Redis的集合使用

簡單來說,Redis的集合就是Redis數據庫中的集合類型,它具有無序不重復的特點。Python有redis客戶端庫,這里主要涉及到的就是SADD和SISMEMBER命令。下面會具體解釋。

具體實現

BloomFilter

這里我們使用pybloom庫,需要pip或者源碼安裝。pybloom庫用起來非常簡單,這里給兩段最基本的代碼:

from pybloom import BloomFilter

# 新建一個過濾器,長度為m,錯誤率為0.1%

bf = BloomFilter(capacity=1000, error_rate=0.001)

'''

不難理解,這句就相當于

for x in range(bf.capacity):

bd.add(x)

但說實話這種寫法我第一次見到

'''

[bf.add(x) for x in range(bf.capacity)]

print (0 in bf)

print (5 in bf)

print (10 in bf)

# 這里是計算它的錯誤率

count = 0

amount = bf.capacity

for i in range(bf.capacity, bf.capacity + amount + 1):

if i in bf:

count += 1

print ("FP: {:2.4f}".format(count / float(amount)))

我從網上搜到文章大多只是介紹了如何新建一個Filter、怎么add以及查看元素是否屬于這個Filter。實際上,如果閱讀過源碼,其實filter還提供了很多其他方法,同時這個庫還提供了一個可自動擴展的Filter,作者比較推薦后者。

from pybloom import BloomFilter

# 新建

bf1 = BloomFilter(capacity=1000, error_rate=0.001)

bf2 = BloomFilter(capacity=1000, error_rate=0.001)

# 添加

[bf1.add(x) for x in range(3)]

[bf2.add(x) for x in range(3,6)]

# 復制

bf3 = bf1.copy()

# | 操作,三種都行

bf3.union(bf1)

bf3 = bf3 | bf2

bf3 = bf3 or bf2

# & 操作, 三種都行

bf3.intersection(bf1)

bf3 = bf3 & bf1

bf3 = bf3 and bf1

# 成員變量和支持的操作符

len(bf3)

3 in bf3

bf3.capacity

bf3.error_rate

# 也支持tofile和fromfile操作

# 具體的代碼可參照源碼中tests.py中的test_serialization()方法

可擴展的過濾器:

from pybloom import ScalableBloomFilter

# 新建, mode目前只有2種

# SMALL_SET_GROWTH = 2, LARGE_SET_GROWTH = 4

# 前者占內存少但速度慢,后者消耗內存快但速度快

bf1 = ScalableBloomFilter(initial_capacity=100, error_rate=0.001, mode=ScalableBloomFilter.SMALL_SET_GROWTH)

bf2 = ScalableBloomFilter(initial_capacity=1000, error_rate=0.001, mode=ScalableBloomFilter.LARGE_SET_GROWTH)

# 添加

[bf1.add(x) for x in range(3)]

[bf2.add(x) for x in range(3,6)]

# 兩個屬性(裝飾器)

bf1.capacity

bf1.count

# 成員變量和支持的操作符

len(bf1)

3 in bf1

bf1.initial_capacity

bf1.error_rate

# 也支持tofile和fromfile操作

# 具體的代碼可參照源碼中tests.py中的test_serialization()方法

這里我建議看下這個庫源碼,核心部分差不多500行,里面很多寫法很值得學習,而且都很容易理解。里面也涉及到了如何選取哈希函數。

Redis

Python的Redis客戶端庫也是開源的,地址是:redis-py。不過在開始之前,你首先需要一個有Redis數據庫運行的主機(搭建一個很簡單)。

這里需要解釋不少東西,首先,上文中有一節本來不叫“Redis的集合”而是叫“Redis集合”,我一開始以為這是一種名叫Redis的特殊集合,然后這個集合帶有不可插入重復內容的特性,事實上這里大錯特錯了。還記得我們的初衷是“去重”,實際上,包括Python在內的很多語言已經實現了具有無序不重復特性的內置數據結構:集合(Set)。也就是說從去重這點看的話,有集合這種數據結構就夠了,跟Redis并沒有什么關系。

那么Redis是什么?它是一種數據庫,它的官網是這樣描述的:

Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker.

關于Redis數據庫還有幾個關鍵詞:key-value,高性能,數據持久化,數據備份,原子操作以及跟這里相關的一個特性:支持集合數據類型。這才是為什么做增量爬取時我們要用到Redis數據庫:我們可以通過將URL或者頁面內容的指紋作為key存入Redis數據庫中的集合里,利用集合的不重復性達到去重的目的,每次爬蟲要處理URL或者頁面時會先去Redis數據庫里檢查一下是否已經存在,因為Redis數據庫著力于key-value形式的存儲,所以這一步的速度將會很可觀;其次Redis可以將內存中的內容持久化到磁盤,并且其每一次操作都是原子操作,這就保證了爬蟲的可靠性,即爬蟲不會應為意外停止而損失數據。

說了這么多,現在就能知道為什么這里要用到Redis的集合。如果只考慮本文相關的內容,那么和本文有關的Redis數據庫操作命令只有兩個:SADD和SISMEMBER,前者可以向集合中插入一條數據,成功返回1,失敗返回0;后者可以查詢某元素是否在集合中存在,存在返回1,不存在返回0。

我在一臺虛擬機Ubuntu-14.04上安裝了Redis數據庫并配置了遠程連接,客戶端測試如下:

>>> import redis

>>> r = redis.StrictRedis(host='192.168.153.131', port=6379, db=0)

>>> r.sadd('1','aa')

1

>>> r.sadd('1','aa')

0

>>> r.sadd('2','aa')

1

>>> r.sadd('3','aa')

1

>>> r.sismember('1','aa')

True

>>> r.sismember('1','b')

False

>>>

但應該如何將這一特性融入到爬蟲中呢?如果是自己寫的爬蟲代碼,添加上述代碼即可;如果使用的是scrapy框架,我們可以在middleware上下功夫,在spider模塊收到要處理的URL時,寫一個Spider中間件用來判斷這條URL的指紋是否在Redis數據庫中存在,如果存在的話,就直接舍棄這條URL;如果是要判斷頁面的內容是否更新,可以在Download中間件中添加代碼來校驗,原理一樣。當然,數據庫的操作可以用類似write()和query()的方法進行封裝,此處不表。

參考

總結

以上是生活随笔為你收集整理的python爬虫实时更新数据_爬虫的增量式抓取和数据更新的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。