news 2026/10/30 8:41:29

PHP实现BT种子转磁力链接:bencode解析与info_hash计算

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PHP实现BT种子转磁力链接:bencode解析与info_hash计算

简介:这份资源面向PHP开发者与P2P文件分享场景的学习者,聚焦BT种子文件与磁力链接之间的转换问题,适合具备一定PHP基础、希望理解DHT与BitTorrent元数据机制的中级开发者参考。压缩包共5个文件,以4个PHP脚本和1份Markdown说明为主,整体仅约3KB,体量轻巧:PHP文件承担种子解析、Bencode编解码与转换入口等核心逻辑,Markdown文档则用于补充使用说明与实现思路。已有4101人学习下载,说明该主题在文件分享与分布式存储方向具有稳定的关注度。读者可从中获得一套可运行的转换脚本,理解如何解析种子文件info部分、计算SHA1得到Info Hash,并按magnet:?xt=urn:btih格式拼接文件名与Tracker参数,同时接触Bencode编解码这一BT协议基础环节,为后续扩展P2P下载工具或研究分布式文件定位提供可复用的代码起点。

1. 一个 PHP 脚本把种子文件转成磁力链接,到底值不值得拆

手里攒了一堆.torrent文件,想批量转成磁力链接丢进自己的资源索引库,结果发现网上能用的在线转换站要么限速、要么夹广告、要么转完的链接缺dn参数看着别扭。这个基于 PHP 实现的 BT 种子转磁力链接工具包,解决的正是这个具体问题:读种子文件、解析 bencode 结构、算出 info_hash、拼出标准磁力链接。它适合谁?适合手里有 PHP 环境、想在自己服务器上跑一个转换接口的从业者,也适合想搞明白磁力链接到底怎么从种子文件里「长」出来的开发者。整包是源码形式,不是编译好的二进制,意味着你能改、能嵌进自己的采集流程,而不是被一个黑匣子卡住。

2. 拆开 bencode:种子文件里到底藏了什么

2.1 为什么不能直接字符串截取

很多人第一次处理.torrent会想当然地去找announce后面的 URL,或者用正则抠name字段。这条路走不通,因为种子文件是 bencode 编码,一种前缀式、自描述的二进制格式。它的规则只有四条:整数写成i<数字>e,字符串写成<长度>:<内容>,列表写成l<元素>e,字典写成d<键值对>e,且字典的键必须按字节序升序排列。字符串长度前缀是字节数不是字符数,中文种子名一个汉字占三字节,长度前缀写错一位,后面整个解析全崩。

磁力链接的核心是xt=urn:btih:<info_hash>,这个 hash 不是整个文件的哈希,而是种子里info这个字典原始字节的 SHA1。注意是原始字节,不是解析后再序列化的结果。这是整个工具最关键的原理点:你必须保留info字典在文件中的原始切片,直接对它做 SHA1,任何「解析成数组再重新编码」的操作都可能因为键序、整数编码差异导致 hash 对不上。血泪经验是,重编码出来的种子看着结构一样,算出的 info_hash 却和主流客户端不一致,链接自然下不动。

2.2 解析器的骨架代码

下面这段是 bencode 解析的核心,思路是游标推进,遇到字典时记录info键对应值的起止偏移,供后面切片用。

<?php // 极简 bencode 解析器,返回解析结果并记录 info 字典的原始区间 function bdecode(string $data, int &$pos = 0, array &$infoRange = []) { $len = strlen($data); if ($pos >= $len) return null; $ch = $data[$pos]; if ($ch === 'i') { // 整数 i123e $end = strpos($data, 'e', $pos); $num = (int)substr($data, $pos + 1, $end - $pos - 1); $pos = $end + 1; return $num; } if ($ch === 'l') { // 列表 l...e $pos++; $list = []; while ($data[$pos] !== 'e') { $list[] = bdecode($data, $pos, $infoRange); } $pos++; // 跳过结尾 e return $list; } if ($ch === 'd') { // 字典 d...e $pos++; $dict = []; while ($data[$pos] !== 'e') { $key = bdecode($data, $pos, $infoRange); // 键一定是字符串 $valStart = $pos; // 记录值起始偏移 $val = bdecode($data, $pos, $infoRange); if ($key === 'info') { $infoRange = [$valStart, $pos - $valStart]; // [起点, 长度] } $dict[$key] = $val; } $pos++; return $dict; } // 字符串 <len>:<content> $colon = strpos($data, ':', $pos); $slen = (int)substr($data, $pos, $colon - $pos); $str = substr($data, $colon + 1, $slen); $pos = $colon + 1 + $slen; return $str; }

