在BitTorrent技术出世以来,很多网络资源被制作成种子进行传播,随后磁力链接加速了资源的传播。然后非常好奇,如今世界人民对什么资源需求大呢,所以在网上查询了许多资料和源代码,完成了这个爬虫。源代码在Github:https://github.com/1205628673/BT-Spider

首先我们先知道什么是" DHT网络"

如上图所示,每个人的机器都是一个"Peer",即对端...,而中间的那个服务器,就是中心叫做Tracker服务器,一些迅雷之流就是这种存在

既然是p2p,那么肯定也能去中心化,即上图去掉Tracker服务器。这样一来,每个使用BT下载软件的人,就遵循规则自动做种,那么在网络的其他端就能通过BEP协议下载到这个种子

什么是BEP协议,这里不多赘述,几个链接大家去阅读:

http://www.bittorrent.org/beps/bep_0005.html

大致意思就是在这个DHT网络中,只有遵循B编码发送标准消息才能得到答复,而消息主要分为几种:

ping

最基本的查询是ping。”q“=”ping”ping查询只有一个参数,“id”值是一个20字节的字符串,按网络字节顺序包含发送者节点id。对ping的适当响应只有一个键“id”,其中包含响应节点的节点id。

格式:

ping Query = {"t":"aa", "y":"q", "q":"ping", "a":{"id":"abcdefghij0123456789"}}
bencoded = d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:y1:qe
Response = {"t":"aa", "y":"r", "r": {"id":"mnopqrstuvwxyz123456"}}
bencoded = d1:rd2:id20:mnopqrstuvwxyz123456e1:t2:aa1:y1:re

find_node

Find node用于查找给定ID的节点的联系信息。“q”=“Find_node”Find_node查询有两个参数,“ID”包含查询节点的节点ID,而“target”包含查询者所查找的节点的ID。当一个节点接收到一个find_node查询时,它应该用一个键“nodes”和一个字符串的值来响应,该字符串包含目标节点或其自身路由表中K(8)个最近的好节点的压缩节点信息。

格式:

find_node Query = {"t":"aa", "y":"q", "q":"find_node", "a": {"id":"abcdefghij0123456789", "target":"mnopqrstuvwxyz123456"}}
bencoded = d1:ad2:id20:abcdefghij01234567896:target20:mnopqrstuvwxyz123456e1:q9:find_node1:t2:aa1:y1:qe
Response = {"t":"aa", "y":"r", "r": {"id":"0123456789abcdefghij", "nodes": "def456..."}}
bencoded = d1:rd2:id20:0123456789abcdefghij5:nodes9:def456...e1:t2:aa1:y1:re

由于我们这是DHT爬虫,所以就不实现Routetable了,只需要将自身id和对端id简单拼一下就行

get_peer

获取与torrent infohash关联的对等方。”q“=”get_peers“get_peers查询有两个参数,“id”包含查询节点的节点id,“info_hash”包含torrent的infohash。如果查询的节点有infohash的对等节点,则它们将作为字符串列表以键“value”的形式返回。每个字符串包含单个对等点的“压缩”格式对等信息。如果查询的节点没有infohash的对等节点,则返回一个键“nodes”,其中包含查询节点路由表中最接近查询中提供的infohash的K个节点。在这两种情况下,“token”密钥也包含在返回值中。token值是将来的announce_peer查询所必需的参数。标记值应为短二进制字符串。

格式:

