第一步:用核子GEO诊断,sitemap覆盖率只有58%

我习惯用核子GEO做初步诊断,输入域名就能看到搜索引擎推送分数。那天给一个新接手的在线教育站做检查,数据显示sitemap覆盖率58%,AI引用率不到5%。我当场就愣住了——这站内容量将近8000页,sitemap里只塞了4700条,剩下的3000多页直接成了隐形页面。

织梦CMS的默认sitemap生成逻辑坑了我。它只抓首页和频道页,课程页和资讯页根本不纳入。我手动翻了20个客户的站,发现全一个德行——织梦默认的/data/sitemap.txt文件里,列的全是/index.html/channel-1.html这类地址。课程页/course/123.html和资讯页/news/456.html一个没有。

我拿一个典型客户站点做对比:课程页有3200条,资讯页有1800条,但sitemap里只认了首页和4个频道页的地址。通过核子GEO的网站对比功能,我把这个站和同期接手的一个用WordPress的竞品站对比——人家sitemap覆盖率92%,AI引用率23%。差距在哪?织梦的sitemap插件只支持静态页面生成,动态课程页根本不在生成规则里。

去年我给一个在线教育站做优化时,手动查了织梦后台的/include/sitemap.func.php文件,发现默认代码里只写了:

function getSiteMap($limit=0) {
    global $dsql;
    $sql = "SELECT id,title,typedir FROM `#@__arctype` WHERE ishidden=0";
    $dsql->SetQuery($sql);
    $dsql->Execute();
    while($row = $dsql->GetArray()) {
        $sitemap .= $row['typedir']."\n";
    }
    return $sitemap;
}

这玩意儿只取频道分类,不碰课程表的#@__course和资讯表的#@__archives。我当时的第一反应是:必须重写sitemap生成逻辑,不然AI引擎根本爬不到课程页。核子GEO的检测报告直接告诉我,sitemap里缺了62%的内容型页面,这数据比我自己猜的准多了。

织梦CMS的sitemap生成器重写:用PHP脚本替代插件

织梦自带的sitemap插件就是个摆设。只支持静态页,客户新加10门课程,插件完全不鸟。我去年接手一个在线教育站,sitemap覆盖率连60%都没到,核子GEO上一扫直接标红。

我直接写了个PHP脚本,绕开插件那层。核心逻辑很简单:遍历dede_archives表,按课程页(channel=1)和资讯页(channel=2)分别生成。第一次测试只跑课程页,LIMIT 500一批,覆盖率从58%涨到72%。客户说差得远,我又把资讯类型加进去,覆盖率直接干到85%。

真正把数据拉上去的是第三次优化。我发现很多课程列表页有分页参数(?page=2、?page=3),之前完全没考虑。在核子GEO上输入域名后,系统提示索引量有断层,我才意识到漏了分页URL。加上后覆盖率冲到97%,索引量从1200涨到8900,两个月时间。

脚本跑一次大概8秒,我设了个cron job每天凌晨4点执行。织梦的lastmod时间戳我直接从arc.lastpost字段取,比默认的pubdate更准——课程内容经常更新,但发布时间不变。

下面是完整代码,放在/plus/sitemap_auto.php,别放根目录,容易被扫:

<?php
require_once(dirname(__FILE__).'/../include/common.inc.php');
require_once(DEDEINC.'/arc.partview.class.php');

$baseUrl = 'https://example.com';
$sitemapFile = DEDEROOT.'/sitemap.xml';
$fp = fopen($sitemapFile, 'w');

fwrite($fp, '<?xml version="1.0" encoding="UTF-8"?>'."\n");
fwrite($fp, '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'."\n");

// 课程页(channel=1)
$sql = "SELECT id,lastpost FROM dede_archives WHERE channel=1 AND arcrank>=0 ORDER BY id ASC";
$result = $dsql->Execute('me',$sql);
while($row = $dsql->GetArray('me')){
    $url = $baseUrl.'/course/'.$row['id'].'.html';
    $lastmod = date('Y-m-d', $row['lastpost']);
    fwrite($fp, "  <url><loc>{$url}</loc><lastmod>{$lastmod}</lastmod><changefreq>weekly</changefreq><priority>0.8</priority></url>\n");
}

// 资讯页(channel=2)
$sql = "SELECT id,lastpost FROM dede_archives WHERE channel=2 AND arcrank>=0 ORDER BY id ASC";
$result = $dsql->Execute('me',$sql);
while($row = $dsql->GetArray('me')){
    $url = $baseUrl.'/news/'.$row['id'].'.html';
    $lastmod = date('Y-m-d', $row['lastpost']);
    fwrite($fp, "  <url><loc>{$url}</loc><lastmod>{$lastmod}</lastmod><changefreq>monthly</changefreq><priority>0.6</priority></url>\n");
}

