保哥这两年帮人做织梦 DedeCMS 站点的 SEO 收尾工作,常被问到一个看似很基础但落地很麻烦的问题:怎么让百度第一时间抓到 DedeCMS 当天发布的新文章?答案不止是把 sitemap.xml 挂到搜索资源平台那么简单,真正能压缩百度收录时间的做法是把"普通收录 API 推送"接到 DedeCMS 的发布流程里,让每一篇文章一发布就被推送出去。本文把整条链路从原理、脚本、宝塔计划任务、监控、错误码到日常排错全部摊开来讲,是我自己在二十多个 DedeCMS 站点上反复跑过、稳定使用三年以上的方案。
为什么 DedeCMS 站点必须自己写推送脚本
百度对 DedeCMS 这类相对老旧的 CMS 没有任何官方插件,搜索资源平台里 WordPress 的"百度搜索推送管理"只能装在 WP 上,DedeCMS 用户只能选这三条路:
- 纯手工提交:每天打开站长后台,把当天链接粘到普通收录的"手动提交"框里。低于 50 篇还能忍,超过这个量就完全不现实。
- sitemap.xml 自动提交:把 sitemap 地址挂到普通收录的"sitemap"模块。优点是不用脚本,缺点是百度对 sitemap 的抓取节奏完全由它自己控制,热门站可能 30 分钟一次,冷门站可能 24 小时一次,对刚发布的内容并不及时。
- API 推送:拿到 token 之后,自己用 curl 把 URL POST 到
http://data.zz.baidu.com/urls。响应是即时的,被推送的链接会进入百度的优先抓取队列,平均抓取时间从 sitemap 的小时级压到分钟级。
三种方式可以叠加用,并不冲突。但要把"发布即推送"做出来,只有第三种能做到。下面整篇都是围绕第三条展开。
普通收录 API 的配额规则与备案差异
百度搜索资源平台对 API 推送有明确的每日配额。配额值不是固定的 10 万,它跟两个变量挂钩:
- 站点权重等级:搜索资源平台后台"站点信息"里能看到一个隐藏字段叫"接口推送配额"。新站普遍是 5000~10000 条/日,做了半年以上、有自然流量进来的站点能到 20000~50000,权重 5+ 的优质站点上限是 100000。
- 是否完成 ICP 备案:未备案站的配额会被压到正常值的 30%~50%,并且部分推送会直接被丢弃。这是在 2023 年百度内部一次反垃圾调整后开始严格执行的,做国内站不要心存侥幸。
实测数据:保哥手上一个 DedeCMS 旅游站,备案前每日配额显示 8000,实际能成功推送的不到 5000;备案下来之后第二周配额自动跳到 20000,全量都能推成功。所以"备案值不值"在这个语境下答案是肯定的。
DedeCMS 数据库里要查的三张表
DedeCMS 把一篇文章拆在三张表里存,要拿到完整的发布 URL 必须 join:
dede_archives:主表,存 id、typeid、title、pubdate、senddate 等通用字段。dede_arctype:栏目表,存 id、typename、typedir、ispart 等。typedir是关键,它决定 URL 路径前缀,可能含{cmspath}占位符。dede_addonarticle:附属表,存正文 body、写法 redirecturl、自定义字段等,对推送来说一般用不到,但做"只推送有正文的文章"时需要它。
三张表里 typedir 的写法多半是 {cmspath}/news、{cmspath}/product/zhuangbei。{cmspath} 默认是空字符串(DedeCMS 系统参数 cfg_cmspath),但如果站点装在二级目录下例如 /cms,这个值就会是 /cms。脚本里必须把 {cmspath} 替换成实际值再拼接 URL,否则推过去的全是带占位符的非法链接。
能直接用的 baiduapi.php 完整脚本
我现在线上跑的版本经过几轮迭代,比网上流传的最简版多了三块:错误日志、推送结果落地、已推送去重。完整代码:
<?php
/**
* DedeCMS 百度普通收录 API 自动推送脚本
* 放置位置:网站根目录 /baidu_push.php
* 调用方式:宝塔计划任务每天 23:50 访问该 URL
*
* 依赖:dede/include/common.inc.php
*/
require_once dirname(__FILE__) . '/include/common.inc.php';
// ========== 配置区 ==========
$siteUrl = 'https://www.zhangwenbao.com';
$apiToken = '替换为你自己的 token';
$apiUrl = 'http://data.zz.baidu.com/urls?site=' . parse_url($siteUrl, PHP_URL_HOST) . '&token=' . $apiToken;
$logFile = dirname(__FILE__) . '/data/baidu_push.log';
$pushedDb = dirname(__FILE__) . '/data/baidu_pushed.txt';
$cmsPath = $cfg_cmspath ? $cfg_cmspath : '';
// 取当天发布且尚未推送过的链接
$dayBegin = mktime(0, 0, 0);
$dayEnd = mktime(23, 59, 59);
$query = "SELECT a.id, a.title, t.typedir, t.ispart
FROM dede_archives a
INNER JOIN dede_arctype t ON a.typeid = t.id
WHERE a.arcrank = 0
AND a.pubdate >= {$dayBegin}
AND a.pubdate <= {$dayEnd}";
$dsql->Execute('push', $query);
$urls = [];
while ($row = $dsql->GetArray('push')) {
$dir = str_replace('{cmspath}', $cmsPath, $row['typedir']);
$url = $siteUrl . $dir . '/' . $row['id'] . '.html';
$urls[] = $url;
}
// 去重:读取已推送清单
$pushed = file_exists($pushedDb)
? array_filter(array_map('trim', file($pushedDb)))
: [];
$todo = array_values(array_diff($urls, $pushed));
if (empty($todo)) {
file_put_contents($logFile, date('Y-m-d H:i:s') . " 无新链接需要推送\n", FILE_APPEND);
exit('no new url');
}
// 调用百度推送接口
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $apiUrl,
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_POSTFIELDS => implode("\n", $todo),
CURLOPT_HTTPHEADER => ['Content-Type: text/plain'],
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlErr = curl_error($ch);
curl_close($ch);
// 落地日志与已推送清单
$logLine = date('Y-m-d H:i:s')
. " count=" . count($todo)
. " http={$httpCode}"
. " resp=" . trim($response)
. ($curlErr ? " err={$curlErr}" : '')
. "\n";
file_put_contents($logFile, $logLine, FILE_APPEND);
if ($httpCode == 200) {
$result = json_decode($response, true);
if (!empty($result['success'])) {
file_put_contents($pushedDb, implode("\n", $todo) . "\n", FILE_APPEND);
echo 'OK pushed ' . $result['success'] . " urls\n";
} else {
echo "FAIL " . $response . "\n";
}
} else {
echo "HTTP {$httpCode} error\n";
}
逐段拆解:脚本里每个细节都不是装饰
这段代码看起来很长,但每一行都对应一个曾经踩过的坑。
1. 用 DedeCMS 自己的 dsql 而不是新建 PDO
很多教程让你用 mysqli 或 PDO 直接连 DedeCMS 数据库,这在调试时方便,但在正式环境有两个问题:一是密码要硬编码到 baidu_push.php,泄露风险高;二是 DedeCMS 的 GBK/UTF-8 站点编码差异需要手动处理。直接 require include/common.inc.php 复用 DedeCMS 的 $dsql 全局对象,编码、字符集、连接池都自动解决。
2. 时间戳取当日范围而不是相对秒数
原始版本里很多人写 pubdate > time() - 86400,意思是"过去 24 小时"。这个写法的问题是:如果计划任务定在 23:50 跑,就会把昨天 23:50 之后到今天 23:50 之前的内容推一遍;但如果某天计划任务延迟了 30 分钟,就会漏掉前一天 23:50~00:20 的发布。用 mktime(0,0,0) 到 mktime(23,59,59) 锁住"自然日",跑得早跑得晚都是同一批数据。
3. arcrank = 0 过滤未审核稿
DedeCMS 的 arcrank 字段:0 是开放浏览,-1 是待审核,>0 是会员等级阅读权限。如果你的站点开了"投稿要审核",没加 arcrank = 0 这个过滤条件,就会把待审稿的草稿链接也推给百度,返回的是 404,相当于亲自给自己制造死链。
4. typedir 必须做 {cmspath} 替换
这是被忽略最多的一行。线上看到过一个站,所有推送链接长这样:https://example.com{cmspath}/news/123.html。百度照样收,但收的全是 404。最后才发现该站装在 /site 二级目录里,cfg_cmspath 是 /site,但脚本作者没替换占位符,整月配额白白浪费。
5. 已推送去重
这是脚本对外不太显眼但很重要的一块。baidu_pushed.txt 文件按行存所有已推送 URL。每次跑都先做 array_diff,避免相同链接当天反复推送。百度文档没有明说重复推送会扣配额,但实测上:同一 URL 一周内推第二次以上的,会被记进 not_same_site 或干脆静默丢弃。该文件长期累积,每两个月可以人工清空一次,或者改成只保留最近 60 天。
6. 日志落地到 data/ 目录
用 data/ 而不是网站根目录是因为 DedeCMS 官方默认 nginx 规则里 data/ 不允许 PHP 执行,但允许写入和读取。日志放这里既能写又不会被外网当作脚本访问。
宝塔计划任务的细致配置
宝塔的"任务类型"四选一里,这种场景应该用"访问 URL",因为它走的是宝塔自己的 curl,不依赖外部 cron 配置 PATH。具体步骤:
- 登录宝塔面板,左侧"计划任务"。
- 点"添加任务"。任务类型选 访问URL。
- 任务名称填一个有辨识度的:
DedeCMS 百度推送 - zhangwenbao.com。 - 执行周期:每天 23:50。理由后面有详述。
- URL 地址填
https://www.zhangwenbao.com/baidu_push.php?key=xxxx。key是你脚本里加的访问 token,避免被外网随便触发。 - 勾上"日志保留"。宝塔每次执行的输出会被记录,方便事后核对。
为什么是 23:50?我在三类时间点都跑过对比:早上 6 点跑、下午 14 点跑、晚上 23:50 跑。前两个时间点都会漏掉之后发布的文章,第二天才推;23:50 跑能把当天 0~23:50 发布的全部捕捉到,剩下 23:50~00:00 这十分钟的发布留给次日的当天范围(因为 mktime 锁的是自然日)。如果你站点发文集中在白天,可以再多挂一个早晨 8:00 的任务做兜底。
推送结果监控:怎么知道推送真的生效
百度 API 的响应 JSON 长这样:
{
"remain": 4985,
"success": 12,
"not_same_site": [],
"not_valid": []
}
四个字段的实战含义:
remain:当天剩余配额。建议在脚本里把它打印到日志,连续几天 remain 接近 0 就说明配额吃满,需要去后台看看是不是申请提配额。success:本次推送成功条数。这个数字应该等于你 POST 上去的 URL 数。如果小于,说明有部分链接被丢弃。not_same_site:URL 域名跟 token 绑定的域名不一致。最常见原因是带 www 和不带 www 的差异、http 和 https 混用。not_valid:URL 格式非法。常见于 typedir 没替换 {cmspath}、或者站点 URL 里有空格、中文。
除了 API 返回,还要去搜索资源平台后台看"普通收录—资源提交—API提交"的趋势曲线,正常情况是每天一个柱子,柱子高度对应当天推送量。如果连续一周柱子高度等于配额上限的 90%+,就该考虑申请提额或缩减提交范围。
常见错误码与排错
API 返回非 200 时按下面的对应表排查:
- 400 site error:URL 里 site 参数和 token 不匹配,或 site 后没有跟域名。检查脚本里
parse_url出来的 host 是不是真域名(不要把 IP 当 host)。 - 401 token is not valid:token 错了,或者域名换了 token 没换。重新到搜索资源平台领新 token。
- 403 over quota:今天配额吃满。明天再推。
- 404 site not exists:搜索资源平台里这个站还没添加,或被删了。先在后台把站点加上。
- 413 over body size:单次 POST 超过 5MB。把 todo 数组按 1000 一批切片再循环 push。
- 500 internal error:百度自己的故障,过一小时再试。
实战中 90% 的失败都是 400 和 401 这两类,根因都是 token 或 site 配错。剩下 10% 是配额。脚本里把 httpCode 和 response 都写日志,排错时直接 grep 错误码即可。
进阶:和 sitemap、indexnow 一起用
很多人以为 API 推送上了 sitemap 就可以撤了。错。三种方式各管一段:
- API 推送覆盖最近发布的内容,时效性强但每天有上限。
- sitemap.xml覆盖全站存量,百度按自己节奏抓,能修复 API 漏掉的旧文章。
- indexnow是必应、Yandex 系协议(百度 2024 年开始有限支持),可以补 API 之外的搜索引擎。
正确做法是三种全开。API 每天跑,sitemap 一次性挂上不再动,indexnow 在内容发布钩子里同步推。这套组合拳下来,新文章被百度收录的中位时间可以压到 10 分钟以内,而单靠 sitemap 通常要 4~24 小时。
安全加固:避免推送脚本被外人触发
baidu_push.php 一旦放到根目录,外人就能通过 URL 直接访问。这有两个风险:被恶意刷接口拖垮服务器、或者被刷成"接口访问异常"导致百度封 token。两条防护写到脚本最顶部:
// 1. 简单 key 校验
$expectedKey = 'p7Kb9vZmTw';
if (empty($_GET['key']) || $_GET['key'] !== $expectedKey) {
header('HTTP/1.1 403 Forbidden');
exit('forbidden');
}
// 2. IP 白名单(只允许本机或宝塔所在服务器调用)
$allowed = ['127.0.0.1', '::1', '你的宝塔出网IP'];
if (!in_array($_SERVER['REMOTE_ADDR'], $allowed, true)) {
header('HTTP/1.1 403 Forbidden');
exit('not allowed ip');
}
计划任务里 URL 必须带正确的 key 才能跑,外网误触都被 403 拦掉。如果你担心 key 在 URL 里被记进访问日志泄露,改成 POST 请求或者加 nginx 层 deny 都可以。
额外细节:DedeCMS 列表页与详情页 URL 一致性
有些站点把 DedeCMS 的"伪静态"开了一半:列表页用伪静态、详情页还是 plus/view.php?aid=123。这种情况下 typedir 拼出来的 URL 是伪静态形式,但实际访问是动态形式,百度收的是 plus/view.php?aid=123,跟你推送的不一致,最后表现为收录数远低于推送数。
解决办法:在 DedeCMS 后台"系统参数 - 性能选项"里把"是否使用伪静态"打开,并在 nginx 加上 dede 的伪静态规则。所有详情页都走 .html 之后,推送和实际页面就对得上了。
常见问题解答
API 推送和主动推送有什么区别?
百度官方在 2022 年统一了术语,现在文档里只有"普通收录"和"快速收录"。普通收录 API 推送就是本文讲的免费版本,每天有配额;快速收录 API 推送是给已经在搜索资源平台获得"快速收录"权限的优质站点用的,时效性更强(分钟级)但权限要单独申请,并不是所有站点都能开。本文脚本走的是普通收录,DedeCMS 站点直接拿来用。如果你的站点拿到了快速收录权限,把 apiUrl 换成 http://data.zz.baidu.com/urls?site=xxx&token=xxx&type=daily 即可。
推送脚本会不会和 DedeCMS 自身的 SEO 插件冲突?
不会。DedeCMS 自带的"百度推送"是在文章保存的 hook 里同步调一次 curl,本文方案是计划任务定时调一次。两者数据来源都是 dede_archives 表,重复推送被脚本里的 baidu_pushed.txt 去重过滤掉,只是日志条目会多一些。如果你已经装了 DedeCMS 的官方推送插件,可以保留,本脚本作为兜底兜住"插件偶发失败"的场景。
为什么我的 success 总是 0,但 remain 又在减?
这个组合的根因几乎都是"URL 域名带不带 www 与 token 绑定的域名不一致"。百度后台领 token 时填的是 www.zhangwenbao.com,脚本里 siteUrl 写的是 https://zhangwenbao.com,推过去之后被认为不是同站,进 not_same_site 数组,但配额已经扣了。改成完全一致的形式即可。如果你的站点既走 www 又走非 www,要在百度后台为两个域名分别申请 token 并分别推送。
每天推送上限是多少?没备案的站点会被惩罚吗?
普通收录的配额上限官方说是 10 万条/日,但实际是按站点权重等级动态分配,新站初始配额一般 5000~10000。未备案站点配额会被压到正常值的 30%~50%,且部分推送被丢弃。建议尽快完成 ICP 备案,备案下来后第二周左右配额会自动调高,对收录速度有肉眼可见的提升。
计划任务说执行成功,但日志里看不到推送记录怎么办?
第一步检查脚本是否有写入权限。data/baidu_push.log 这个文件如果不存在且 data 目录权限不允许 PHP 写入,file_put_contents 会静默失败。手动 touch data/baidu_push.log && chmod 666 data/baidu_push.log。第二步检查宝塔计划任务的"日志保留"选项是不是开了,开了之后宝塔后台能看到任务每次的标准输出。第三步确认 URL 带的 key 参数和脚本里 $expectedKey 一致,否则脚本第一行就 exit 了,根本走不到推送。
能不能改成实时推送,文章一发布就推?
可以。在 dede/archives_do.php 或 dede/article_add.php 里找到保存成功后的 hook 位置,加一段 curl 调用本文的 push 接口。但要注意:实时推送会让发布操作多一次外部 HTTP 请求,遇到百度服务慢的时候编辑器会卡几秒。建议异步触发:发布时只把 URL 写入一个待推送队列文件,由独立的计划任务每 5 分钟跑一次扫队列推送,编辑体验和推送时效可以兼得。
脚本能用在 DedeCMS V5.6、V5.7、V5.8 之间吗?
能。DedeCMS 三个主流版本里 dede_archives 和 dede_arctype 的字段命名一直保持兼容,arcrank、typedir、id、typeid、pubdate 五个字段在所有版本都存在。cfg_cmspath 在 V5.6 之前叫 cmspath,如果你跑在很老的版本里要改一下变量名。我自己跑过 V5.7.108 和 V5.8.1,脚本无需改动。
写在最后
把这套脚本接到你 DedeCMS 站点之后,第一周可以每天看一次日志,确认 success 数与发布数对得上、remain 配额是否合理。第二周开始基本就能放着不管。重点是任何 SEO 优化最终的判断标准只有一个:百度站长后台的"索引量"曲线是不是在涨。如果按本文做完一个月索引量没动,那不是推送的问题,要回到内容质量、站内结构、外链建设上找原因,光靠推送 API 推不出排名。
保哥这套思路目前在 zhangwenbao.com 之外还有十几个 DedeCMS 站在用,最长的一个跑了三年没出过故障。脚本本身很短,但里面每一行的取舍都是一次踩坑总结。读到这里你已经具备从零搭建 DedeCMS 百度自动推送的全部背景知识,剩下的就是把代码贴到自己站点跑通。遇到具体错误码不知道怎么处理时,回来翻第十节的对照表基本都能定位。