get_peers Query = {"t":"aa", "y":"q", "q":"get_peers", "a": {"id":"abcdefghij0123456789", "info_hash":"mnopqrstuvwxyz123456"}}
bencoded = d1:ad2:id20:abcdefghij01234567899:info_hash20:mnopqrstuvwxyz123456e1:q9:get_peers1:t2:aa1:y1:qe
Response with peers = {"t":"aa", "y":"r", "r": {"id":"abcdefghij0123456789", "token":"aoeusnth", "values": ["axje.u", "idhtnm"]}}
bencoded = d1:rd2:id20:abcdefghij01234567895:token8:aoeusnth6:valuesl6:axje.u6:idhtnmee1:t2:aa1:y1:re
Response with closest nodes = {"t":"aa", "y":"r", "r": {"id":"abcdefghij0123456789", "token":"aoeusnth", "nodes": "def456..."}}
bencoded = d1:rd2:id20:abcdefghij01234567895:nodes9:def456...5:token8:aoeusnthe1:t2:aa1:y1:re

announce_peers

宣布控制查询节点的对等方正在端口上下载torrent。announce_peer有四个参数:“id”包含查询节点的节点id,“info_hash”包含torrent的infohash,“port”包含端口作为整数,以及响应先前get_peers查询而接收的“token”。查询的节点必须验证令牌以前是否发送到与查询节点相同的IP地址。然后,被查询节点应将查询节点的IP地址和提供的端口号存储在其对等联系人信息存储中的infohash下。

有一个名为implied_port的可选参数,其值为0或1。如果存在且非零,则应忽略端口参数,而应将UDP数据包的源端口用作对等端口。这对于NAT后面的可能不知道其外部端口的对等机很有用,并且支持uTP,它们接受与DHT端口在同一端口上的传入连接。

格式:

announce_peers Query = {"t":"aa", "y":"q", "q":"announce_peer", "a": {"id":"abcdefghij0123456789", "implied_port": 1, "info_hash":"mnopqrstuvwxyz123456", "port": 6881, "token": "aoeusnth"}}
bencoded = d1:ad2:id20:abcdefghij012345678912:implied_porti1e9:info_hash20:mnopqrstuvwxyz1234564:porti6881e5:token8:aoeusnthe1:q13:announce_peer1:t2:aa1:y1:qe
Response = {"t":"aa", "y":"r", "r": {"id":"mnopqrstuvwxyz123456"}}
bencoded = d1:rd2:id20:mnopqrstuvwxyz123456e1:t2:aa1:y1:re

这里的info_hash值要注意,很多人包括我一开始弄错,磁力链接的infohash是经过hex编码的,如果我们需要用这个infohash去get_peer或者后面使用infohash去下载matadata,我们需要把infohash进行hex解码,存储的infohash是先hex编码在存起来,不然看起来都是乱码

编码的infohash:

F51CE16FB39313132EE9BF2631F217213C63043C

解码后的infohash:

\xf5\x1c\xe1o\xb3\x93\x13\x13.\xe9\xbf&1\xf2\x17!<c\x04<

注意重点长度不同,解码后的infohash长度是20字节,而编码的是40字节,协议中要求infohash和id都是20字节的,很多人发送请求没有回复都是没注意这里。基本上如果发送消息没有得到回复,那么肯定是你的消息报文写错了,导致没有遵循BEP协议被其他Peer拒绝,可能是哪里长了,哪里字母写错了仔细找找

在我们成功加入DHT网络后,我们从其他Peer接收到其他节点发送的报文了,主要处理get_peer、announce_peer消息,其他消息正常处理

async def handle_announce_peer(self,infohash,addr,peer_addr):print(infohash)'''sql = "INSERT INTO torrent_info(infohash,addr) values('%s','%s','%s')"data = (infohash,addr[0],addr[1])cursor.execute(sql % data)connect.commit()'''if self.check_country(peer_addr[0]):if infoQueue.qsize() <= 5000:infoQueue.put((infohash,peer_addr))
async def handle_get_peers(self,infohash,addr):#print(infohash)sql = "INSERT INTO torrent_info(infohash,addr) values('%s','%s','%s')"data = (infohash,addr[0],addr[1])cursor.execute(sql % data)connect.commit()if self.check_country(addr[0]):#infoQueue.put((infohash,addr[0])) 

这里check_country(),处理和一下节点的国家,只要国内和本子国的种子

