简介:该资源为qBittorrent下载工具配套的搜索插件合集,面向使用qBittorrent进行BT种子检索与下载的用户。压缩包内共44个文件,均为Python脚本,涵盖学术资源(academictorrents)、电影资源(yts、yts_am)、综合种子站(extratorrent、torrentgalaxy、nyaasi等)等类别,用户将脚本放入qBittorrent插件目录后,即可在软件内直接搜索对应来源的种子并转为磁力链接。包体仅70KB,体积小巧,部署便捷,适合希望在qBittorrent内集中完成资源搜索与下载的爱好者、科研人员及影音用户。目前已有3985人学习下载,插件列表覆盖多个活跃与镜像站点,可有效提升资源获取效率,但需注意遵守版权法规并关注搜索引擎变动以便及时更新。
1. 下载工具最容易被浪费的功能:qBittorrent 搜索插件
下载工具最容易被浪费的功能是聚合搜索。qBittorrent 自带搜索标签页,但默认只有一个演示插件,搜什么都没结果;把第三方搜索插件装进去后,同一个关键词可以并行查询多个索引站,结果带做种数、大小、来源分类,比打开浏览器挨个站点输入快得多。这篇文章围绕一份 qbittorrent 搜索插件包,讲清楚插件从哪来、怎么装、为什么装了没反应,以及如何自己改出一份可维护的搜索脚本。想直接把下载工具当搜索工具用的人,尤其适合往下看。
2. 插件机制与文件构成:从搜索框到返回结果的完整链路
2.1 搜索引擎外壳:客户端不联网也能跑插件
qBittorrent 的搜索功能本质上是一个内置的迷你爬虫框架。打开搜索标签页时,客户端会扫描一个叫 nova3 的插件目录,把每个合法插件注册到列表里。输入关键词并点击搜索按钮后,客户端会遍历所有已启用的插件,把关键词分别传递给每个插件的search方法。
这个框架不依赖系统的 Python 环境。qBittorrent 内部带有一套独立的 Python 运行时,插件作者只需要提供一个.py文件,文件里包含固定头部和类定义,客户端就能直接调用。也就是说,插件执行环境相对封闭,外部装的第三方库大多不可用,依赖requests这类库的插件放进默认目录通常会报错。我见过不少新写好的插件在本地运行正常,放到 qBittorrent 里搜不出结果,就是因为误用了requests,而客户端环境里没有这个库。
另一个值得注意的机制是插件列表的加载时机。客户端启动时会扫描 nova3 目录,但如果你在客户端已经打开的情况下手动往 nova3 里放文件,插件列表不会自动刷新。正确做法是先放文件,再打开搜索标签页,或者通过插件管理器点“重新加载插件列表”。很多人以为插件数量没有变化是压缩包问题,实际只是没有触发重扫。
2.2 最小插件骨架:#VERSION、#PLUGIN 与 search 方法
qBittorrent 插件文件有严格的信息头,结构通常长这样:
#VERSION: 206 #SEARCHURL: https://example-index.com/search #PLUGIN: example_index #CATEGORIES: all import urllib.request import urllib.parse import re class example_index(object): def __init__(self): self.supported_categories = { 'all': '', 'video': 'videos', 'audio': 'music', } self.url = 'https://example-index.com/search' def search(self, query, cat): search_url = self.url + '?' + urllib.parse.urlencode({'q': query}) html = self.fetch(search_url) if html: return self.parse(html) return [] def fetch(self, url): req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'}) try: with urllib.request.urlopen(req, timeout=10) as resp: return resp.read().decode('utf-8', 'ignore') except Exception: return '' def parse(self, html): results = [] return results这个文件里的#VERSION是插件接口版本号,qBittorrent 会根据这个值判断插件能否在当前版本运行。#PLUGIN后面的字符串是插件名称,插件管理器里显示的名字就取这里,与文件名无关。#SEARCHURL只是描述信息,真正决定搜索地址的是search方法里的 URL 拼接逻辑。
search方法必须返回一个列表,列表元素是包含固定字段的字典。qBittorrent 约定字段包括name、magnet、size、seeders、leechers、category、link、descs。缺少其中核心字段,结果行虽然能显示,但排序和下载会把缺字段的条目当垃圾数据处理。我一般会让空实现先返回空列表,确保插件能在插件管理器里通过基本校验,再逐步添加解析逻辑。
2.3 网络请求与解析分离:把 fetch 单独拆出来的意义
很多老插件把请求和解析全部塞在search方法里,一次写完看似省事,后面排错很难受。我习惯把网络请求单独拆成fetch方法,这样search只负责拼 URL 和调解析,逻辑更清晰。
def fetch(self, url): headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', } req = urllib.request.Request(url, headers=headers) with urllib.request.urlopen(req, timeout=10) as resp: raw = resp.read() charset = resp.headers.get_content_charset() or 'utf-8' return raw.decode(charset, 'ignore')这段代码的关键参数是timeout和charset。timeout=10表示单个页面最多等待 10 秒,避免某站点无法访问时一直占用线程。get_content_charset是响应头里的编码声明,如果目标站点没有给编码,再回退到 UTF-8 解码。不同站点自定义编码的情况很多,在fetch里统一处理比在parse里补救要省事。
拆出fetch之后,调试时可以直接在插件外部调用这个方法,打印返回的 HTML,确认请求是否被拦截。很多搜索无结果的问题并不是正则写错,而是请求没有真正到达目标站点。把这个方法单独测一遍,能快速区分是网络层问题还是解析层问题。
2.4 parse 方法里的字段映射,决定结果列表能不能排序
parse方法是整个插件最容易翻车的部分。HTML 页面中的一个搜索条目,通常包含标题、链接、大小、做种数、下载数。需要用正则或 HTML 解析器把原始结构拆出来,然后映射到 qBittorrent 约定的字段。
def parse(self, html): results = [] blocks = re.findall(r'<tr class="[^"]*">(.*?)</tr>', html, re.S) for block in blocks: name = re.search(r'<a[^>]*title="([^"]+)"', block) magnet = re.search(r'href="(magnet:\?xt=urn:btih:[^"]+)"', block) seeders = re.search(r'<td[^>]*class="seeders"[^>]*>(\d+)</td>', block) if not name or not magnet: continue results.append({ 'name': name.group(1), 'magnet': magnet.group(1), 'size': '', 'seeders': int(seeders.group(1)) if seeders else 0, 'leechers': 0, 'category': '', 'link': magnet.group(1), 'descs': '' }) return results这段代码里最优先匹配的是name和magnet,它们决定一个条目是否有效。seeders匹配不到时置为 0,不会让整个条目消失,但排序时会排在后面。size字段如果为空,结果列表只显示文件名,用户体验很差,所以后续可以补抓。
写parse时容易陷入一个误区:用浏览器开发者工具看到的 DOM 结构去写正则。实际后端返回的 HTML 可能经过压缩、换行、转义,F12 面板里看到的节点是浏览器处理过的结果。最可靠的调试方法是把fetch得到的 HTML 保存到本地文件,直接搜索目标关键词在源码里的上下文,再写正则。否则看着标签写出来的正则,一遇到真实页面就失灵。
3. 把 .rar 插件装进 qBittorrent:手动复制、zip 导入与路径误区
3.1 解压后先检查文件结构,避免 .py.txt 隐形炸弹
拿到qbittorrent搜索插件.rar这类包,第一步不是急着把文件丢进客户端,而是先检查压缩包的目录结构。qBittorrent 插件只认.py单文件或者.zip离线包,.rar 本身没意义,必须先解压。
解压后常见两种结构:一种是根目录直接一堆.py文件,另一种是所有文件在二级子目录里。第一种可以直接放进配置目录;第二种需要做一次整理,把根目录下的.py取出来,或者重新打成 zip。
检查文件后缀这一点不能跳。很多 Windows 用户下载的压缩包解压后,文件显示名称是plugin.py,但实际文件名是plugin.py.txt。qBittorrent 扫描插件目录时按文件名后缀过滤,看到.txt会直接跳过。如果你想确认文件是否被隐藏了扩展名,可以在资源管理器里勾选“显示文件扩展名”,或者在命令行里用dir查看完整文件名。这个检查只需要十秒,但能省掉后面所有“装了没反应”的排查时间。
3.2 手动放进 nova3 目录:路径对不上一切白搭
手动安装插件的核心是把.py文件复制到 qBittorrent 的用户数据目录。Windows 上最常见的位置是C:\Users\<用户名>\AppData\Local\qBittorrent\nova3,其中<用户名>是当前系统用户。macOS 上通常在~/Library/Preferences/qBittorrent/nova3。
如果你不确定路径,可以在 qBittorrent 的搜索标签页点“搜索插件”按钮,打开插件管理器,点“查看文件夹”,客户端会直接打开对应的目录。这个方法比在磁盘里到处找可靠得多。
复制完文件后,回到插件管理器,点“重新加载”,新插件应该出现在列表里。如果列表里已经有同名插件,优先清理掉旧版本再导入,避免两个同名插件互相覆盖。我要提醒的是,qBittorrent 对插件文件的覆盖策略是静默的。同名的旧插件如果正在被启用状态,覆盖文件后需要重启客户端才能加载新代码。
3.3 通过插件管理器导入 zip 离线包
如果压缩包被整理成规范的 zip 结构,可以直接用插件管理器导入。具体操作是:在搜索标签页点击“搜索插件”,选择“安装新的插件”,选中 zip 文件,客户端会自动把根目录下的.py文件复制到 nova3 目录。
这里有一个容易踩的坑:zip 包内不能有嵌套目录。qBittorrent 只会处理 zip 根目录下的.py文件,如果所有脚本被放在release/子文件夹里,导入时会提示无法识别或直接忽略。我一般会先解压 zip,确认文件层级,再决定是手动复制还是重新打包。
导入 zip 的另一个好处是批量操作。手动复制几十个文件容易漏,zip 导入一次就能完成注册。导入后打开插件管理器,看看文件数量是否全部出现。如果有部分缺失,先检查缺失的文件头是否完整,再检查 zip 里是否混入了.pyc或系统自动生成的__MACOSX目录。
3.4 日志验证:插件不是打勾就完事
插件在管理器里打勾,只代表客户端认可了这个文件的头部信息,不代表它运行正常。我见过不少插件,启用后搜索返回空列表,管理器里却一切正常。这时候日志是唯一的突破口。
qBittorrent 的日志通常写在用户数据目录的logs文件夹里。Windows 路径是C:\Users\<用户名>\AppData\Local\qBittorrent\logs\qbt.log。打开日志,搜索“search”或插件名,能看到导入和调用信息。如果插件在 import 阶段失败,会有类似“failed to load plugin”的记录。如果插件执行但异常退出,日志里会出现 traceback。
还有一个隐蔽问题:qBittorrent 会把插件文件按名称拼音顺序加载,如果某个插件头部缺少换行或包含非法字符,后面几个插件可能都不会被加载。所以当一批插件里只有一个坏文件时,可能会出现连续多个插件无法导入的现象。排查时一定要优先看日志,而不是一个个重装。
4. 参数与脚本调试:UA、分类映射、编码和字段校验
4.1 请求头与超时参数:让插件在站点面前像浏览器
搜索插件向目标站点发出去的请求,本质上和浏览器请求没有区别。站点会先看 User-Agent,再判断是否接受这个请求。很多插件默认不设置 UA,qBittorrent 发起请求时可能会带上自己的客户端标识,一些站点看到非浏览器 UA 直接返回 403。
在fetch方法里,我一般至少设置两个头:User-Agent和Accept-Language。UA 用主流浏览器的完整版本字符串,不用简写。Accept-Language设置成zh-CN,zh;q=0.9,en;q=0.8,对中文站点的响应内容会更友好。如果站点需要登录后搜索,还必须在 Headers 里带上Cookie字段。
def fetch(self, url): headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Cookie': self.cookie, } req = urllib.request.Request(url, headers=headers) with urllib.request.urlopen(req, timeout=10) as resp: return resp.read().decode('utf-8', 'ignore')超时参数不要省略。qBittorrent 并行调用多个插件时,如果某个插件没有设 timeout,它会长时间阻塞整个搜索任务。10 秒是标准值,对大多数索引站够用。设置为 15 秒以上时,搜索速度会明显变慢。如果站点经常超时,优先降低请求频率,而不是无限拉长超时时间。
4.2 supported_categories 分类映射不能乱拼参数
supported_categories是插件声明自身支持哪些分类的字典。qBittorrent 搜索界面上方有“全部、视频、音频、图片、软件、书籍、游戏、其他”这些固定分类。用户选择分类后,客户端会遍历插件,检查该分类是否在插件的supported_categories里,不存在则跳过。
这个字典的 key 应该使用 qBittorrent 规定的固定分类名,value 是插件传给search方法的 cat 字符串。常见的写法如下:
self.supported_categories = { 'all': '', 'video': 'videos;movies', 'audio': 'music', 'software': 'apps', }value 的具体内容完全由插件作者决定。有些站点的搜索 URL 需要cat=200,有些需要cat=movies。不管哪种,search方法只是把 value 当字符串接进请求参数。千万不要在 value 里拼接&page=1之类的额外参数,那样会破坏 URL 结构。正确做法是在search方法里单独处理分页参数,和分类参数分开拼接。
4.3 关键词与页面编码:中文搜索为空的核心排查点
搜索插件接收到的查询关键词是一个 Unicode 字符串。发送请求前必须做 URL 编码,否则中文、空格、特殊符号会直接导致搜索失败。Python 3 下用urllib.parse.quote最稳妥。
search_query = urllib.parse.quote(query, safe='')safe=''表示空格、斜杠、问号等字符都会被编码,避免拼进 URL 后改变路径结构。某些站点的搜索路径是/search/%s,这种形式通常会把关键词放在路径里;另一些站点使用?keyword=%s的查询参数风格。两种方式编码规则一样,但如果是路径式 URL,要注意quote之后不要把%2F再解码,否则斜杠会被还原,导致参数错位。
页面编码是另一个高频坑。请求返回的 HTML,不能默认按 UTF-8 解码。部分站点使用 GBK 或 GB2312,按 UTF-8 解码后中文会变成乱码,正则明显匹配不到真实内容。处理办法是读取响应头中的charset,没有则再看 HTML 里的<meta charset>,实在找不到就尝试 GBK。
raw = resp.read() if resp.headers.get('Content-Type', '').find('charset=gbk') != -1: html = raw.decode('gbk', 'ignore') else: html = raw.decode('utf-8', 'ignore')这段逻辑不复杂,但能让插件覆盖更多站点。如果你只针对一个站点调试,建议在fetch里把解码后的内容打印前两百个字符,能立刻看出字符是否正常。
4.4 字段校验:magnet、seeders、size 一个都不能少
搜索结果返回后,qBittorrent 展示在列表里的所有信息都来自插件返回的字典。字段缺失不会报错,但会留下很难看的空白列,或者让下载操作失败。最关键的字段是magnet,没有它,点击下载时客户端不知道要下载什么。
写插件时,我会在parse方法末尾加一个简单过滤器:
valid_results = [] for item in results: if not item.get('magnet', '').startswith('magnet:?xt=urn:btih:'): continue if not item.get('name', '').strip(): continue valid_results.append(item) return valid_results这个过滤器把缺失磁力链和标题的条目直接丢弃,避免脏数据出现在结果列表里。seeders和size字段如果缺失,我一般会赋空字符串或 0,而不是让字段不存在。这样客户端在渲染列表时不需要处理 None 值,排序逻辑也能稳定运行。检查字段类型也有意义:seeders必须是整数类型,否则按做种数排序时会报比较错误。在解析时统一用int()转换,转换失败时给 0。
5. 避坑与常见问题排查:装不上、搜不到、被反爬的六个案例
5.1 插件管理器里看不到新导入的插件
现象:解压完qbittorrent搜索插件.rar,把文件放进了 nova3 目录,重启客户端后插件列表没有新增项。
原因:文件后缀被系统隐藏,实际文件名是plugin.py.txt;或者文件头部缺少#VERSION和#PLUGIN注释。qBittorrent 扫描时,.txt直接跳过,头部不合法的文件被当作无效数据。
解决:先打开资源管理器的“显示文件扩展名”,把.py.txt改成.py;再用纯文本编辑器检查文件头,确保第一行是#VERSION: 206这样的格式。顺便检查文件编码是否为 UTF-8 无 BOM,带 BOM 的文件头在部分客户端版本里也会影响解析。
5.2 插件已启用但搜索始终空列表
现象:插件管理器里能看到插件并已勾选,输入关键词搜索,结果栏始终空白,也没有报错弹窗。
原因:插件执行成功但返回了空列表。常见原因是站点改了 HTML 结构,旧正则匹配不到内容;也可能是搜索请求被站点拒绝,返回的是验证码或空页面。另一个容易被忽略的原因是插件里的搜索 URL 使用了旧的域名,站点已经迁移。
解决:先把fetch返回的 HTML 落盘,查看是否包含搜索结果条目。打开插件所在目录,写一个临时脚本调用插件的search方法,打印返回结果。如果 HTML 正常但 list 为空,问题在parse;如果 HTML 本身是空页面,问题在fetch的 URL 或请求头。
5.3 勾选过多插件导致客户端卡死
现象:同时启用 15 个以上插件,点击搜索后 qBittorrent 界面无响应,CPU 占用居高不下,最后才慢慢返回部分结果。
原因:插件并行执行时,每个网络请求都有等待时间。老插件没有设置 timeout,遇到响应慢的站点会一直挂起。qBittorrent 虽是多线程调用,但结构上仍会等待慢任务,数量一多整体就卡住。
解决:把插件数量控制在 5 到 8 个之间,优先保留目标类别相关且维护时间较新的插件。打开插件源码,检查所有urlopen调用是否带了 timeout 参数,没有就补上 10 秒。同时避免在插件里写多层重试循环,一次搜索两个请求失败就够了,再多只会占用更多执行时间。
5.4 能搜索但不能下载:磁力链和详情页混淆
现象:搜索结果列表正常显示文件名和做种数,但点击下载后 qBittorrent 没有添加任务,或者添加的任务是空的。
原因:插件把link或magnet字段写成了详情页 URL,而不是标准 magnet 链接。qBittorrent 下载引擎只认 magnet 协议和.torrent直链,详情页链接不会被识别。
解决:在parse中优先匹配magnet:?xt=urn:btih:开头的字符串;如果目标站点只有详情页链接,需要在插件里额外请求详情页,再从页面里提取 magnet 链接。提取后做一次哈希校验,正则匹配[0-9a-fA-F]{40}或[0-9a-fA-F]{32}。匹配不到就直接丢弃该条目,别让残缺数据留在列表里。
5.5 中文关键词搜不到,英文关键词却能搜到
现象:同一个插件,输入英文关键词能正常返回结果,输入中文书名或作者名时结果为零。
原因:关键词 URL 编码方式不对。部分站点的搜索逻辑使用 GBK 编码解析关键词,用 UTF-8 编码发过去会变成乱码,导致查询结果为空。另一种原因是在路径式 URL 中,中文关键词被编码后又经过一次解码,最终变成空格。
解决:先确定目标站点的搜索编码类型,在插件里调用urllib.parse.quote(query.encode('gbk'))进行编码。如果不想针对站点写死,可以在fetch方法里同时尝试 UTF-8 和 GBK 两种编码,哪个能返回有效结果就用哪个。这个方法会增加一次请求,但能兼容更多站点。
5.6 站点反爬:搜索请求返回 403 或验证码页
现象:插件本地测试正常,连续搜索几十次后结果列表突然全是验证码或提示,浏览器手动访问却正常。
原因:站点对高频请求或非浏览器特征请求做了临时限流。插件发出的大量重复请求被识别为脚本,IP 被标记为风险用户。UA 字符串太旧或缺少浏览器特征头,也会触发同样的限制。
解决:在两次插件请求之间加 1 到 2 秒的延迟,用time.sleep(1)控制频率。把 UA 替换成最新版浏览器的完整字符串,并补上Accept-Language与Accept头。如果站点必须携带 Cookie,就把浏览器登录后的 Cookie 写进插件类的属性中。被临时标记的 IP 通常在几小时后自动解除,不要通过频繁重试硬闯,越闯限制时间越长。
6. 进阶:自己写一个轻量搜索插件骨架,并用本地脚本验证
6.1 不带业务逻辑的搜索插件骨架
写新插件时,我不会从零开始写,而是保留一份不依赖任何第三方库的骨架。这份骨架只包含最基本的信息头、搜索方法、请求方法和解析入口,复制一份就能开始针对新站点开发。
#VERSION: 206 #SEARCHURL: https://example-index.com/search #PLUGIN: example_index #CATEGORIES: all import urllib.request import urllib.parse import time import re class example_index(object): def __init__(self): self.supported_categories = {'all': ''} self.url = 'https://example-index.com/search' self.ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36' def search(self, query, cat): params = urllib.parse.urlencode({'q': query}) html = self.fetch(self.url + '?' + params) if html: return self.parse(html) return [] def fetch(self, url): req = urllib.request.Request(url, headers={'User-Agent': self.ua}) try: with urllib.request.urlopen(req, timeout=10) as resp: return resp.read().decode('utf-8', 'ignore') except Exception: time.sleep(1) return '' def parse(self, html): return []这个骨架里的parse是空实现。实际调通时,我会先面对真实 HTML,把标题、磁力链接、大小、做种数这四个字段的正则写出来。骨架的好处是确保环境正常运行,后续改动只需要聚焦在url和parse两块,不必每次检查 request 头是否写错。
6.2 用本地脚本验证返回结果
插件写完后,不要急着丢进 qBittorrent。可以在插件文件底部加一段测试代码,直接运行 Python 脚本验证。
if __name__ == '__main__': plugin = example_index() results = plugin.search('test', '') print(results[:3])如果脚本运行后输出空列表,先打印fetch的 HTML 内容;如果输出结果但 magnet 字段不完整,则需要回到parse修正正则。这种验证方式比在 qBittorrent 搜索框里手动测试快得多,因为每次修改插件后,只需要重新运行脚本,不需要重启客户端。
我现在每次拿到新插件,第一件事不是解压放目录,而是在本地用这段测试代码跑一遍。这个习惯帮我避开了至少一半的“装不上、搜不到”问题,也让我在写新插件时能立刻看到每个字段的返回效果。希望帮到你。
本文还有配套的精品资源,点击获取