广大网民下载资源的方式还是以BT,磁力链搜索为主,而BT种子的来源就是DHT网络。
尝试着写了一个DHT的爬虫, DHT协议中,种子的获取源自两个请求,
1 get_peer,是别的客户端向你请求某个infohash的下载地址
2 announce_peer, 是某个客户端通知你他正在上传/下载某个infohash,他可以作为该infohash的源
根据这两个请求的描述,大家也不难看出,采集到的infohash的重复率很高,怎么去保证一个DHT爬虫过滤掉重复的数据呢?
一个infohash是40个字节的字符串,如0fe6b765f95746aee440fc2552dd59b58c6b5460
Infohash实际是 SHA1算法的出来的一个哈希值,实际是一个20字节的byte序列。某些语言可能不太方便直接用这么一个来做字典的KEY。
大概估计了一下种子的总数, 20个字节的SHA1,实际上是有 2的160次方信息量。数据太大不好评估,而实际的种子数,我只好通过一些成熟的资源网站去统计,如www.btkitty.com有大约 2800万的数据,www.btmay.com也有大几百万的数据。看来数据规模大概是千万级的样子。
如果要生成一个1000万条数据的字典,大概需要 40 * 1000 * 10000 字节= 400M 内存。这个大小的内存对于现在的VPS配置来说其实也还好,但是爬虫不可能瞬间爬完所有的数据,为了记录上一次爬了哪些,这400M的字典会定时写一下磁盘。400M写次硬盘就是好几秒钟的事了,如果数据规模比想象中的更大一点,字典到了TB级别的,那就更夸张了。
所以这里可以用布隆过滤器,当然也有些其他的措施,但是布隆过滤器在这里是一个很优秀的选择。能够用相对小的内存占用解决去重的问题,但是布隆过滤器也有些缺点。详细的大家可以找下布隆过滤器的说明,网上布隆过滤器大都是概念上的说明,很少有源码的实现。本身也是因为布隆过滤器需要根据具体的环境设计其算法,这里我就直接附上详细的设计思路了。
我是将infohash 用6个hash函数映射到了6个3字节的小字典里。字典的直接存成了定长的bytearray, 因此内存占用固定为0xFFFFFF/8 * 6 = 12MB理论上能容纳的信息量是2**144。实际用100万的数据做单元测试,出错率为0。所以实际上还能更节省的,比如说少用一个hash算法或者将其中几个hash的长度再减少一点
思路清晰了,编码就很简单了,需要实现的接口就是set_flag check_flag save load
//判断是否重复。 6个hash算法都是True才是True
def check_flag(s):
s = s.lower()
for i in xrange(6):
n = bloom_filter_func_list[i](s)
n1 = n >> 3
n2 = n & 7
b = bloom_filter_map_list[i]
if b[n1] & (1 << n2) == 0:
return False
return True
至于几种hash算法,推荐用djb, dek, 另外infohash本身就是hash算法生成的,直接从里面截取几个字节都是不错的选择,如int(infohash[24:30], 16)