可以去百度的API请求

https://sp0.baidu.com/8aQDcjqpAAV3otqbppnN2DJv/api.php?query="+172.1.35.666+"&co=&resource_id=6006&t=1586779286008&ie=utf8&oe=gbk&cb=op_aladdin_callback&format=json&tn=baidu&cb=jQuery1102024254573672262314_1586779227570&_=1586779227662

整个爬虫有现成的轮子,我也是继承这个轮子在开发的

这个是基于异步的种子爬虫maga

https://github.com/whtsky/maga/blob/master/maga.py

下载metadata的代码借鉴的'ok搜搜'的源代码

然后运行一下,得到很多的infohash,当然有重复的...

有了infohash没用啊,就是一串字母,这里就需要用BEP-0009、BEP-0010协议来去peer端下载metadata了

http://www.bittorrent.org/beps/bep_0009.html

http://www.bittorrent.org/beps/bep_0010.html

先遵循BEP-0010来和Peer完成握手

整个下载过程需要用到

  • info_hash
  • Peer的ip地址(address)
  • Peer的下载端口(port)

与Peer的通信分为几步:

(BEP-9、BEP-10的消息前都要加上整个消息的长度,除了握手报文)

1.HandShake(握手)

发送格式为  19的ASCII码+BitTorrent protocol+\x00\x00\x00\x00\x00\x01\x00\x00+infohash的十六进制解码+二十字节长的

nodeid

def send_handshake(self,_socket,infohash):bt_header = chr(len(BT_PROTOCOL)) + BT_PROTOCOLbt_ext_byte = "\x00\x00\x00\x00\x00\x10\x00\x00"peerId = self.random_node_id(20)infohash = base64.b16decode(infohash)msg = bt_header.encode() + bt_ext_byte.encode() + infohash + peerIdpacket = self.send_packet(_socket,msg)return packet

HandShake握手后,我们来看看报文是怎么样的,打开wireshake

 看得出HandShake双方的报文都有 BitTorrent protocol 这个协议头,和HTTP有点类似呢都是应用层的

2.Extend Hand Shake (扩张协议)

发送格式为  消息长度+MSG_ID的ASCII + EXTEND_ID的ASCII + B编码的字典{'m':{'ut_metadata':1}}

def send_ext_handshake(self,_socket): msg = chr(BT_MSG_ID).encode() + chr(EXT_HANDSHAKE).encode() + bencoder.bencode({"m":{"ut_metadata":1}}) packet = self.send_ext_message(_socket,msg) return packet

MSG_ID是20,由于是扩展握手,EXTEND_ID是0

再来看看Extend HandShake的报文

可以看到Peer的响应报文,里面包含了两个我们下一步用得到的键值:ut_metadata、和metadata_size

3:Request Metadata

我们在握手完毕,收到ut_metadata、metadata_size后就能进行下载了

为什么需要这两个值,因为请求的格式为  消息长度+MSG_ID的ASCII+ut_metadata的ASCII+B编码的字典{'msg_type':0,'piece':piece}

piece值为分片标记,协议中说,一个piece分片的长度为 16KB=16*1024B,所以我们需要拿metadata_size和16*1024除法计算分片标记

for piece in range(int(math.ceil(metadata_size/(16*1024)))):packet = self.request_metadata(_socket,2,piece)index = packet.find('ee'.encode())if index != -1:packet = packet[index+2:]metadata = metadata + packet

这里的ut_metadata必须是2,不然Peer不会给你回复,之前我这里弄错,从Extend回复报文中取,一直取得50 简直了...

然后就是socket.recv来接收下载的数据,这里有个小插曲,下载大文件一定要while来下,不能一次性recv,不然接收不完的,只能收到第一次回复,大家要注意...

def send_and_recv_all(self,_socket,msg,timeout = 5):msgLen = pack(">I",len(msg))msg = msgLen+msg_socket.send(msg)total = b''while True: try:data = _socket.recv(1024)if data:total = total + dataexcept socket.timeout as e:print(str(e))breakreturn total

