一为导航热榜空白?不续费也能用:免费 JSON 热榜源配置教程
适用:OneNav / 一为导航 系列主题(含子主题 onenavsub) 场景:
/hotnews/今日热榜页 20 张卡片全部空白,提示「服务状态不可用」
一、先搞清楚为什么会空白
打开热榜页按 F12 → Network,点右上角刷新图标,会看到卡片发出去的请求是:
//ionews.top/api/get.php?rule_id=100000&key=xxxx
返回内容是:
{"state":0,"title":"iotheme","subtitle":"续期",
"data":[{"title":"服务状态不可用!","hot":"¥98"}]}
结论很直白:卡片走的是 ioTheme 的付费热榜接口,订阅到期了。
主题里每张热榜卡都有一个「类型」字段,共四种:
| 类型 | 走哪里 | 费用 |
|---|---|---|
api |
ionews.top 付费热榜接口 | 要钱,订阅到期就空白 |
weixin |
同上(微信公众号列表) | 要钱 |
json |
站内 admin-ajax.php 服务端抓取自定义源 |
免费 |
rss |
同上,解析 RSS/XML | 免费 |
所以「不付费也能用」的答案是:把卡片从 api 切到 json,再配一组免费数据源。主题本来就内置了这条免费通道,只是默认卡片全给了 api。
免费通道的好处:
- 服务端抓取,没有浏览器跨域问题,也不怕目标站要登录态;
- 自带 60 分钟缓存(transient),不会每次刷新页面都去打数据源;
- 后台可视化配置,一行代码都不用改。
二、找一个免费的热榜接口
我自己实测过一圈,结论先给你,省得踩坑:
| 候选 | 实测 | 说明 |
|---|---|---|
| uapis.cn 聚合接口 | ✅ 推荐 | 免费、无需 key、20+ 平台、结构完全统一 |
| DailyHotApi 公共镜像 | ⚠️ 不稳 | 同样结构,但实测 weibo/zhihu 直接 500,baidu 返回缓存空数据 |
| 微博/知乎官方接口 | ❌ | 微博 403、知乎 401,都要签名或登录态 |
| 各平台自己抓 HTML | ❌ | 主题只认 JSON 和 XML,抓不了网页 |
推荐接口长这样(把 TYPE 换成平台名):
https://uapis.cn/api/v1/misc/hotboard?type=weibo
返回结构固定:
{
"type": "weibo",
"update_time": "2026-10-06T10:36:05.868Z",
"list": [
{"index":1, "title":"热搜词", "url":"https://s.weibo.com/...", "hot_value":"1435288", "extra":{}}
]
}
可用的 TYPE(我逐个跑通的):
baidu 36kr juejin bilibili xiaohongshu history sspai weibo zhihu huxiu
smzdm qq-news douyin kuaishou ithome toutiao tieba hupu v2ex acfun
csdn sina thepaper weread nodeseek hellogithub coolapk 51cto jianshu ifanr
想换别的免费源也行,只要它是「返回 JSON + 里面有个对象数组」就行,下面的节点配置照葫芦画瓢即可。
三、后台配置(核心步骤)
第 1 步:添加数据源
后台 → 设置 → ◉ 自定义热榜 → 「JSON数据源」里新增一条,字段这样填:
| 后台字段 | 填什么 | 本例值 |
|---|---|---|
| 名称 | 卡片标题(会覆盖卡片上的名字) | 微博热搜榜 |
| 小标题 | 卡片标题旁的小字,没热度就留空 | 热度 |
| 地址 | 接口 URL | https://uapis.cn/api/v1/misc/hotboard?type=weibo |
| 数据节点 | 数组所在的层级路径,支持 . 分层 |
list |
| 标题节点 | 每条里的标题字段 | title |
| 链接节点 | 每条里的链接字段 | url |
| 热度节点(可留空) | 热度/阅读量字段 | hot_value |
| 链接补全 | 链接不全时用 %s% 占位补全 |
留空 |
| 缓存时间 | 分钟,默认 60 | 60 |
保存即可,不需要动请求头、请求数据。
第 2 步:把卡片切到 JSON
后台 → 主题设置 → 今日热榜 → 今日热榜页列表,逐张卡片改两个字段:
- 类型:
API→JSON - 热榜ID:填它在「自定义热榜 → JSON数据源」里的序号(从 1 开始,按顺序)
⚠️ 序号 = 卡片顺序。主题是按下标
$rule_id - 1去取源的,所以: 源的排列顺序必须和卡片顺序一一对应;中途删掉一条源,后面所有卡片就全错位了。 改完顺序记得把缓存清一下(或者等 60 分钟自动过期)。
最多可配 30 张卡,20 张也够用。
四、最容易踩的 4 个坑(都是实测结论)
1. 有些平台压根没有链接字段
比如某些类型返回 "url": "",配了链接节点也没用,点进去是空链接。两种解法:
- 换源:换成同类别的、有链接的平台(我就是把「吾爱破解」换成了「稀土掘金」);
- 用链接补全兜底:像「历史上的今天」没链接,我把链接节点填
title,链接补全填https://www.baidu.com/s?wd=%s%,点标题直接百度搜这条历史事件,体验反而更顺。
2. 有些平台没有热度值
实测没有热度的类型(hot_value 全是空字符串):
36kr、smzdm、qq-news、ithome、toutiao、v2ex、thepaper、51cto、jianshu、ifanr、coolapk
这些卡片的热度节点直接留空。留空时主题返回 type:"",卡片右侧不会渲染热度列;
反之如果填了节点但值是空的,右侧会多出一块空白区域,很难看。
3. 「名称」会覆盖卡片标题
主题 JS 里是 $title.text(response.title) —— 数据源里的「名称」才是页面上显示的标题。 所以:源的名称要和卡片名保持一致,否则你改了卡片名也不生效。
4. 微信、API 两种类型别抱幻想
api 和 weixin 都走 ionews 付费接口,订阅没续就一直是「服务状态不可用」。 不想付费就彻底切到 json / rss。
五、验证与排错
改完先别急着看页面,直接测接口最快(N 换成热榜ID):
https://你的站点/wp-admin/admin-ajax.php?action=get_hot_data&type=json&id=1
成功长这样(state:1 且 data 是数组):
{"state":1,"title":"微博热搜榜","subtitle":"热度","type":"hot",
"data":[{"index":1,"title":"...","link":"...","hot":"..."}]}
常见错误对照表:
| 返回 | 原因 | 怎么修 |
|---|---|---|
state:0, code:202, data:"没有获取到内容。" |
节点路径写错 / 源返回的不是 JSON | 检查「数据节点」,用浏览器直接打开接口 URL 看结构 |
state:0, code:0, data:7 |
连不上目标服务器(域名挂了/写错) | 换源 |
state:0, code:0, data:3 |
地址是空的或格式非法 | 检查「地址」字段 |
state:1 但标题空白 |
「标题节点」写错 | 对照 JSON 里的真实字段名 |
| 页面还是旧的 | 缓存 / CDN | 等 60 分钟,或 Ctrl+F5,或后台清缓存 |
页面本身如果发现请求还是发往 ionews.top,说明卡片类型没改成 JSON(或 CDN 缓存了旧 HTML)。 看页面源码里 data-api_type="json" 的数量即可确认。
六、偷懒办法:批量写入(可选)
20 张卡一张张点太痛苦,可以写个一次性脚本直接写选项。下面这份是完整可跑的版本 (7.3 / 8.2 语法均验证过),思路是「先探测 → 再写入 → 自动验证 → 删文件」四步:
用法:存成 热榜-fix.php,放到 wp-content/themes/你的主题/ 下,然后访问:
热榜-fix.php?key=你的随机串&step=probe ← 只检测,不改动任何数据
热榜-fix.php?key=你的随机串&step=run ← 检测 + 写入 + 端到端验证
热榜-fix.php?key=你的随机串&step=run&self=1 ← 写入后自动删除本文件
<?php
/**
* OneNav 热榜免费源批量配置脚本
*
* 放到:wp-content/themes/你的主题目录/
* 用法:
* 检测(不改动): 热榜-fix.php?key=xxx&step=probe
* 写入配置: 热榜-fix.php?key=xxx&step=run
* 写入后自毁: 热榜-fix.php?key=xxx&step=run&self=1
*
* ⚠️ 用完必须删除本文件
*/
/* 1) 换成你自己的随机串(别用原文,也别外传) */
$KEY = 'put-your-own-random-string-here';
/* 2) 主题目录 → 站点根目录,层级不对就自己调 */
require dirname(__FILE__) . '/../../../wp-load.php';
if (!isset($_GET['key']) || !hash_equals($KEY, (string) $_GET['key'])) {
http_response_code(403);
exit('403');
}
/* 兜底:确保主题的解析函数已加载 */
if (!function_exists('io_get_free_hot_data') || !function_exists('get_json_hot_data')) {
require get_theme_file_path('/inc/hot-search.php');
}
if (!function_exists('io_get_free_hot_data') || !function_exists('get_json_hot_data')) {
exit('主题热榜函数加载失败');
}
@set_time_limit(600);
header('Content-Type: text/html; charset=utf-8;');
$UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36';
/**
* 3) 卡片方案:一行一张卡,顺序 = 页面卡片顺序 = 热榜ID(从 1 开始)
* n 卡片标题(会覆盖页面上的名字)
* t 接口的平台 type
* hot 热度节点,该平台没有热度就填 ''
* link 链接节点,没链接就填 'title' 配合 lr 兜底
* lr 链接补全模板(%s% 会被 link 的值替换),没有就填 ''
* keep true=保留原卡片名与图标,false=用上面的 n 改名并换默认图标
*/
$PLAN = array(
array('n' => '百度热点', 't' => 'baidu', 'hot' => 'hot_value', 'link' => 'url', 'lr' => '', 'keep' => true),
array('n' => '36氪人气榜', 't' => '36kr', 'hot' => '', 'link' => 'url', 'lr' => '', 'keep' => true),
array('n' => '微博热搜榜', 't' => 'weibo', 'hot' => 'hot_value', 'link' => 'url', 'lr' => '', 'keep' => true),
array('n' => '知乎热榜', 't' => 'zhihu', 'hot' => 'hot_value', 'link' => 'url', 'lr' => '', 'keep' => true),
array('n' => '历史上的今天', 't' => 'history', 'hot' => '', 'link' => 'title', 'lr' => 'https://www.baidu.com/s?wd=%s%', 'keep' => true),
// ... 按需继续加,最多 30 行
);
/* ========== 以下不用改 ========== */
function hnf_sources($plan)
{
$out = array();
foreach ($plan as $p) {
$out[] = array(
'name' => $p['n'],
'subtitle' => $p['hot'] ? '热度' : '',
'url' => 'https://uapis.cn/api/v1/misc/hotboard?type=' . $p['t'],
'datas' => 'list',
'title' => 'title',
'link' => $p['link'],
'hot' => $p['hot'],
'link_regular' => $p['lr'],
'cache' => 60,
'request_type' => 'get',
);
}
return $out;
}
/* 用主题自己的解析管线试跑,保证写进去一定能跑通 */
function hnf_probe($src, $ua)
{
$r = wp_remote_get($src['url'], array('timeout' => 20, 'redirection' => 5, 'headers' => array('User-Agent' => $ua)));
if (is_wp_error($r)) {
return array(false, 0, $r->get_error_message());
}
if ((int) wp_remote_retrieve_response_code($r) !== 200) {
return array(false, 0, 'HTTP ' . wp_remote_retrieve_response_code($r));
}
$json = json_decode(wp_remote_retrieve_body($r), true);
if (!is_array($json)) {
return array(false, 0, '返回不是 JSON');
}
$rows = io_get_free_hot_data($json, $src['datas']);
if (!is_array($rows) || !$rows) {
return array(false, 0, '数据节点 ' . $src['datas'] . ' 取不到');
}
$res = get_json_hot_data($rows, $src);
if (empty($res['state']) || empty($res['data'])) {
return array(false, 0, '格式化为空');
}
return array(true, count($res['data']), $res['data'][0]['title']);
}
$step = isset($_GET['step']) ? (string) $_GET['step'] : 'probe';
$sources = hnf_sources($PLAN);
echo '<meta charset="utf-8"><style>body{font:14px/1.7 system-ui;margin:24px}'
. 'table{border-collapse:collapse}td,th{border:1px solid #ddd;padding:6px 10px}'
. '.ok{color:#0a7a2f}.bad{color:#c62828}</style><h3>热榜免费源配置</h3>';
/* ---- 步骤1:探测 ---- */
echo '<p><b>步骤1 探测</b>(走主题自己的解析代码)</p><table>';
echo '<tr><th>#</th><th>卡片</th><th>类型</th><th>状态</th><th>条数</th><th>首条</th></tr>';
foreach ($PLAN as $i => $p) {
list($ok, $num, $msg) = hnf_probe($sources[$i], $UA);
printf(
'<tr><td>%d</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>',
$i + 1,
htmlspecialchars($p['n'], ENT_QUOTES, 'UTF-8'),
htmlspecialchars($p['t'], ENT_QUOTES, 'UTF-8'),
$ok ? '<b class="ok">可用</b>' : '<b class="bad">' . htmlspecialchars($msg, ENT_QUOTES, 'UTF-8') . '</b>',
$ok ? $num : '-',
htmlspecialchars(mb_substr((string) $msg, 0, 40, 'UTF-8'), ENT_QUOTES, 'UTF-8')
);
}
echo '</table>';
if ($step === 'probe') {
echo '<p>仅检测,未改动。确认后访问:<code>热榜-fix.php?key=' . htmlspecialchars($KEY, ENT_QUOTES, 'UTF-8')
. '&step=run</code></p>';
exit;
}
/* ---- 步骤2:写入 ---- */
$theme = get_option('io_get_option');
if (!is_array($theme)) {
$theme = array();
}
$cards = (isset($theme['hot_home_list']) && is_array($theme['hot_home_list'])) ? $theme['hot_home_list'] : array();
if (!$cards && function_exists('all_topnew_list')) {
$cards = all_topnew_list();
}
/* 备份原配置(改坏了照它还原) */
echo '<p><b>原配置备份(先复制保存):</b></p><textarea rows="5" style="width:100%;font:12px monospace">'
. htmlspecialchars(wp_json_encode($cards, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), ENT_QUOTES, 'UTF-8')
. '</textarea>';
/* 写 JSON 源 */
$hot = get_option('io_hot_search_list');
if (!is_array($hot)) {
$hot = array();
}
$hot['json_list'] = $sources;
if (!isset($hot['rss_list']) || !is_array($hot['rss_list'])) {
$hot['rss_list'] = array();
}
update_option('io_hot_search_list', $hot);
/* 写卡片:类型→json,热榜ID→序号 */
$ico = get_theme_file_uri('/assets/images/hot_ico.png');
$n = 0;
foreach ($cards as $i => $card) {
if (!isset($PLAN[$i])) {
break;
}
$card['hot_type'] = 'json';
$card['rule_id'] = (string) ($i + 1);
if (empty($PLAN[$i]['keep'])) {
$card['name'] = $PLAN[$i]['n'];
$card['ico'] = $ico;
$card['description'] = $PLAN[$i]['n'];
}
$cards[$i] = $card;
$n++;
}
$theme['hot_home_list'] = $cards;
if (isset($theme['is_show_hot']) && !$theme['is_show_hot']) {
$theme['is_show_hot'] = true;
}
update_option('io_get_option', $theme);
echo '<p><b class="ok">已写入 ' . $n . ' 张卡片,JSON 源 ' . count($sources) . ' 条</b></p>';
/* ---- 步骤3:端到端验证(回环调自己的 admin-ajax) ---- */
echo '<table><tr><th>ID</th><th>state</th><th>条数</th></tr>';
for ($i = 1; $i <= count($PLAN); $i++) {
$u = add_query_arg(array('action' => 'get_hot_data', 'type' => 'json', 'id' => $i), admin_url('admin-ajax.php'));
$r = wp_remote_get($u, array('timeout' => 40, 'headers' => array('User-Agent' => $UA)));
$j = is_wp_error($r) ? array() : (array) json_decode(wp_remote_retrieve_body($r), true);
$st = isset($j['state']) ? (int) $j['state'] : -1;
$num = (isset($j['data']) && is_array($j['data'])) ? count($j['data']) : 0;
printf('<tr><td>%d</td><td class="%s">%d</td><td>%d</td></tr>', $i, ($st === 1 && $num) ? 'ok' : 'bad', $st, $num);
}
echo '</table>';
echo '<p>收尾:① 打开 /hotnews/ 首次加载会现抓数据,稍等几秒;'
. '② 记得 <b>删除本文件</b>;③ 想调整卡片去「设置 → 自定义热榜」「主题设置 → 今日热榜」。</p>';
if (isset($_GET['self']) && $_GET['self'] == '1') {
@unlink(__FILE__);
echo '<p><b class="ok">本文件已自毁</b></p>';
}
只改三处就能用:$KEY 换成自己的随机串、$PLAN 换成你的卡片方案、 require 那行的层级按你的主题目录深度调。
注意事项(血泪教训):
- 先跑
probe看报告再run:探测阶段会用主题真实解析代码试跑每个源,哪条不通一目了然; - 先备份:
run会自动把原hot_home_list打印成 JSON,先复制保存再继续,改坏了照它还原; $KEY一定要换:这脚本能改数据库选项,别把默认值原样挂公网上;- 跑完立刻删文件(或者直接带
&self=1让它自毁); - 串行请求别太贪:如果站点开了 CDN/防 CC,
run那步(探测+验证共几十次请求) 可能会超时返回 524 —— 超时不代表失败,配置往往已经写进去了, 用第四节的admin-ajax验证 URL 复查一遍即可。
七、维持与恢复
- 数据多久更新:主题按「缓存时间」存 transient,默认 60 分钟,够用;
- 免费源挂了怎么办:第三方免费接口的存活全看作者心情,建议每个月手动访问一次 第二步的接口 URL,发现返回不对就去「自定义热榜」换条源,1 分钟的事;
- 想换回官方数据:去 ioTheme 商店续费热榜 API 激活码,然后把卡片类型改回
API、 热榜ID 换成 ionews 的规则 ID(100000那串)即可; - 换源前记得备份原配置,主题设置页本身也支持导出。
八、效果自查清单
- 页面源码里
data-api_type="json"有 N 张,api是 0 admin-ajax.php?action=get_hot_data&type=json&id=1..N全部返回state:1- F12 里不再出现
ionews.top的请求 - 卡片有标题、有可点击的链接、有热度(无热度的平台右侧不显示空白块)
- 一次性脚本已删除
搞完这几条,热榜页就是完完全全的「白嫖」状态了——不续费、不改主题核心代码、后台点几下就好。
本文所有接口与字段均为实测结论,第三方免费源随时可能失效,以你实测为准,效果请看本站今日热榜 - 网站领地。