逻辑说明:$pos是全局游标,递归调用共享同一个引用,保证解析连续推进。$infoRange用引用传出,记录info值在原始字符串里的起点和长度。参数上唯一要注意的是$pos必须按引用传,否则递归回来游标就乱了。这段代码不处理异常输入,生产环境要在每个strpos返回false时抛错,否则畸形种子会让脚本静默产出错误 hash。

2.3 从 info 切片到磁力链接

拿到$infoRange后,切片、SHA1、拼链接三步走。dn参数是显示名,取自info.name,要做 URL 编码;tr是 tracker,取自顶层announce和announce-list,可以有多个。

<?php $raw = file_get_contents($argv[1]); $pos = 0; $infoRange = []; $meta = bdecode($raw, $pos, $infoRange); // 关键:对 info 字典的原始字节做 SHA1 $infoBytes = substr($raw, $infoRange[0], $infoRange[1]); $infoHash = sha1($infoBytes); // 40 位十六进制 $name = $meta['info']['name'] ?? 'unknown'; $trackers = []; if (!empty($meta['announce'])) $trackers[] = $meta['announce']; if (!empty($meta['announce-list'])) { foreach ($meta['announce-list'] as $tier) { foreach ($tier as $tr) $trackers[] = $tr; } } $trackers = array_unique($trackers); $magnet = 'magnet:?xt=urn:btih:' . $infoHash . '&dn=' . rawurlencode($name); foreach ($trackers as $tr) { $magnet .= '&tr=' . rawurlencode($tr); } echo $magnet . PHP_EOL;

参数说明:sha1()默认输出小写十六进制,磁力链接里大小写都合法,但部分老客户端只认大写,稳妥做法是统一转大写或统一小写并在文档里写清。rawurlencode比urlencode更适合 URL 参数,前者把空格编成%20而非+。tracker 去重后顺序保留,announce-list的分层结构在拼链接时被拍平,这是常见做法,因为磁力链接的tr参数本身不表达优先级。

3. 把脚本跑起来:命令行、批量与接口三种用法

3.1 命令行单文件转换

最直接的用法是 CLI。把上面两段合成一个torrent2magnet.php,接收文件路径参数。

php torrent2magnet.php ./demo.torrent # 输出示例 # magnet:?xt=urn:btih:ABCDEF...&dn=demo&tr=udp%3A%2F%2Ftracker.example%3A80

跑之前确认 PHP 版本,这套代码只用到sha1、substr、strpos这些基础函数,PHP 7.0 以上都能跑,不需要额外扩展。如果输出为空,先看文件是不是真的 bencode——用xxd demo.torrent | head看头几个字节,正常种子以d8:announce或d4:info开头,如果开头是PK说明你拿到的是 zip 压缩包没解压。

3.2 批量目录处理

实际场景很少只转一个。批量版加个目录遍历,把结果写成一行一个的文本,方便后续导入。

