(可直接转到文末下载)
找资源的时候,在某些网站下载到torrent文件。虽说挺常见的,但是这玩意只能用第三方软件打开,总觉得不爽。另外也是单纯出于好奇心,想看看这玩意长什么样子,一探究竟。
在网上扒拉了挺多,感觉这个文件都被他们解析烂了,挺多看起来不错的例子。但还是没有找到感觉比较合适的,而且看起来貌似也不太难,所以还是决定自己动手。
这个感觉不重要,我最后解析完得到的是一个 dict,大致长这样子:
{
"announce": ...,
"announce-list": [...],
"encoding": "UTF-8",
"info": {
"length": ...,
"name": ...,
"piece length": ...,
"pieces": ...
}
}
发现跟网上许多文章的描述还是挺符合的。
这个最重要了,这个是一切分析的基础。
Torrent 使用的编码方式名为 BEncoding,它定义了 4 种数据类型:
'test' -> 4:test
'1a2a.i' -> 6:1a2a.i
123 -> i123e
-30 -> i-30e
['str:1', -123] -> l5:str:1i-123e
{'length': 20} -> d6:lengthl20ee
这些数据类型初看很怪,之后觉得这样也还行,数据非常紧凑。
# 由基本数据类型得到以下一些标识符:
sign_num = b'0123456789' # 表示数字
sign_str = b':' # 字符串
sign_int = b'i' # 整数
sign_list= b'l' # 列表
sign_dict= b'd' # 字典
sign_end = b'e' # 整数和列表和字典的结尾
考虑到 列表 和 字典 里面塞了很多奇怪的东西,而且最主要是出于 字符串 本身的特殊性(什么都能往里面塞,但是本身格式又非常固定),所以首先要解决的问题是把字符串给提取出来。
file = 'a.torrent' # 文件名
f = open(file, 'rb')
s = f.read() # 待操作的 bytes
f.close()
# 这个函数用来获取字符串的长度
def len_str(s,i, start_safe=0):
'''### 获取字符串/字节串的长度
#### input:
- s: 整体的字符串
- i: 标识符 ":" 的位置
- start_safe: 从此位置开始往后计算,此前的屏蔽掉
#### return: 字符串的长度
'''
num=b''
j=0
while s[i-j-1:i-j] in sign_num and i-j>start_safe:
num = s[i-j-1:i-j]+num
j+=1
if len(num)>0:
return int(num)
else:
return 0
# 将里面的字符串全部提取
# 将所有的字符串全部转化为指定的字符:s
i=0
s_del_str = s
length_del=0
end_str=0
len_str_lis=[]
str_lis = [] # 提取出来的字符串
while i<len(s):
if s[i:i+1]==sign_str:
length=len_str(s,i, start_safe=end_str)
len_str_lis.append(length)
if length>0:
# print(s[i+1:i+length+1])
str_lis.append(s[i+1:i+length+1])
s_del_str = s_del_str[:i-len(str(length))-length_del]+ b's' +s_del_str[i+1+length-length_del:]
length_del += len(str(length))+length
end_str = i+length+1
i+=length
i+=1
(变量名混乱不堪,见谅~)
这里代码不需要深究(屎山),毕竟修改了很多次,结果反正能用就行。你可以在变量 str_lis 里面看到提取的结果。
至此,我们将所有的字符串数据都统一转化成了一个字符:s
这样做的合理性是:除了字符串以外的其他 3 种数据,都非常干净,只包含那些特定字符,如数字和 l, d, e。于是剩下的数据就非常简洁,我将字符串替换掉之后的数据为:
b'dsssllselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselseesssi1560759851esssdsi334632621esssi524288essee'
def len_int(s,i):
j=1
while s[i+j:i+j+1] in sign_num and i+j<len(s):
j+=1
return j-1
# 去除数字,修改为字符:i
i=0
s_del_int=s_del_str
length_del_int=0
int_lis=[]
while i<len(s_del_str):
if s_del_str[i:i+1] == sign_int:
length=len_int(s_del_str,i)
print(s_del_str[i+1:i+1+length])
int_lis.append(int(s_del_str[i+1:i+1+length]))
s_del_int = s_del_int[:i-length_del_int]+ b'i' +s_del_int[i+2+length-length_del_int:]
i+=length
length_del_int+=length+1
i+=1
至此将数字替换为了字符:i
只剩下列表(l开头e结尾)和字典(d开头e结尾),其中的字符串统一改为's',数字统一改为'i'
剩下的数据为:
b'dsssllselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselselseesssisssdsisssissee'
s_list_dict=s_del_int.replace(b'l',b'[').replace(b'd',b'{')
s_del_list_dict=s_list_dict
lis_sign_near=[]
i=0
while i<len(s_list_dict):
if s_list_dict[i:i+1]==b'[':
lis_sign_near.append(b']')
elif s_list_dict[i:i+1]==b'{':
lis_sign_near.append(b'}')
elif s_list_dict[i:i+1]==b'e':
s_del_list_dict=s_del_list_dict[:i]+lis_sign_near[-1]+s_del_list_dict[i+1:]
lis_sign_near=lis_sign_near[:-1]
i+=1
s_del_list_dict = s_del_list_dict.replace(b'[s]',b's')
print(s_del_list_dict)
得到以下结果:
b'{sss[ssssssssssssssssssssssssssssssssssssssssssssssssss]sssisss{sisssiss}}'
现在就已经是彻底干净了,已经能看出来是个 dict 了,之后就是把数据重组起来。
i=0
j=0
sign_lis=False
k_lis=[]
data=s_del_list_dict
lis_sign_near=[]
while i<len(s_del_list_dict):
if s_del_list_dict[i:i+1] in [b's',b'i'] and s_del_list_dict[i+1:i+2] not in [b']',b'}']:
j+=1
if not sign_lis:
k_lis[-1]+=1
if k_lis[-1]%2==1:
data=data[:i+j]+b':'+data[i+j:]
else:
data=data[:i+j]+b','+data[i+j:]
else:
data=data[:i+j]+b','+data[i+j:]
elif s_del_list_dict[i:i+1] in [b'[',b'{']:
lis_sign_near.append(s_del_list_dict[i:i+1])
if s_del_list_dict[i:i+1]==b'{':
k_lis.append(0)
elif s_del_list_dict[i:i+1] in [b']',b'}']:
lis_sign_near=lis_sign_near[:-1]
j+=1
data=data[:i+j]+b','+data[i+j:]
if s_del_list_dict[i:i+1]==b'}' and len(k_lis)>0:
k_lis=k_lis[:-1]
if len(lis_sign_near)>0:
if lis_sign_near[-1]==b'[':
sign_lis=True
else:
sign_lis=False
k_lis[-1]+=1
if len(lis_sign_near)>0:
if lis_sign_near[-1]==b'[':
sign_lis=True
else:
sign_lis=False
i+=1
data=data.replace(b',]',b']').replace(b',}',b'}')[:-1]
print(data)
结果十分明了:
b'{s:s,s:[s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s,s],s:s,s:i,s:s,s:{s:i,s:s,s:i,s:s}}'
由于前面把字符串和整数给销毁掉了,所有现在要把 's', 'i' 这些数据给填充回去:
# 用特殊字符作过渡
data = data.decode('utf8').replace('s','"ABBCCCDDDD"').replace('i','"EFFGG"')
# 填充
for i in str_lis:
if len(i)<100:
try:
data=data.replace("ABBCCCDDDD",str(i.decode('utf8')), 1)
except:
data=data.replace("ABBCCCDDDD","can not decode", 1)
else:
data=data.replace("ABBCCCDDDD","too long", 1)
for i in int_lis:
data=data.replace("EFFGG",str(i), 1)
# 意外情况:
while '""' in data:
data=data.replace('""','"')
while '\t' in data:
data=data.replace('\t','')
while '\n' in data:
data=data.replace('\n','-')
现在就已经完全是一个 dict 了,可以输出变量 data 查看
import json
result_json = json.loads(data)
f=open(file.replace('.torrent','.json'),'w', encoding='utf8')
f.write(json.dumps(result_json, indent=4))
f.close()
至此大功告成!
参考链接:
暂时不知道怎么上传文件,先贴出整局的代码吧。输入torrent文件路径就能使用了~
import json
# 二话不说,先读取
file=input('请输入torrent文件:\n')
if file[0] in ["'",'"']:
file=file[1:-1]
f=open(file,'rb')
s=f.read()
f.close()
# %%
# 由基本数据类型得到以下一些标识符:
sign_num = b'0123456789' # 表示数字
sign_str = b':' # 字符串
sign_int = b'i' # 整数
sign_list= b'l' # 列表
sign_dict= b'd' # 字典
sign_end = b'e' # 整数和列表和字典的结尾
# %%
# 提取字符串
# 这个函数用来获取字符串的长度
def len_str(s,i, start_safe=0):
'''### 获取字符串/字节串的长度
#### input:
- s: 整体的字符串
- i: 标识符 ":" 的位置
- start_safe: 从此位置开始往后计算,此前的屏蔽掉
#### return: 字符串的长度
'''
num=b''
j=0
while s[i-j-1:i-j] in sign_num and i-j>start_safe:
num = s[i-j-1:i-j]+num
j+=1
if len(num)>0:
return int(num)
else:
return 0
# %%
# 将里面的字符串全部提取
# 将所有的字符串全部转化为指定的字符:s
i=0
s_del_str = s
length_del=0
end_str=0
len_str_lis=[]
str_lis = []
while i<len(s):
if s[i:i+1]==sign_str:
length=len_str(s,i, start_safe=end_str)
len_str_lis.append(length)
if length>0:
# print(s[i+1:i+length+1])
str_lis.append(s[i+1:i+length+1])
s_del_str = s_del_str[:i-len(str(length))-length_del]+ b's' +s_del_str[i+1+length-length_del:]
length_del += len(str(length))+length
end_str = i+length+1
i+=length
i+=1
# %%
# 提取数字
def len_int(s,i):
j=1
while s[i+j:i+j+1] in sign_num and i+j<len(s):
j+=1
return j-1
i=0
s_del_int=s_del_str
length_del_int=0
int_lis=[]
while i<len(s_del_str):
if s_del_str[i:i+1] == sign_int:
length=len_int(s_del_str,i)
# print(s_del_str[i+1:i+1+length])
int_lis.append(int(s_del_str[i+1:i+1+length]))
s_del_int = s_del_int[:i-length_del_int]+ b'i' +s_del_int[i+2+length-length_del_int:]
i+=length
length_del_int+=length+1
i+=1
# %%
# 处理列表和字典
s_list_dict=s_del_int.replace(b'l',b'[').replace(b'd',b'{')
s_del_list_dict=s_list_dict
lis_sign_near=[]
i=0
while i<len(s_list_dict):
if s_list_dict[i:i+1]==b'[':
lis_sign_near.append(b']')
elif s_list_dict[i:i+1]==b'{':
lis_sign_near.append(b'}')
elif s_list_dict[i:i+1]==b'e':
s_del_list_dict=s_del_list_dict[:i]+lis_sign_near[-1]+s_del_list_dict[i+1:]
lis_sign_near=lis_sign_near[:-1]
i+=1
s_del_list_dict = s_del_list_dict.replace(b'[s]',b's')
# %%
# 相当难搞
i=0
j=0
sign_lis=False
k_lis=[]
data=s_del_list_dict
lis_sign_near=[]
while i<len(s_del_list_dict):
if s_del_list_dict[i:i+1] in [b's',b'i'] and s_del_list_dict[i+1:i+2] not in [b']',b'}']:
j+=1
if not sign_lis:
k_lis[-1]+=1
if k_lis[-1]%2==1:
data=data[:i+j]+b':'+data[i+j:]
else:
data=data[:i+j]+b','+data[i+j:]
else:
data=data[:i+j]+b','+data[i+j:]
elif s_del_list_dict[i:i+1] in [b'[',b'{']:
lis_sign_near.append(s_del_list_dict[i:i+1])
if s_del_list_dict[i:i+1]==b'{':
k_lis.append(0)
elif s_del_list_dict[i:i+1] in [b']',b'}']:
lis_sign_near=lis_sign_near[:-1]
j+=1
data=data[:i+j]+b','+data[i+j:]
if s_del_list_dict[i:i+1]==b'}' and len(k_lis)>0:
k_lis=k_lis[:-1]
if len(lis_sign_near)>0:
if lis_sign_near[-1]==b'[':
sign_lis=True
else:
sign_lis=False
k_lis[-1]+=1
if len(lis_sign_near)>0:
if lis_sign_near[-1]==b'[':
sign_lis=True
else:
sign_lis=False
i+=1
# 最后填充数据
data=data.replace(b',]',b']').replace(b',}',b'}')[:-1]
data=data.decode('utf8').replace('s','"temp_str"').replace('i','"temp_int"')
# %%
# 填充数据
for i in str_lis:
if len(i)<100:
try:
data=data.replace('temp_str',str(i.decode()), 1)
except:
data=data.replace('temp_str',"can't decode", 1)
else:
data=data.replace('temp_str','too long', 1)
for i in int_lis:
data=data.replace('temp_int',str(i), 1)
# 一些意外情况
while '""' in data:
data=data.replace('""','"')
while '\t' in data:
data=data.replace('\t','-')
while '\n' in data:
data=data.replace('\n','-')
# %%
# 输出为json
# 这里输出为一种肉眼可见的方便的格式(json)
# 更人性化的输出后续在此基础上自己动手修改
try:
result_json = json.loads(data) # except,主要就是上面那些意外情况
if 'info' in result_json:
print('\ninfo:')
print(json.dumps(result_json['info'], indent=4, ensure_ascii=False))
f=open(file.replace('.torrent','.json'),'w', encoding='utf8')
f.write(json.dumps(result_json, indent=4, ensure_ascii=False))
f.close()
input('\n 读取成功\n\n详细信息已保存至同名json文件中')
except:
input('\n 读取失败:字符串转为字典的时候出错\n\n赶紧滚去修bug')
有相关问题欢迎在评论区提出,说不定啥时候就看到了~