Python-基於布隆過濾器下URL去重實例。

寫這篇文章的目的主要是總結一下目前知道的去重方法。文章有點雜亂看着參考。

常見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()

以上做個簡單分享吧,後續可能自己會看。如有錯誤指正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章