在 NAS 上部署一个属于个人的磁力搜索引擎 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
journey0ad
V2EX    分享创造

在 NAS 上部署一个属于个人的磁力搜索引擎

  •  2
     
  •   journey0ad
    journey-ad 2024-07-16 12:58:40 +08:00 4381 次点击
    这是一个创建于 533 天前的主题,其中的信息可能已经有所发展或是发生改变。

    前段时间想给 NAS 找点事情做,发现了 Bitmagnet 这个项目,用了几天发现爬虫的部分还不错,但是自带的 webui 有点简陋,有权限敞开、种子数量上来后搜索慢等问题,用起来总是不那么爽

    于是花了几天时间用 Nextjs + NextUI 写了个前端界面,顺带也是为了学一下 Next 的开发,界面功能参考了常见的一些磁力搜索引擎,顺带做了夜间模式、多语言、内容预览和搜索分词等功能

    详细的部署过程可以看仓库,提供有 Docker Compose 配置可以快速部署,也可以点 Demo 链接体验,Demo 为了避免版权等问题,内容是固定的

    仓库地址: https://github.com/journey-ad/Bitmagnet-Next-Web
    Demo: https://bitmagnet-next-web.vercel.app/

    预览:

    下面是一些开发过程的记录:

    原版的 Bitmagnet 自带一个 GraphQL 的 api 可以进行搜索,但用下来和 webui 遇到的问题是一样的,数量上去之后普遍搜索时间在几十秒,最重要的是返回的结果数量不准,没办法做分页

    关于搜索和索引问题我问过 bitmagnet 的开发者,他回复是已经结合种子标题和文件内容,在 torrent_contents.tsv 里创建了向量索引。研究后发现是先转罗马音然后存的向量,这样好处是可以用 pg 原生支持的 tsquery 来查询,而且转成罗马音后对应中文的是拼音,可以做到错字也能搜到,坏处是只要同音字就能搜到,就算同音字很离谱也是一样

    另外就是 bitmagnet 用了 go-unidecode 这个库做罗马音化,但这个库在 node 上没有一比一对应的,转罗马音的过程和 bitmagnet 做不到完全一致,影响搜索效果

    综合以上问题,决定自己写后端代码直连 DB 来查询,但之前没怎么搞过后端,更没搞过搜索这种东西,总之边写边测边改,搞出来了 gin 索引+传统 like 模糊匹配+分词,并根据每个关键词的词性确定为必须或非必须,生成对应的 SQL 查询这种野路子方案。至于为什么不上 ES ,还有个考虑是想侵入性的修改尽量少点,后面 bitmagnet 库表结构有升级时好适配,所以没选 ES 这种重的方案

    结果比较多的情况下基本在几百毫秒到几秒内就能返回,部分收录少的词可能要跑全表,要等十几秒,这个性能还算能接受,暂时是我能想到的最好的方案了,等有空了研究一下 discuz 之类的论坛怎么做的搜索

    11 条回复    2025-02-13 20:03:44 +08:00
    chunkingName
        1
    chunkingName  
       2024-07-16 14:19:27 +08:00
    是的,我目前 nas 上爬了三百多万种子,自带的搜索很慢,前几天连续搜索出错了,现在一直报错不进行搜索了,说数据库处于查询状态。试试你这个
    pxiphx891
        2
    pxiphx891  
       2024-07-16 14:21:58 +08:00
    点赞,这个好
    chunkingName
        3
    chunkingName  
       2024-07-16 14:34:28 +08:00
    搜索报这个错,用他原来的 web 搜索正常

    发生意外错误
    Message: An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.

    Digest: 1841269720
    journey0ad
        4
    journey0ad  
    OP
       2024-07-16 14:38:46 +08:00
    @chunkingName #3
    看下容器内的日志?
    chunkingName
        5
    chunkingName  
       2024-07-16 14:50:20 +08:00
    @journey0ad date stream content
    2024/07/16 14:44:54 stderr }
    2024/07/16 14:44:54 stderr digest: '1841269720'
    2024/07/16 14:44:54 stderr at async y (/app/.next/server/app/search/page.js:1:11391) {
    2024/07/16 14:44:54 stderr at async b (/app/.next/server/app/search/page.js:1:10813)
    2024/07/16 14:44:54 stderr at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    2024/07/16 14:44:54 stderr at a (/app/.next/server/chunks/200.js:1:25217)
    2024/07/16 14:44:54 stderr Error: Network response was not ok: Internal Server Error
    2024/07/16 14:44:54 stderr at async y (/app/.next/server/app/search/page.js:1:11391)
    2024/07/16 14:44:54 stderr at async b (/app/.next/server/app/search/page.js:1:10813)
    2024/07/16 14:44:54 stderr at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    2024/07/16 14:44:54 stderr at a (/app/.next/server/chunks/200.js:1:25217)
    2024/07/16 14:44:54 stderr Error: Network response was not ok: Internal Server Error
    2024/07/16 14:44:54 stderr Failed to fetch: Network response was not ok: Internal Server Error
    2024/07/16 14:44:54 stdout keywords: [ { _: '$1', keyword: '雷米', required: true } ]
    2024/07/16 14:44:54 stderr }
    2024/07/16 14:44:54 stderr extraInfo: undefined
    2024/07/16 14:44:54 stderr networkError: null,
    2024/07/16 14:44:54 stderr clientErrors: [],
    2024/07/16 14:44:54 stderr protocolErrors: [],
    2024/07/16 14:44:54 stderr ],
    2024/07/16 14:44:54 stderr }
    2024/07/16 14:44:54 stderr extensions: [Object]
    2024/07/16 14:44:54 stderr path: [Array],
    2024/07/16 14:44:54 stderr locations: [Array],
    2024/07/16 14:44:54 stderr message: 'Failed to execute search query',
    2024/07/16 14:44:54 stderr {
    2024/07/16 14:44:54 stderr graphQLErrors: [
    2024/07/16 14:44:54 stderr at t.next (/app/.next/server/chunks/38.js:1:134768) {
    2024/07/16 14:44:54 stderr at b (/app/.next/server/chunks/38.js:1:134267)
    2024/07/16 14:44:54 stderr at g (/app/.next/server/chunks/38.js:1:133726)
    2024/07/16 14:44:54 stderr at Object.next (/app/.next/server/chunks/38.js:1:39726)
    2024/07/16 14:44:54 stderr at Object.then (/app/.next/server/chunks/38.js:1:39598)
    2024/07/16 14:44:54 stderr at new Promise (<anonymous>)
    2024/07/16 14:44:54 stderr at /app/.next/server/chunks/38.js:1:39631
    2024/07/16 14:44:54 stderr at o (/app/.next/server/chunks/38.js:1:39716)
    2024/07/16 14:44:54 stderr at /app/.next/server/chunks/38.js:1:76524
    2024/07/16 14:44:54 stderr at new t (/app/.next/server/chunks/38.js:1:88954)
    2024/07/16 14:44:54 stderr t [ApolloError]: Failed to execute search query
    2024/07/16 14:44:54 stderr }
    2024/07/16 14:44:54 stderr port: 5433
    2024/07/16 14:44:54 stderr address: '192.168.11.2',
    2024/07/16 14:44:54 stderr syscall: 'connect',
    2024/07/16 14:44:54 stderr code: 'ECONNREFUSED',
    2024/07/16 14:44:54 stderr errno: -111,
    2024/07/16 14:44:54 stderr at async Object.g [as search] (/app/.next/server/app/api/graphql/route.js:131:107) {
    2024/07/16 14:44:54 stderr at async Promise.all (index 1)
    2024/07/16 14:44:54 stderr at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    2024/07/16 14:44:54 stderr at /app/node_modules/pg-pool/index.js:45:11
    2024/07/16 14:44:54 stderr Error in search resolver: Error: connect ECONNREFUSED 192.168.11.2:5433
    2024/07/16 14:44:54 stdout [ '%雷米%', 10, 0 ]
    2024/07/16 14:44:54 stdout filtered; -- 从过滤后的数据中查询
    2024/07/16 14:44:54 stdout FROM
    2024/07/16 14:44:54 stdout END AS files -- 结果别名设为 'files'
    2024/07/16 14:44:54 stdout ELSE NULL -- 如果 files_count 为空, 则设置为 NULL
    2024/07/16 14:44:54 stdout )
    2024/07/16 14:44:54 stdout WHERE torrent_files.info_hash = filtered.info_hash -- 根据 info_hash 匹配文件
    2024/07/16 14:44:54 stdout FROM torrent_files
    2024/07/16 14:44:54 stdout ))
    2024/07/16 14:44:54 stdout 'extension', torrent_files.extension -- 文件扩展名
    2024/07/16 14:44:54 stdout 'size', torrent_files.size, -- 文件大小
    2024/07/16 14:44:54 stdout 'path', torrent_files.path, -- 文件在种子中的路径
    2024/07/16 14:44:54 stdout 'index', torrent_files.index, -- 文件在种子中的索引
    2024/07/16 14:44:54 stdout SELECT json_agg(json_build_object(
    2024/07/16 14:44:54 stdout -- 如果有数量, 根据 info_hash 查询文件信息到 'files' 列, 聚合成 JSON
    2024/07/16 14:44:54 stdout WHEN filtered.files_count IS NOT NULL THEN (
    2024/07/16 14:44:54 stdout CASE
    2024/07/16 14:44:54 stdout -- 检查 files_count, 是否有文件数量
    2024/07/16 14:44:54 stdout filtered.files_count, -- 种子文件数
    2024/07/16 14:44:54 stdout filtered.updated_at, -- 更新时间戳
    2024/07/16 14:44:54 stdout filtered.created_at, -- 创建时间戳
    2024/07/16 14:44:54 stdout filtered.size, -- 种子大小
    2024/07/16 14:44:54 stdout filtered.name, -- 种子名称
    2024/07/16 14:44:54 stdout filtered.info_hash, -- 种子哈希
    2024/07/16 14:44:54 stdout SELECT
    2024/07/16 14:44:54 stdout -- 从过滤后的数据中查询文件信息
    2024/07/16 14:44:54 stdout )
    chunkingName
        6
    chunkingName  
       2024-07-16 14:51:29 +08:00
    @journey0ad 我将默认数据库 5432 改为了 5433 其余的没动 原版网页搜索正常,你的这个报日志这些错误
    journey0ad
        7
    journey0ad  
    OP
       2024-07-16 15:18:27 +08:00
    @chunkingName #6
    看上去是 webui 到数据库容器的连接不通

    是用 docker compose 部署的吗? docker 内有网络隔离,db 连接串的 host 部分需要和 postgres 所在的容器名一致才能连接上,参考 https://github.com/journey-ad/Bitmagnet-Next-Web/blob/1981a093cea8291e476fadab82bdf4b07bc207a4/docker-compose.yml
    43 行的容器名和暴露的端口号,和 11 行 db 连接串的 postgres:5432 是对应的,29 行的 POSTGRES_HOST=postgres 也是一样的道理

    或者不管用什么方法,保证 webui 的容器到 postgres 容器是可联通的就行
    lisawang
        8
    lisawang  
       2024-07-20 01:31:28 +08:00
    我在宝塔里用直接复制的 docker-compose.yml 用 Docker Compose 部署,ip:3000 搜索就是错误,但是:3333 是没问题,进去 ping 了一下 PostgreSQL ,ping 的通,奇怪,怎么解决呢
    发生意外错误
    Message: An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.

    Digest: 2644143229
    lisawang
        9
    lisawang  
       2024-07-20 01:34:19 +08:00
    TypeError: fetch failed
    at node:internal/deps/undici/undici:12618:11
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async a (/app/.next/server/chunks/200.js:1:25185)
    at async b (/app/.next/server/app/search/page.js:1:10813)
    at async y (/app/.next/server/app/search/page.js:1:11391) {
    cause: Error: connect ECONNREFUSED ::1:3000
    at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1555:16)
    at TCPConnectWrap.callbackTrampoline (node:internal/async_hooks:128:17) {
    errno: -111,
    code: 'ECONNREFUSED',
    syscall: 'connect',
    address: '::1',
    port: 3000
    },
    digest: '2644143229'
    }
    错误日志
    journey0ad
        10
    journey0ad  
    OP
       2024-07-22 18:59:23 +08:00
    kaesi0
        11
    kaesi0  
       320 天前
    up 现在这个优化的怎么样了,有心想要学习
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5240 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 23ms UTC 06:26 PVG 14:6 LAX 22:26 JFK 01:26
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86