像上面的代码,由于Peer给你返回数据后不会像HTTP服务器那样结束给你返回一个空白符,所以我们这里要等待下载完毕的timeout,而且既然是多线程下载,我用不了非阻塞(有其他高效率办法的小伙伴可以说说)

Request Matedata下载到一个B编码的字典,里面包含了我们想要的数据,这个数据处理一下就能写到文件里变成BT种子了

老样子,看看请求报文和响应报文

这个wireshake很蛋疼,看不出里面的数据,可能是我操作有误,知道的小伙伴可以告诉我为什么吗

最后一步

对metadata进行处理。因为要写成种子,返回的报文里是一个B编码的字典,里面包含我们需要的Metadata,处理的代码

if b'files' in detail:info['files'] = []for x in detail[b'files']:if b'path.utf-8' in x:v = {'path': b'/'.join(x[b'path.utf-8']).decode('utf-8', 'ignore'), 'length': x[b'length']}else:v = {'path': b'/'.join(x[b'path']).decode('utf-8','ignore'), 'length': x[b'length']}if b'filehash' in x:v['filehash'] = base64.b16encode(x[b'filehash'])info['files'].append(v)info['length'] = sum([x['length'] for x in info['files']])else:info['length'] = detail[b'length']info['data_hash'] = base64.b16encode(detail[b'pieces'])

根据之前request_metadata和recv_all函数对返回截取部分内容,然后再这样处理就成了种子的内容,对info字典进行B编码后写入文件中就成种子了,当然随便存一下库,这样就能把infohash和种子名字关联,以后弄成种子搜索库..(狗头)

最后运行一段时间的战果:

啧啧啧啧啧啧...

很奇怪的是,我下载的infohash都是从announce_peer来的,但是我连接对方主机缺被拒绝,不是对面端口关闭就是连接不上,之前还是获取国内和本子的消息,然鹅国内和本子的Peer很多时候连不上,本子连不上就算了国内的还连不上,就很迷。。。懂得为什么小伙伴联系我和我说说...

马克思说的好,矛盾是事物运动、发展和前进的源泉和动力,最后大家可以发表发表想法,有什么错的地方欢迎指出!

