在线地址:https://tagsuggest.zeabur.app/api/tags/suggest 无需 API Key、无需注册,浏览器里可以直接 fetch(已开放 CORS)。
这个接口本来是给 WeBooru 自己的搜索框做输入联想的:你打「海贼」它告诉你对应的 tag 是 One Piece,你打 miku 它告诉你中文叫「初音未来」。因为数据本身就是一份维护了很久的中英日对照标签库 ,所以单独拎出来也能当一个小工具用——比如给你自己的图站加联想、批量把 tag 翻成中文、写脚本查某个 tag 大概有多少作品。
一、请求 1 GET /api/tags/suggest?q=<关键词>&source=<图源>
参数
类型
必填
说明
q
string
是
要查的关键词,最少 1 个字符。可以是中文、日文、英文
source
string
否
传 pixiv 查 Pixiv 标签库 ;不传或传其它值查 Booru 标签库
完整示例:
1 https://tagsuggest.zeabur.app/api/tags/suggest?q=one_piece&source=pixiv
[!IMPORTANT] 这是公益站自用的接口,不承诺 SLA :可能限流、可能随站点改版调整参数。个人小工具、自用脚本随便调,请不要做高频轮询或大规模抓取。
二、返回结构 固定返回一个对象,结果放在 results 数组里:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 { "results" : [ { "name" : "One Piece" , "cn_name" : "海贼王" , "count" : 263718 , "category" : 0 } , { "name" : "Uta (One Piece)" , "cn_name" : "乌塔(海贼王)" , "count" : 24353 , "category" : 0 } , { "name" : "One Piece 100 Users Bookmark" , "cn_name" : "海贼王_100users收藏" , "count" : 19583 , "category" : 0 } ] }
字段
类型
说明
name
string
该图源上真正使用的标签本体。Booru 用下划线,Pixiv 用空格 ,直接拿去搜索即可
cn_name
string
中文译名。可能是空的字符串
count
number
热度:Pixiv 库是收藏数 ,Booru 库是作品数
category
number
标签类别,仅 Booru 库有意义,见下表
category 取值(Pixiv 库恒为 0 ):
值
含义
界面上的颜色
0
通用 / 描述性标签
蓝
1
画师
红
3
作品 / 版权
紫
4
角色
绿
5
元数据 / 风格
橙
上面那份 one_piece 的 Booru 结果就是很好的例子:one_piece 是作品(category: 3),而 one-piece_swimsuit(连体泳衣)只是恰好同名的通用标签(category: 0)。
三、匹配规则(本篇最该看的一段) 这些规则来自实现本身,踩过才知道:
只匹配一个词。 查询串里只要出现空格,直接返回空数组 ,不会去猜。所以前端联想都是取「最后一个空格后的那一段」再发请求。
中文走中文列,其它走原名列。 关键词里含中文(CJK 字符)时匹配 cn_name 字段;否则匹配 name 字段,且不区分大小写 。
纯数字(≥4 位)会返回两条「快捷指令」 ,而不是搜索标签——这是给站内搜索用的暗号:
1 2 3 4 5 6 { "results" : [ { "name" : "pixiv_user:1234567" , "cn_name" : "uid搜索作者:{q}在pixiv图源搜哦" , "count" : 0 } , { "name" : "pixiv_pool:1234567" , "cn_name" : "pid搜索作品:{q}" , "count" : 0 } ] }
{q} 是字面量,没有被替换——当作已知的小瑕疵即可。把返回的 name 原样粘进 WeBooru 的搜索框,就能跳到「该 UID 画师的作品」或「该 PID 的作品」。
最多 30 条 ,按热度(count)从高到低排。命中的标签超过 30 个时只给最热的前 30 条。
开头的 - 会被忽略 (-miku 与 miku 等价)。
出错不报错 :数据库查询异常时也返回 HTTP 200 + {"results": []},方便前端少写分支。唯一的例外是完全没传 q ,此时是 422:
1 { "detail" : [ { "type" : "missing" , "loc" : [ "query" , "q" ] , "msg" : "Field required" , "input" : null } ] }
四、动手试 4.1 在浏览器地址栏直接看 把下面这条贴进地址栏回车,就是上面那份 JSON(Firefox / Chrome 会自动格式化):
1 https://tagsuggest.zeabur.app/api/tags/suggest?q=one_piece&source=pixiv
4.2 curl 1 2 3 4 5 6 7 8 9 10 11 12 13 curl "https://tagsuggest.zeabur.app/api/tags/suggest?q=one_piece&source=pixiv" curl -G "https://tagsuggest.zeabur.app/api/tags/suggest" \ --data-urlencode "q=海贼" --data-urlencode "source=pixiv" curl "https://tagsuggest.zeabur.app/api/tags/suggest?q=%E6%B5%B7%E8%B4%BC&source=pixiv" curl "https://tagsuggest.zeabur.app/api/tags/suggest?q=one_piece"
[!TIP] 直接把中文写进 URL 丢给 curl,服务器会返回 400 ——这是 curl 不做百分号编码导致的, 浏览器、requests、fetch 都会自动处理,所以只有手写 curl 时会踩到。用 --data-urlencode 或预先编码即可。
4.3 Python 1 2 3 4 5 6 7 8 9 10 11 12 import requestsAPI = "https://tagsuggest.zeabur.app/api/tags/suggest" def suggest (word: str , source: str = "" ): """查标签联想;source="pixiv" 走 Pixiv 库,否则走 Booru 库。""" r = requests.get(API, params={"q" : word, "source" : source}, timeout=10 ) r.raise_for_status() return r.json().get("results" , []) for item in suggest("初音" , "pixiv" )[:5 ]: print (f'{item["name" ]:<28 } {item["cn_name" ]:<24 } {item["count" ]:,} ' )
输出大致是:
1 2 3 hatsune miku 初音未来(VOCALOID) 145,600 yuki miku 雪初音(初音未来) 5,200 asahina mafuyu 朝比奈真冬(世界计划_多彩舞台!_feat._初音未来) 4,100
4.4 JavaScript(浏览器 / Node 18+) 1 2 3 4 5 6 7 8 9 const API = "https://tagsuggest.zeabur.app/api/tags/suggest" ;async function suggest (word, source = "" ) { const url = `${API} ?q=${encodeURIComponent (word)} &source=${encodeURIComponent (source)} ` ; const resp = await fetch (url); return (await resp.json ()).results ; } console .log (await suggest ("miku" ));
4.5 单文件 Demo(复制即用) 下面这段存成 demo.html 双击打开,就是一个带键盘操作的联想输入框。它演示了三件正事:取最后一个词 、丢弃过期响应 (输入快时防止旧结果覆盖新结果)、点选后补一个空格 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 <!DOCTYPE html > <html lang ="zh-CN" > <head > <meta charset ="utf-8" > <title > WeBooru 标签联想 Demo</title > <style > body { font : 15px /1.6 system-ui, sans-serif; max-width : 620px ; margin : 48px auto; } .field { position : relative; } #q { width : 100% ; padding : 10px 12px ; font-size : 15px ; box-sizing : border-box; } #list { position : absolute; left : 0 ; right : 0 ; max-height : 300px ; overflow-y : auto; display : none; border : 1px solid #ccc ; background : #fff ; z-index : 10 ; } .row { display : flex; gap : 8px ; padding : 7px 10px ; cursor : pointer; } .row :hover , .row .active { background : #eef4ff ; } .en { color : #0073ff ; font-family : monospace; } .cnt { margin-left : auto; color : #888 ; font-size : 12px ; } </style > </head > <body > <input id ="q" placeholder ="试试:one_piece / 海贼 / 初音" > <select id ="src" > <option value ="pixiv" > pixiv</option > <option value ="" > booru</option > </select > <div id ="list" > </div > <p id ="status" style ="color:#888;font-size:12px" > </p > <script > const API = "https://tagsuggest.zeabur.app/api/tags/suggest" ;const $q = document .getElementById ("q" ), $list = document .getElementById ("list" );const $src = document .getElementById ("src" ), $status = document .getElementById ("status" );let items = [], active = -1 , timer = null , seq = 0 ;const fmt = n => n >= 1e6 ? (n/1e6 ).toFixed (1 )+"M" : n >= 1e3 ? (n/1e3 ).toFixed (1 )+"k" : String (n);const render = ( ) => { $list.innerHTML = items.map ((it, i ) => `<div class="row${i === active ? " active" : "" } " data-i="${i} "> <span class="en">${it.name.replace(/_/g, " " )} </span> <span>${it.cn_name || "" } </span><span class="cnt">${fmt(it.count)} </span> </div>` ).join ("" ); $list.style .display = items.length ? "block" : "none" ; }; async function query (word ) { const url = `${API} ?q=${encodeURIComponent (word)} &source=${encodeURIComponent ($src.value)} ` ; const mine = ++seq; const resp = await fetch (url); const data = await resp.json (); if (mine !== seq) return ; items = data.results || []; active = items.length ? 0 : -1 ; render (); $status.textContent = `${items.length} 条结果` ; } $q.addEventListener ("input" , () => { const word = $q.value .split (/\s+/ ).pop (); clearTimeout (timer); if (!word) { items = []; render (); return ; } timer = setTimeout (() => query (word), 250 ); }); $q.addEventListener ("keydown" , e => { if (!items.length ) return ; if (e.key === "ArrowDown" ) { active = (active + 1 ) % items.length ; render (); e.preventDefault (); } if (e.key === "ArrowUp" ) { active = (active - 1 + items.length ) % items.length ; render (); e.preventDefault (); } if (e.key === "Enter" && active >= 0 ) { $q.value = items[active].name + " " ; items = []; render (); } if (e.key === "Escape" ) { items = []; render (); } }); $list.addEventListener ("click" , e => { const row = e.target .closest (".row" ); if (!row) return ; $q.value = items[+row.dataset .i ].name + " " ; items = []; render (); $q.focus (); }); document .addEventListener ("click" , e => { if (!e.target .closest (".field, #list" )) { items = []; render (); } });</script > </body > </html >
Demo 的实际效果:
中文关键词同样能查(走中文列):
五、什么时候用哪个库
Booru 库(source 留空)
Pixiv 库(source=pixiv)
标签写法
hatsune_miku(下划线)
hatsune miku / 初音ミク(空格)
热度含义
图站作品数
Pixiv 作品数
有类别信息
有(画师/作品/角色…)
无,恒为 0
适合
Danbooru / Gelbooru / Yande 等图站场景
Pixiv 场景、中文找日文 tag
两边是两套独立维护的词表 ,同一个概念可能查到不同结果,这是正常的——one_piece 在 Booru 库排第一的是「连体泳衣」,在 Pixiv 库排第一的才是「海贼王」。
六、相关接口
接口
作用
说明
POST /api/tags/sync
从线上仓库重新拉取整套标签库
管理操作,需在站内后台点击,约 38 MB
POST /api/tag/correct
提交某条标签的译名纠错
站内点「纠错」时调用,见 纠错说明
标签库本身由两个长期维护的 GitHub 仓库提供,站内可一键同步:
ffdkj/Pixiv_Tag-Chinese-English-Translation-Table
ffdkj/Danbooru_Tag-Chinese-English-Translation-Table
七、常见问题 Q:能不能商用 / 大量调用? 数据来自公开翻译表,接口本身无鉴权。请自用、低频调用;要做正式产品建议自己拉取那两个仓库建库。
Q:为什么中文搜出来的 count 很大但 name 是空的? 不会为空。若某个词在库里没有译名,cn_name 会是空字符串(""),此时该条只会靠 name 命中。
Q:source 传了 danbooru 会怎样? 和留空一样,走 Booru 库。只有精确传 pixiv 才切到 Pixiv 库。
Q:返回 30 条,怎么拿更多? 不支持分页。把关键词写长一点、更精确即可。也可以把github的翻译库下到本地就可以换着花样查了。
本文对应的站点版本:webooru_server_version_sfw,接口行为以线上实测为准(示例响应均为真实返回)。