一为导航热榜空白?不续费也能用:免费 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')
        . '&amp;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 那行的层级按你的主题目录深度调。

注意事项(血泪教训):

  1. 先跑 probe 看报告再 run:探测阶段会用主题真实解析代码试跑每个源,哪条不通一目了然;
  2. 先备份:run 会自动把原 hot_home_list 打印成 JSON,先复制保存再继续,改坏了照它还原;
  3. $KEY 一定要换:这脚本能改数据库选项,别把默认值原样挂公网上;
  4. 跑完立刻删文件(或者直接带 &self=1 让它自毁);
  5. 串行请求别太贪:如果站点开了 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 的请求
  • 卡片有标题、有可点击的链接、有热度(无热度的平台右侧不显示空白块)
  • 一次性脚本已删除

搞完这几条,热榜页就是完完全全的「白嫖」状态了——不续费、不改主题核心代码、后台点几下就好。


本文所有接口与字段均为实测结论,第三方免费源随时可能失效,以你实测为准,效果请看本站今日热榜 - 网站领地。

© 版权声明

相关文章

没有相关内容!

暂无评论

none
暂无评论...