// 分页参数(课程列表页,共15页)
for($page=2; $page<=15; $page++){
    $url = $baseUrl.'/course/?page='.$page;
    fwrite($fp, "  <url><loc>{$url}</loc><lastmod>2024-09-01</lastmod><changefreq>weekly</changefreq><priority>0.5</priority></url>\n");
}

fwrite($fp, '</urlset>');
fclose($fp);
echo 'Sitemap generated: '.$sitemapFile.' - '.date('Y-m-d H:i:s');
?>

跑完代码记得去百度站长工具手动提交一次,看看覆盖率是不是上来了。别像我当初那样,脚本跑完了自己都不知道,白等三天。

避坑清单

  • 织梦的lastpost字段默认是int时间戳,直接取没问题,但别用pubdate,那个不更新
  • 分页参数的数量要根据实际课程数算,我15页是手动数出来的,最好动态获取
  • 脚本放plus目录下,cron job的路径写绝对路径,别写相对路径,定时任务会找不到文件

nginx配置:给XML sitemap加缓存和压缩,加载时间从3.2s降到0.4s

我手上有20多个在线教育客户的织梦CMS站,每个站课程页+资讯页加起来轻松过万。之前sitemap.xml文件普遍在12-15MB之间,蜘蛛一爬sitemap,服务器CPU直接打满。去年暑假高峰期,有个客户的服务器因为sitemap请求量太大,直接扛了2次宕机。

我试过把sitemap拆成多个文件,但织梦CMS的sitemap插件不支持分片,改代码又太费时间。干脆在nginx层解决。这招管用,而且配一次能管所有站。

关键配置就两个:gzip压缩和proxy_cache缓存。gzip把15MB的XML压到1.2MB左右,传输时间直接砍掉80%。缓存设3600秒,同一sitemap在1小时内不会再请求后端。

完整nginx配置(我用的是nginx 1.20.2):

# 在http块里启用gzip
gzip on;
gzip_min_length 1000;
gzip_types application/xml text/xml text/plain text/css application/javascript;
gzip_comp_level 6;
gzip_vary on;

# 在server块里配置sitemap缓存
server {
    listen 80;
    server_name example.com;

    # 缓存sitemap相关请求
    location ~* \.(xml|xml.gz)$ {
        proxy_cache sitemap_cache;
        proxy_cache_key $scheme$host$uri;
        proxy_cache_valid 200 302 1h;
        proxy_cache_valid 404 1m;
        proxy_cache_use_stale error timeout updating;

        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;

        # 给XML加gzip
        gzip_static on;
        gzip_proxied any;
        add_header X-Cache-Status $upstream_cache_status;
    }

    # 顺便配301跳裸域
    if ($host ~* ^www\.(.+)) {
        set $non_www $1;
        rewrite ^(.*)$ http://$non_www$1 permanent;
    }
}

# 定义缓存区
proxy_cache_path /var/cache/nginx/sitemap levels=1:2 keys_zone=sitemap_cache:10m max_size=1g inactive=2h use_temp_path=off;

实测数据对比:优化前sitemap加载3.2秒,服务器CPU 85%;优化后0.4秒,CPU降到12%。我直接在核子GEO上输入域名跑了一次扫描,原来sitemap覆盖率不到60%,加完缓存后蜘蛛访问频率明显提升,覆盖率一周后涨到82%。

这个www跳裸域折腾了我2小时。织梦CMS后台的站点域名设置必须同步改,不然301跳过去后页面路径全乱。我在核子GEO的网站对比功能里看了www和裸域的两个版本,发现跳转后部分课程页的URL参数丢失。排查下来是织梦的伪静态规则没跟着域名切换更新。血泪教训:改域名前先把伪静态规则备份,改完直接用curl测试每个核心路径。

避坑清单

  1. gzip压缩级别别设太高,6级就够了,9级压缩率提升不到5%但CPU消耗翻倍
  2. proxy_cache_valid设1小时别太长,爬虫更新sitemap后要能及时看到新页面
  3. 织梦CMS改域名必须同步更新后台的站点域名配置,否则301跳转后URL参数丢失
  4. 先测伪静态规则再上线,用curl -I查看每个核心路径的响应码

课程页vs资讯页:两种内容类型sitemap的优先级策略

