实现DHT网络上的种子爬虫

近年来,由于众所周知的原因,种子搜索逐渐变得困难了起来。一些收藏的磁力链站点或者种子搜索站都相继倒闭,笔者不得不开始思考如何更好地从网络上发掘资源这个难题。自建一个种子站也许是一个方法,正好最近种草了树莓派4,组装上了一块500G的SSD,却又不知道能拿来干什么,于是刚好可以拿来实现这个计划。

前置理论

但是,如何才能在网络上发掘种子呢?首先,爬取其他网站一定是可行的,但是很多网站更新很慢,而且效率低下而且绝大部分还得挂代理,显然不是个上策。

那是否有其他手段呢?刚好翻到了知乎上有一位答主分享了自己从BT的P2P网络上扒种子的经验[1],看了下确实有一定可行性,那么说干就干。

先看原理,当年为了解决Tracker服务器被爆破的问题,BT特地提出了基于DHT(即分布式哈希表)的去中心化方案,使得节点可以散落到网络各处保存,也就是实现了一个P2P网络。而只要连上了这个大网络,就可以在上面寻找到做种的节点,完成资源的下载,从而摆脱了对Tracker服务器的依赖。

这里面值得一提的就是运用了Kademlia这种分布式哈希表算法来实现了P2P网络[2],具体理论部分笔者只了解了皮毛,就不在这里展开了。简单的说,Kademlia算法可以通过构造一颗二叉查找树来帮助我们维护一个路由表,我们可以在路由表上完成查找节点的操作。这个路由表具备几个特点:

  • 具有一定的大小(废话,当然不能无穷大)
  • 每一个路由节点(Peer)由160比特的值构成(其实是SHA1值,可以根据规则或者随机产生)
  • 通过异或比较节点的值来计算节点间距离
  • 越靠近当前节点(距离越小)的路由记录的越详细

当我们需要寻找哪个节点在下载某个种子时,我们可以通过种子的SHA1值(即InfoHash,也就是磁力链上的神秘代码)去路由表中寻找距离这个SHA1值最近的节点,然后发起询问。如果这个节点上存储了正在下载种子的节点,那他会直接返回,否则,他会告诉我们要去哪个更近的节点查询[3]。

这里事实上隐含了一个规则,就是某个种子的下载信息会保存在网络中与种子SHA1值最接近的若干节点中,只有这样才能通过在DHT网络上的有限步迭代找到相关下载的节点,从而实现P2P的下载。

正因如此,网络上的节点也需要宣告(Announce):"我正在下载XXX资源(他的Hash值是XXX),我的IP端口是XXX,要一起下载的老铁联系我"。这样相关节点就会记录这个数据,等到别人也来下载时就可以告知其他人。

说到这里,我们就会发现这里的Announce消息可以被用于侦听网络上正在下载哪些种子文件。之后就可以和做种方建立连接了。

但是对于爬虫来说,光有InfoHash显然是不够的,你总不能去搜索一串神秘代码吧。无论如何我们都要取得这段神秘代码背后的种子文件,然后解析出种子包含的标题和文件列表,作为搜索的正文内容。这里就需要请出BT协议中的元信息发送扩展协议了[4]。当我们和对端握手后,可以通过kRPC请求向对方索要种子的元信息,即种子文件(当然对方可以不理会你的请求,如果他的客户端不支持这种操作的话)。到这里就完成了爬虫的整个采集工作。

爬取种子过程

方案选型

具体到实现我们需要考虑几个问题。

首先,为了连上DHT网络,我们必须获得公网IP地址,这样才能接收到其他节点的宣告信息。但是具体到笔者的网络,却只有内网环境,因此需要有一台公网的机器来作为嗅探节点爬取种子。这里我选取了某云的低配机器(1C/1G/50G)用于种子爬取(穷,不过主要数据存树莓派上也算节约成本)。然后我们打通机器间的网络(通过一些微屁恩软件),将嗅探结果通过虚拟的内网落地到树莓派上(4C/8G/500G)。网络拓扑如下:

网络拓扑

其次,为了减少重复劳动,笔者选取了webtorrent仓库中一些现成的代码来完成种子相关的协议处理和文件解析。因此选型上是nodejs + typescript,然后用vue3 + elementjs实现了一个种子的查找页面。

网页化

最后,存储上简单处理,使用pgsql存储种子文件的二进制(Blob),用elastic search搞定种子元文件的搜索。另外也接入了prometheus + grafana进行性能和采集状态的监控。具体代码实现按下不表,整体架构如下:

架构

这样的设计有一个好处就是可以不断扩展嗅探节点,甚至部署到海外来解决墙的问题。

一些问题

最后是一些坑,首先是嗅探部分需要上一些缓存,防止已经入库的种子反复查询或入库。然后嗅探器上需要开启足够多的DHT客户端,才能保证能监听到足够多的宣告事件。因为种子交换的实际成功率很低,所以需要尽量增加客户端来弥补不足。

最后,注意到嗅探器执行一段时间后种子的爬取速率就下降了,这是因为一个节点的NodeID启动后就固定了,根据DHT数据结构的特点我们只能和一小撮用户进行交换(他们的宣告会到我们这个Peer上),那么在一段时间后,种子交换的差不多了,整个爬取速率就掉下去了。因此需要定时重启一下客户端更换随机一个NodeID重新冷启动可以获取大量新的种子。

爬取速率特点

此外,为了优化冷启动的时间,可以考虑连接到一些诸如群晖的NAS的BT端口上,因为这些节点往往开机时间足够久,有比较多的Peer可以帮助加入网络,否则国内的情况很可能连不到常用Tracker,导致一直Bootstrap不起来。

性能数据

在运行了一段时间后,可以看到开启了50个DHT Client的嗅探节点基本稳定在50~100kB的外部网络流量,一天爬取1k~2k的种子,数量不是很多,还有优化的余地(种子交换成功率不高)。内部网络的流量大部分时间在5~10kB左右,比较省流量。树莓派保持在5~10%的CPU占用,消耗了绝大多数内存来换取性能。另外每10k种子大概会吃掉1GB左右的磁盘空间,这么算500G的磁盘放个100万种子应该没问题(诶嘿),只不过按这个速度收集到这么多种子得猴年马月了。

总结

本文介绍了基于DHT网络进行种子嗅探的方法,通过这一方法可以建立种子搜索站点来查找种子。考虑到相关技术对DHT网络造成的不良影响(影响路由),本文不附实现源码,读者可以参考网络上的其他实现或自行实现相应功能。

参考资料

  1. 实现基于BitTorrent协议的DHT磁力爬虫
  2. Kademlia: A Peer-to-Peer Information System Based on the XOR Metric
  3. DHT Protocol
  4. Extension for Peers to Send Metadata Files