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

以上做个简单分享吧,后续可能自己会看。如有错误指正。

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