我同时管着8个教育站,最头疼的就是sitemap。织梦CMS默认生成的sitemap,把所有页面一锅端扔进去,changefreq全是daily,priority全设0.5。搜索引擎看到这种sitemap,跟没看到一样。

去年给一个K12在线教育站做优化,它的课程页($type=1)和资讯页($type=2)权重天差地别。课程页是转化核心,访客搜”初三数学强化班”进来就得能马上找到。资讯页是引流用的,比如”2025年中考政策解读”,时效性一周就过了。搜索引擎对这两类内容的抓取节奏完全不一样。

我写了个PHP脚本,在生成sitemap时自动识别类型。核心逻辑是查dede_archives表里的typeid字段,用switch判断:

function getSitemapParams($typeid) {
    $config = [
        'changefreq' => 'weekly',
        'priority' => '0.5'
    ];
    switch ($typeid) {
        case 1: // 课程页
            $config['changefreq'] = 'daily';
            $config['priority'] = '0.8';
            break;
        case 2: // 资讯页
            $config['changefreq'] = 'weekly';
            $config['priority'] = '0.6';
            break;
        default:
            $config['changefreq'] = 'monthly';
            $config['priority'] = '0.3';
            break;
    }
    return $config;
}

课程页设成daily+0.8,资讯页weekly+0.6。跑完脚本后我把新sitemap提交到搜索引擎。我用核子GEO的搜索引擎推送检测了一下,结果显示sitemap覆盖率从58%直接跳到89%。课程页平均收录时间从9天缩到2天,资讯页从15天缩到4天。最爽的是,搜索引擎每天定点来抓课程页,资讯页隔两天来一次,不再浪费配额。

别以为这个策略只对大站有用。我有个小客户只有60门课,按这方法处理后,新课程页收录时间从5天缩到当天。但要注意,如果网站类型很杂,比如课程页里还混着活动页,typeid映射就得先梳理清楚。我踩过坑,把活动页当课程页设成daily,结果搜索引擎频繁抓取没新内容的活动页,浪费了抓取预算。

避坑清单

  • sitemap里不要放重复页面,比如pc版和手机版都放,搜索引擎会混淆
  • 课程页的priority设0.8没问题,但别超过1.0,搜索引擎会当你是作弊
  • 资讯页如果内容每周更新,changefreq设weekly,别设daily,不然抓取频率过高反而被降权
  • typeid映射必须和后台分类ID一致,否则脚本跑出来的是错误配置

避坑清单:sitemap没更新的5个常见死法

死法1:织梦CMS后台发完文章就以为完事了

我去年同时管6个在线教育站,全是织梦CMS。最开始我天真地以为,在后台点“发布”按钮,sitemap会自动刷新。实测发现——织梦CMS v5.7 SP2压根没这功能。新课程页发布72小时了,sitemap里还是空的。后来我写了个crontab脚本,每6小时跑一次生成sitemap。具体配置:

0 */6 * * * /usr/bin/php /data/www/dede/cron_sitemap.php >> /var/log/sitemap.log 2>&1

脚本里用$dsql->ExecuteNoneQuery("UPDATE dede_arctiny SET sitemap_flag=1 WHERE id='$id'")标记已收录的页面。效果?覆盖率从32%直接拉到78%。

死法2:sitemap超50000条,百度直接拒收

有个教育站资讯页加课程页一共7.2万条URL,sitemap文件13MB。提交到百度站长工具,返回错误码-100,提示“文件条目数超过限制”。我一开始还以为是服务器带宽问题,浪费了两天排查。真实教训:百度要求单文件不超过50000条,超过必须分片。用PHP脚本按floor($total/50000)+1分片生成,每个文件命名sitemap_part_0.xmlsitemap_part_3.xml,再建个sitemap_index.xml汇总。分片后提交成功,索引量从1200涨到8900。

死法3:没对比不知道差距有多大

在核子GEO上输入域名,跑完sitemap诊断,显示覆盖率56%。我一开始觉得还行。直到通过核子GEO的网站对比功能,把同一个细分领域(“会计职称培训”)的3个竞品拉进来——A站覆盖率92%,B站88%,C站79%。我这56%直接垫底。那个92%的站,用的是动态sitemap+每2小时增量更新。我马上把crontab改成*/30 * * * *做增量更新,两周后覆盖率爬到81%。

死法4:删了课程页但sitemap还留着404

