开源地址: https://github.com/callmelanmao/dhtspider 。
开源的dht爬虫已经有很多了,有php版本的,python版本的和nodejs版本。经过一些测试,发现还是nodejs版本的爬虫效率最高,测试使用的是github上面的已有开源项目, https://github.com/dontcontactme/p2pspider/ 。
p2pspider开发的时候es2015才刚出来,所以决定用es2015把p2pspider项目重写一遍,顺便深入学习一下dht爬虫的原理。
dht爬虫总体分成两个模块。
dht模块实现一个dht节点,用来和网上的其他dht节点进行通信,在通信的过程中完成收集磁力链接的任务。
bt模块实现一个bt协议的客户端程序,当发现一个磁力链接可以下载的时候,通过bt客户端和远程的服务器通信,下载种子的元数据,
对等计算(Peer to Peer,简称p2p)可以简单的定义成通过直接交换来共享计算机资源和服务,而对等计算模型应用层形成的网络通常称为对等网络。相信大家都用过迅雷,就不多说了。
DHT(Distributed Hash Table,分布式哈希表),DHT由节点组成,它存储peer的位置,是一种分布式存储方法。在不需要服务器的情况下,每个客户端负责一个小范围的路由,并负责存储一小部分数据,从而实现整个DHT网络的寻址和存储,其中BT客户端包含一个DHT节点,用来联系DHT中其他节点,从而得到peer的位置,进而通过BitTorrent协议下载。
简单来说DHT就是负责管理提供信息和服务节点的管理与路由功能,两个需要区分的概念:
“peer” 是在一个 TCP 端口上监听的客户端/服务器,它实现了 BitTorrent 协议。
“节点” 是在一个 UDP 端口上监听的客户端/服务器,它实现了 DHT(分布式哈希表) 协议。
Kademlia是DHT网络的一种实现。在Kademlia网络中,距离是通过异或(XOR)计算的,结果为无符号整数。distance(A, B) = |A xor B|,值越小表示越近。
KRPC 是节点之间的交互协议,是由 bencode 编码组成的一个简单的 RPC 结构,他使用 UDP 报文发送。一个独立的请求包被发出去然后一个独立的包被回复。这个协议没有重发。它包含 3 种消息:请求,回复和错误。对DHT协议而言,这里有 4 种请求:
ping 检查一个节点是否有效
find_node 向一个节点发送查找节点的请求,在初始路由表或验证桶是否存活时使用
get_peers 向一个节点发送查找资源的请求
announce_peer 向一个节点发送自己已经开始下载某个资源的通知
一条KRPC消息由一个独立的字典组成,其中t和y关键字是每条信息都包含的
MagNet协议,也就是磁力链接。是一个通过sha1算法生成一个20字节长的字符串,P2P客户端使用磁力链接,下载资源的种子文件,然后根据种子文件下载资源。
因已有现成的脚本实现,只需要对相关协议有个大概了解就可以动手了。
#### 2.1 实现原理
伪装成DHT节点加入DHT网络中收集信息,DHT中node(加入网络的时候随机生成)与infohash都是使用160bit的表示方式,也就是40位的16进制,意味着数量级有2^160,爬虫主要收集get_peer、announce_peer这两个请求的信息
#### 2.2 get_peer
get_peers与torrent文件的infohash有关,找到待查资源是否有peer。这时KPRC中的q=get_peers,其中包含节点id和info_hash两个参数,如果被请求的节点有对应info_hash的peers,将返回一个关键字values,如果无则返回关键字nodes,同时也返回一个token,token在annouce_peer中需要携带。
参数:
{"id" : "<querying nodes id>", "info_hash" : "<20-byte infohash of target torrent>"}
回复:
{"id" : "<queried nodes id>", "token" :"<opaque write token>", "values" : ["<peer 1 info string>", "<peer 2 info string>"]}
或者
{"id" : "<queried nodes id>", "token" :"<opaque write token>", "nodes" : "<compact node info>"}
这里过来的info_hash不一定是有真实存在的
#### 2.3 announce_peer
这个请求用来表明发出announce_peer请求的节点,正在某个端口下载torrent文件。包含四个参数请求节点id、info_hash、整型端口port和tonken,收到请求的节点检查这个token,如果相同,则返回节点的IP和port等联系信息。爬虫中不能直接用announce_peer,否则很容易从上下文中判断是通报虚假资源而被禁掉。
参数:
{"id" : "<querying nodes id>", "implied_port": <0 or 1>, "info_hash" : "<20-byte infohash of target torrent>", "port" : <port number>, "token" : "<opaque token>"}
回复:
{"id" : "<queried nodes id>"}
准确的来说是bt客户端,因为到这里一步我们已经知道种子存放的服务器ip,端口,只需要通过bt协议向服务器请求对于的种子文件就可以了。
还有另外一个方法是根据磁力哈希码通过网上的种子缓存库下载种子, http://bt.box.n0808.com/ , http://torrage.info/
缓存库下载的缺陷是有可能种子不存在,而通过bt客户端下载几乎可以99%获取到完整的种子元数据,所以p2pspider的效率还是很高的。