<?php $dir = $argv[1]; $out = fopen('magnets.txt', 'w'); foreach (glob($dir . '/*.torrent') as $file) { $raw = file_get_contents($file); $pos = 0; $infoRange = []; $meta = bdecode($raw, $pos, $infoRange); if (empty($infoRange)) { // 没有 info 字典,跳过 fwrite(STDERR, "skip: $file\n"); continue; } $hash = strtoupper(sha1(substr($raw, $infoRange[0], $infoRange[1]))); $name = $meta['info']['name'] ?? basename($file, '.torrent'); fwrite($out, "magnet:?xt=urn:btih:$hash&dn=" . rawurlencode($name) . "\n"); } fclose($out);

逻辑说明:glob只匹配当前层,子目录要换RecursiveDirectoryIterator。$infoRange为空说明这个文件不是合法种子或结构异常,直接跳过并打到 stderr,不污染结果文件。hash 统一转大写,避免下游系统大小写敏感导致去重失败。批量跑几千个文件时注意内存,file_get_contents一次读整个文件,单个种子通常几十 KB 到几 MB,问题不大,但超大种子(含大封面图)要留意memory_limit。

3.3 包成 HTTP 接口

想给前端或其他服务调用,就套一层最简单的路由。不引框架,原生 PHP 足够。

<?php // upload.php 接收 multipart 上传的种子,返回 JSON header('Content-Type: application/json'); if (empty($_FILES['torrent']['tmp_name'])) { http_response_code(400); echo json_encode(['error' => 'no file']); exit; } $raw = file_get_contents($_FILES['torrent']['tmp_name']); $pos = 0; $infoRange = []; $meta = bdecode($raw, $pos, $infoRange); if (empty($infoRange)) { http_response_code(422); echo json_encode(['error' => 'invalid torrent']); exit; } $hash = strtoupper(sha1(substr($raw, $infoRange[0], $infoRange[1]))); echo json_encode([ 'info_hash' => $hash, 'name' => $meta['info']['name'] ?? '', 'magnet' => 'magnet:?xt=urn:btih:' . $hash . '&dn=' . rawurlencode($meta['info']['name'] ?? ''), ]);

参数与边界:上传大小受upload_max_filesize和post_max_size限制,默认 2M,种子一般够用。接口不要回显原始文件内容,只回 hash 和名字。生产环境要加文件类型校验和频率限制,否则就是个现成的滥用入口。这套接口适合内网或带鉴权的场景,裸奔在公网迟早被刷。

4. 避坑与排查:五个让链接「看着对却下不动」的坑

4.1 现象:磁力链接能生成,客户端却一直找元数据

原因几乎都是 info_hash 算错。最常见的是解析后重新bencode编码再哈希,键序或整数编码和原始字节不一致。解决:坚持对原始切片做 SHA1,不要重编码。验证方法是拿一个已知种子,用主流客户端「复制磁力链接」功能对比 hash,一致才算过。

4.2 现象:中文种子名在链接里变成乱码

原因是对name直接拼接没做 URL 编码,或者用了urlencode把空格编成+。解决:统一rawurlencode。另外 bencode 字符串是字节序列,PHP 里不要对它做mb_convert_encoding,原样编码即可,客户端自己会按 UTF-8 解析。

4.3 现象:批量转换中途脚本挂掉,结果文件半截

原因是某个畸形种子触发了解析器无限递归或越界,PHP 报 fatal 直接终止。解决:给解析器加深度上限和边界检查,批量循环里用try/catch包住单文件处理,单个失败不影响整体。更稳的做法是先把所有文件解析成 hash 列表,再统一写文件。

4.4 现象:同一批种子转出的链接去重后数量对不上

原因是 hash 大小写不统一,或者dn参数编码差异导致字符串比较失败。解决:入库前统一strtoupper处理 hash,去重只按 info_hash 比较,不要拿整条链接比。这是资源索引场景的高频翻车点。

4.5 现象:接口上传大种子返回 500

原因是post_max_size超限后$_FILES为空但$_POST也被清空,代码没判空直接访问数组键。解决:入口先判$_FILES和$_SERVER['CONTENT_LENGTH'],超限时返回明确的 413 而不是让脚本报错。后悔药是提前在php.ini里把限制调到合理值并在接口文档里写明。

5. 进阶:用 info_hash 做去重与校验的完整闭环

转换只是第一步,真正让这套工具产生价值的是把 info_hash 当成资源指纹用。同一个文件无论换多少个种子、换多少个 tracker,info_hash 不变,这是它比文件名可靠得多的原因。我一般会在转换后做两件事:一是把 hash 存进带唯一索引的表,导入时INSERT ... ON DUPLICATE KEY UPDATE天然去重;二是拿 hash 反查已有资源,避免重复采集。

CREATE TABLE magnets ( info_hash CHAR(40) NOT NULL, name VARCHAR(512), magnet TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (info_hash) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; INSERT INTO magnets (info_hash, name, magnet) VALUES ('ABCDEF...', 'demo', 'magnet:?xt=urn:btih:ABCDEF...') ON DUPLICATE KEY UPDATE name = VALUES(name);

表结构里info_hash用CHAR(40)定长,比VARCHAR索引更紧凑。magnet字段存完整链接方便直接取用,但去重逻辑只认主键。校验环节可以再加一步:对转换结果做一次「反向解析」,从磁力链接里抠出 hash 和原始种子算出的 hash 比对,不一致就报警。这个自检在批量导入时能拦住绝大多数解析器 bug。

校验项方法失败含义
hash 一致性原始切片 SHA1 vs 链接中 btih解析或编码有误
大小写统一转大写后比较下游去重失效
name 编码rawurldecode后与原文比对URL 编码用错函数
tracker 数量去重后计数announce-list 拍平遗漏

最后说个习惯。从那以后我每次把这类转换脚本接进采集流程,都会先拿十个已知种子跑一遍回归,把 hash 和客户端结果逐个对齐,再放开批量。这套 PHP 实现不大,但 bencode 的边界和 info_hash 的原始字节语义,是那种「不看一眼就敢上线、上线就翻车」的地方。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/30 8:41:08

BorgWarner DESADV报文JSON与EDI转换技术解析

1. BorgWarner DESADV报文解析概述 在汽车零部件供应链管理中&#xff0c;DESADV&#xff08;Despatch Advice Message&#xff09;是供应商向客户发送发货通知的标准EDI报文。BorgWarner作为全球领先的汽车零部件供应商&#xff0c;其DESADV报文遵循EDIFACT标准&#xff0c;但…

作者头像 李华
网站建设 2026/10/30 8:39:41

高等代数通关地图:从线性方程组到线性空间的逻辑主干

简介&#xff1a;本资源是面向数学专业本科生及高等代数自学者的系统性学习笔记&#xff0c;深度对标丘维声教授北京大学《高等代数》课程&#xff08;B站151讲视频&#xff09;&#xff0c;聚焦线性方程组、行列式、线性空间三大核心模块&#xff0c;解决概念抽象、逻辑链条长…

作者头像 李华
网站建设 2026/10/30 8:38:09

PyQt5+MySQL简易人事管理系统:数据建模、事务留痕与上线避坑

简介&#xff1a;这份简易人事管理系统源码面向学习 PyQt5 桌面开发与 MySQL 数据库应用的学生及初级开发者&#xff0c;围绕企业人事信息管理场景&#xff0c;提供从登录鉴权到员工、部门、职称、薪资记录维护的完整功能示例&#xff0c;可用于课程设计、毕业设计参考或桌面管…

作者头像 李华
网站建设 2026/10/30 8:35:58

S1 data forwarding测试:TEID、End Marker与抓包

简介&#xff1a;围绕S1数据转发测试整理的docx小结&#xff0c;面向移动通信核心网、无线侧测试人员及协议分析学习者&#xff0c;聚焦切换过程中用户面数据连续性验证。内容从切换前后的数据流向入手&#xff0c;梳理SGW、源eNodeB与目标eNodeB间的转发路径&#xff0c;结合T…

作者头像 李华
网站建设 2026/10/30 8:35:46

S1切换数据转发验证:用TEID与End Marker检查点定位业务中断

简介&#xff1a;围绕S1数据转发测试的docx小结&#xff0c;面向移动通信核心网、无线侧测试工程师及LTE切换学习者&#xff0c;聚焦切换过程中用户面数据连续性验证这一关键问题。内容从数据流向入手&#xff0c;梳理切换前SGW经源eNodeB至终端、切换中经SGW回传再转目标eNode…

作者头像 李华
网站建设 2026/10/30 8:33:34

TIF/TIFF多页合并与拆分:位深、分块和BigTIFF避坑指南

简介&#xff1a;TIF/TIFF文件的合并及拆分工具是一份面向图形设计、扫描归档与印刷从业者的小型Windows实用程序&#xff0c;用于把多幅图像拼合为多页TIFF&#xff0c;或从多页文件中按页提取单页&#xff0c;并兼顾元数据保留与批量处理需求。压缩包共3个文件&#xff0c;以…

作者头像 李华