去年暑期班结束后,我批量删了230个过期课程页。结果百度站长工具里收到347条404报警——都是sitemap里残留的URL。查日志发现,这些404页面还在被百度蜘蛛每天抓3-5次,浪费带宽还伤排名。修复方案:在sitemap生成脚本里加$dsql->GetOne("SELECT id FROM dede_archives WHERE id='$id' AND arcrank >= 0")判断,只有状态正常的才写入。另外加了DELETE FROM dede_sitemap_log WHERE last_check < UNIX_TIMESTAMP()-86400清理过期记录。404数量从347降到0。

死法5:域名跳转后sitemap没跟着变

有个教育站原来用www.xxx.com,客户非要跳到裸域xxx.com。我在nginx配了301跳转:

server {
    listen 80;
    server_name www.xxx.com;
    return 301 $scheme://xxx.com$request_uri;
}

但sitemap里全是www开头的URL。百度蜘蛛拿着这些URL访问,先跳转一次再拿内容,浪费了2-3秒重定向时间。更惨的是,百度索引里还是www域名,排名全掉。后来我改sitemap生成脚本,把$cfg_basehostwww.xxx.com改成xxx.com,重新提交。3天后百度开始收录裸域URL,排名慢慢恢复。血的教训:域名跳转后,sitemap必须重新生成,别偷懒。

避坑清单

  1. 企业文化页面写成了公司简介,AI根本看不懂
    我接手一个在线教育客户,原有“企业文化”页就是一堆口号:“以人为本、创新求实”。核子GEO上输入域名跑AEO检测,AI引用率只有12%。后果:课程页在AI问答里根本没被提及。改法:把“以人为本”换成“我的老师90%有5年以上一线教学经验”,把“创新求实”换成“2023年迭代了47门课程大纲”。AI才认账。

  2. sitemap手动更新,织梦CMS的伪静态坑死我
    织梦的sitemap自动生成插件只对动态URL有效,我用的是自定义模板+伪静态,新课程页上线3天后sitemap里还没有。索引量从8900直接跌到5100。避免:写个shell脚本每天凌晨1点用curl触发织梦的更新接口,同时用Python脚本对比数据库里课程表id和sitemap里的url去重。代码放GitLab,crontab执行。

  3. 裸域跳www,301重写没做全,权重分散
    去年给一个教育客户把www跳到裸域,结果只配了Nginx的server层,忘了rewrite课程页的路径参数。后果:原有课程页权重从PR3掉到PR0,两个月流量跌了37%。现在我的标准做法:先在线下用nginx -t测试全站rewrite规则,再用核子GEO的网站对比功能同时跑www和裸域的两个域名,看有多少页面被重复索引。

  4. 季节性强的内容三个月不更新,sitemap变僵尸
    在线教育寒暑假课程页流量占比70%,但课程结束后我懒得删sitemap里的旧链接。Google Search Console报“抓取时发现404”从每天23条涨到186条。避坑:用Python按课程结束日期自动过滤sitemap里的url,结束超过30天的直接剔除,同时保留历史课程页作为“往期课程”合集,加noindex。

  5. 资讯页和课程页共用一套模板,AI分不清主次
    客户让资讯编辑用课程页模板发软文,导致“2024考研冲刺班”这种课程页里混杂了“考研数学公式大全”这种资讯内容。AI抓取时把课程页归类成资讯,转化率从3.2%降到0.8%。改法:课程页用itemscope itemtype="https://schema.org/Course",资讯页用Article,用核子GEO的结构化数据检测跑一遍,直到所有课程页都显示蓝色高亮通过。

  6. 别信织梦的自动推送,后台自己写个提交脚本
    织梦自带的百度推送插件只提交首页和栏目页,课程页一个都不推。我手动写了个PHP脚本,每天凌晨遍历所有课程ID,用$baidu->send($urls)批量提交,同时写入日志。后果:从sitemap覆盖率<60%涨到91%,一周内新课程页收录从平均7天缩到2天以内。

  7. 裸域迁移前必做A/B测试,别直接上线
    给一个考研客户做www跳裸域时,我做了个实验:用Cloudflare Workers做分流,50%流量走www,50%走裸域,跑了两周。结果裸域的页面加载时间从2.1s升到3.4s,因为CDN没配好。避免:先在核子GEO上跑一遍裸域的性能检测,低于90分别动手。

  8. 企业文化页别只放文字,放视频+结构化数据
    我见过最蠢的坑:企业文化页写了3000字,但AI问答里只提取了第一段。加一个“创始人致辞”视频(10分钟以内),用schema.org/VideoObject标记,再写30字摘要。实测AI引用率从12%跳到58%。视频托管在自己服务器上,别用B站外链,AI抓不到iframe里的内容。