寫這篇文章的目的主要是總結一下目前知道的去重方法。文章有點雜亂看着參考。
常見URL過濾方法
第一,基於磁盤的順序存儲。
這裏,就是指把每個已經下載過的URL進行順序存儲。你可以把全部已經下載完成的URL存放到磁盤記事本文件中。每次有一個爬蟲線程得到一個任務URL開始下載之前,通過到磁盤上的該文件中檢索,如果沒有出現過,則將這個新的URL寫入記事本的最後一行,否則就放棄該URL的下載。
這種方式幾乎沒有人考慮使用了,但是這種檢查的思想是非常直觀的。試想,如果已經下載了100億網頁,那麼對應着100億個鏈接,也就是這個檢查URL是否重複的記事本文件就要存儲這100億URL,況且,很多URL字符串的長度也不小,佔用存儲空間不說,查找效率超級低下,這種方案肯定放棄。
第二 入數據庫查詢比較
即假設要存儲url A,在入庫前首先查詢url庫中是否存在 A,如果存在,則url A 不入庫,否則存入url庫。這種方法準確性高,但是一旦數據量變大,佔用的存儲空間也變大,同時,由於要查庫,數據一多,查詢時間變長,存儲效率下降。
第三 MD5入數據庫
在第二種的情況之下可以減少內存的消耗,一定程度上提高了效率。畢竟:直接用MD5對URL做編碼。MD5的結果是128 bit也就是16 byte的長度。相比於之間估計的URL平均長度100byte已經縮小了好幾倍,可以多撐好多天了。
當然,哪怕找個一個可以壓縮到極致的算法,隨着URL越來越多,終有一天會Out Of Memory。所以,這個方案不解決本質問題。
第四 Set 集合存儲
同樣建議MD5後儲存到Set集合中,畢竟可以很大程度上減少內存。
第五,基於布隆過濾器(Bloom Filter)的存儲。
使用布隆過濾器,設計多個Hash函數,也就是對每個字符串進行映射是經過多個Hash函數進行映射,映射到一個二進制向量上,這種方式充分利用了比特位。
基於內存的HashSet的方法存在一個本質的問題,就是它消耗的內存是隨着URL的增長而不斷增長的。除非能夠保證內存的大小能夠容納下所有需要抓取的URL,否則這個方案終有一天會到達瓶頸。
以上自己大多數的方法已經實現過了,在數據量較少的情況下都可以達到很好的效果。但是在數據量較大的情況下,採集速度還是很不樂觀的,今天就分享一下自己在網上看大佬寫的一個基於布隆過濾器和redis結合自己的寫的爬蟲看一下效果。
參考網站:https://blog.csdn.net/hellozhxy/article/details/80942581
https://blog.csdn.net/a1368783069/article/details/52137417
先講一下布隆過濾器的實現吧,原理我自己從網上看的很多分享頁大概瞭解了,你們也可以自行去網上看一些原理這裏就不分享了,可以給你們分享我所參考的大佬博客:
https://blog.csdn.net/a1368783069/article/details/52137417
直接上代碼結構吧
這是我的代碼結構
bloom_filter 代碼如下:
# encoding=utf-8
import redis
from hashlib import md5
class SimpleHash(object):
def __init__(self, cap, seed):
self.cap = cap
self.seed = seed
def hash(self, value):
ret = 0
for i in range(len(value)):
ret += self.seed * ret + ord(value[i])
return (self.cap - 1) & ret
class BloomFilter(object):
def __init__(self, host='localhost', port=6379, db=0, blockNum=2, key='bloomfilter'):
"""
:param host: the host of Redis
:param port: the port of Redis
:param db: witch db in Redis
:param blockNum: one blockNum for about 90,000,000; if you have more strings for filtering, increase it.
:param key: the key's name in Redis
"""
self.server = redis.Redis(host=host, port=port, db=db)
self.bit_size = 1 << 31 # Redis的String類型最大容量爲512M,現使用256M
self.seeds = [5, 7, 11, 13, 31, 37, 61]
self.key = key
self.blockNum = blockNum
self.hashfunc = []
for seed in self.seeds:
self.hashfunc.append(SimpleHash(self.bit_size, seed))
def isContains(self, str_input):
if not str_input:
return False
m5 = md5()
str_input = str_input.encode('utf-8')
m5.update(str_input)
str_input = m5.hexdigest()
ret = True
name = self.key + str(int(str_input[0:2], 16) % self.blockNum)
for f in self.hashfunc:
loc = f.hash(str_input)
ret = ret & self.server.getbit(name, loc)
return ret
def insert(self, str_input):
m5 = md5()
str_input = str_input.encode('utf-8')
m5.update(str_input)
str_input = m5.hexdigest()
name = self.key + str(int(str_input[0:2], 16) % self.blockNum)
for f in self.hashfunc:
loc = f.hash(str_input)
self.server.setbit(name, loc, 1)
if __name__ == '__main__':
""" 第一次運行時會顯示 not exists!,之後再運行會顯示 exists! """
bf = BloomFilter()
if bf.isContains('http://www.66.com'): # 判斷字符串是否存在
print('exists!')
else:
print('not exists!')
bf.insert('http://www.66.com')
寫的那麼好肯定是轉載別人的
轉載博客:https://blog.csdn.net/bone_ace/article/details/53107018 大佬勿怪 替你博客做個廣告 可以關注這位大佬。
tian_tang_img 文件代碼
import os
import time
import requests
from scrapy import Selector
from fake_useragent import UserAgent
from fanyu_spider.bloom_filter import *
class TianTangSpider:
def __init__(self):
self.bf = BloomFilter() # 初始布隆過濾器
self.md = md5() # 初始化一個md5對象
self.ua = UserAgent() # 隨機請求頭的設置
self.key_world = input('Please input the content of the picture you want to grab >:')
self.img_path = r'D:\Image\TianTangImgDownLoad\mask' # 下載到的本地目錄
if not os.path.exists(self.img_path): # 路徑不存在時創建一個
os.makedirs(self.img_path)
self.url = 'http://www.ivsky.com/search.php?q=%s' % self.key_world
def start_crawl(self):
print('being grabbed %s contents of page' % self.url)
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36'
}
try:
start_response = requests.get(url=self.url, headers=headers)
start_response = Selector(text=start_response.text)
all_img_url = start_response.xpath('//img/@src').extract()
except BaseException as e:
print(e)
else:
self.img_down(all_img_url)
next_page_url = start_response.xpath('//a[@class="page-next"]/@href').extract_first()
if next_page_url: # 判斷是否存在下一頁了。
self.url = 'http://www.ivsky.com/' + next_page_url
self.start_crawl()
else:
print('採集結束')
def img_down(self, all_img_url):
for each_img_url in all_img_url:
if self.bf.isContains(each_img_url):
print('this %s url is exists!' % each_img_url)
continue
else:
try:
self.bf.insert(each_img_url) # 插入redis數據庫方便分佈式管理爬取,當然你也可以選擇不插入
except BaseException as e:
print('插入第 %s 個urls 報錯 請檢查' % each_img_url)
else:
headers = {'User-Agent': self.ua.random}
try:
img_response = requests.get(url=each_img_url, headers=headers)
time.sleep(2)
except BaseException as e:
time.sleep(5)
print(e)
self.proxies = self.random_agent()
print('\n' + '*' * 500 + '\n' + ('this %s url is flase' % each_img_url))
continue
else:
with open('%s/%s.jpg' % (self.img_path, time.time()), 'wb')as f:
f.write(img_response.content)
if __name__ == '__main__':
tian_tang = TianTangSpider()
tian_tang.start_crawl()
以上做個簡單分享吧,後續可能自己會看。如有錯誤指正。