DHT爬虫和使用BEP协议完成metadata的下载(BT下载)相关推荐

  1. 用Python养一只DHT爬虫

    DHT是什么 DHT全称叫分布式哈希表(Distributed Hash Table),是一种分布式存储方法.在不需要服务器的情况下,每个客户端负责一个小范围的路由,并负责存储一小部分数据,从而实现整 ...

  2. 最近用.NET实现DHT爬虫,全.NET实现

    最近用.NET实现DHT爬虫,全.NET实现,大家可以加我QQ交流下  309159808 转载于:https://www.cnblogs.com/oshoh/p/9236186.html

  3. [网络爬虫|smtp协议|python]东方财富网爬虫,python smtp协议发送爬取数据至QQ邮箱

    本文改自 [网络爬虫|smtp协议|python]东方财富网爬虫,python smtp协议发送爬取数据至QQ邮箱 之前写的爬虫单子,代码已经跑了快3个月了,后续又增加了一些需求,修改了一些小bug ...

  4. 天猫双12爬虫(福利:266万条商品数据免费下载)

    前言: 继:<天猫双11爬虫(福利:212万条商品数据免费下载)>. 天猫双12商品原始数据\color{red}{天猫双12商品原始数据} 链接:http://pan.baidu.com ...

  5. FTP协议中的登录 上传 下载 新建目录 删除目录 的wireshark包分析(一文看完TCP包分析,附源文件,ppt,操作视频)

    ​​​​​​​目录 一原理 二.FTP登录 三.FTP下载 四.FTP上传 五.FTP新建目录 六.FTP删除目录 一原理 前言:TCP/IP四层模型和OSI模型对照,以及FTP在模型中的位置. • ...

  6. c语言bt下载程序,用C语言开发一个BT下载软件(一) ------ BitTorrent协议 -1

    BitTorrent(简称BT)是一个文件分发协议,每个下载者在下载的同时不断地向其他下载者上传已下载的数据.它是属于一个应用层的协议. 基于BT协议的文件分发系统由以下几个实体构成: 一个web服务 ...

  7. 用C语言开发一个BT下载软件(一) ------ BitTorrent协议 -1

    BitTorrent(简称BT)是一个文件分发协议,每个下载者在下载的同时不断地向其他下载者上传已下载的数据.它是属于一个应用层的协议. 基于BT协议的文件分发系统由以下几个实体构成: 一个web服务 ...

  8. Python爬虫实战(5)斗图啦表情包下载(单线程)

    Python爬虫实战(5)斗图啦表情包下载(单线程) 网页分析 代码 注意事项 文件名的中文标点 文件后缀os.path.splitext 网页反爬虫 输出结果 总结 利用BeautifulSoup ...

  9. 天猫双11爬虫(福利:212万条商品数据免费下载)

    2016年12月12日更新:<天猫双12爬虫(福利:266万条商品数据免费下载)> 背景: 2016年11月11日,中午刷了一下天猫,突然来了兴致想要把天猫上参与双11活动的商品都爬下来. ...

  10. python实现bt下载器_使用Python编写基于DHT协议的BT资源爬虫

    关于DHT协议 DHT协议作为BT协议的一个辅助,是非常好玩的.它主要是为了在BT正式下载时得到种子或者BT资源.传统的网络,需要一台中央服务器存放种子或者BT资源,不仅浪费服务器资源,还容易出现单点 ...

最新文章

  1. 客制化键盘键位修改_IQUNIX Slim87 RGB机械键盘评测
  2. 在.NET中调用存储过程
  3. 电脑突然卡主动不了了_必看!电脑运行卡或软件卡死无响应,怎么办?
  4. 云中台技术架构_为什么开放基础架构在云中很重要
  5. 安装pycrypto 2.6.1各种坑
  6. 保存多序列tiff文件_解码TIFF文件
  7. python接口自动化发送get请求 详解(一)
  8. 【Spring揭秘】Spring简介
  9. 大学英语综合教程三 Unit 1至Unit 8 课文内容英译中 中英翻译
  10. 《HTTP权威指南》思维导图一览全书
  11. 后渗透权限维持的方法
  12. ubuntu16.04使用阿路比-LPMS-IG1进行ros数据发布
  13. 惊闻女程序员公开举报:摩拜单车前端组 leader 性骚扰下属!
  14. JAVA高考加油,2017年高考加油祝福语
  15. 计算机win7几位,Win7 32位与64位有什么区别 Win7系统32位和64位的区别科普篇
  16. 详解什么是跨链桥?你需要了解这六点 |Tokenview
  17. python微博文本分析_基于Python的微博情感分析系统设计
  18. android 重力感应的使用
  19. GAE、SAE和BAE的对比分析
  20. ZBrush 2018 Essential Training ZBrush 2018基本培训 Lynda课程中文字幕

热门文章

  1. python新年快乐代码_新年快乐! python实现绚烂的烟花绽放效果
  2. 使用Syncthing文件同步工具在两台Windows server服务器实现文件同步(实战)
  3. Matplotlib库学习笔记(4) pyplot的文本显示以及Latex的使用
  4. 抖音短视频去水印教程
  5. 内存带宽stream测试
  6. [HAOI2009]毛毛虫(树的直径)
  7. 关于小米手机用微信会重启的问题
  8. 【深度学习基础-02】概念学习-例子3则
  9. 怎么还原计算机字体库,电脑字体怎么恢复默认设置
  10. 台式计算机无故重启,台式电脑突然自动重启是怎么回事