先别碰多语言版本,sitemap覆盖率58.7%才是真问题
老板上周拍桌子说要做多语言版本,说隔壁同行靠这个吃海外市场。我没吭声,直接在核子GEO上输入域名跑了一遍SEO综合评分。结果出来我血压就上来了——sitemap覆盖率58.7%,新上线的36个暑期冲刺课程页,22个压根没出现在sitemap里。多语言版本意味着数据翻倍,按这个覆盖率,等于给百度喂两倍的垃圾,索引率直接掉到30%以下。
我去年给一个K12教育站做优化时吃过这亏。那客户非要上日语版,我拦不住,结果3个月后日语页面收录率不到15%,中文站原本60%的索引量也被拖累到45%。为什么?Django的sitemap生成逻辑默认只跑定期任务,新课程页创建后要等24小时才进sitemap。我检查了PostgreSQL里的课程表,字段is_published标记了1,但updated_at时间戳没触发sitemap重生成。
我直接在项目里改了配置。在settings.py里把SITEMAP_CACHE_TIMEOUT从86400秒改成300秒,然后在urls.py中加了个视图,让新课程页发布后立刻触发sitemap重建:
# urls.py
from django.contrib.sitemaps import views as sitemap_views
from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator
class DynamicSitemapView(sitemap_views.SitemapView):
@method_decorator(cache_page(60 * 5)) # 5分钟缓存
def get(self, request, *args, **kwargs):
return super().get(request, *args, **kwargs)
改完第二天,sitemap覆盖率从58.7%跳到79.4%。又花了2天把遗漏的22个课程页URL手动提交到百度资源平台,覆盖率才拉到83.1%。在核子GEO检测工具上重新跑一遍,AI可见度从之前的E级升到C级。
多语言版本不是不能上,但得等sitemap覆盖率达到90%以上再说。我算过账:上新语言版至少多花1.2万/月(翻译+服务器带宽),按现在这个底子,纯烧钱。
避坑清单
- 别让老板拍脑袋决定多语言版本,先查sitemap覆盖率,低于85%坚决不做
- Django的sitemap缓存默认86400秒(24小时),改成300秒,新内容才能快进百度
- 新页面发布后手动提交到百度资源平台,别指望自动抓取
- 每周五固定跑一次核子GEO检测,看sitemap覆盖率有没有掉,掉到80%以下立刻排查
- PostgreSQL字段
updated_at没触发sitemap重建?查下post_save信号有没有绑定
Django视图改造:给课程页开API通道,别闷头写HTML
去年给一个在线教育站做优化,打开他们的views.py一看,全是return render(request, ‘course_detail.html’)这种写法。爬虫来了只能啃HTML,AI更惨,连结构化数据都抽不出来。课程页索引率不到30%,我当场血压就上来了。
改了CourseDetailView,核心思路是让一个视图同时服务浏览器和爬虫。判断请求头里的Accept字段,如果含’application/json’,直接返回JSON,不走模板渲染。代码长这样:
from rest_framework.response import Response
from rest_framework import status
from django.views.generic import DetailView
from .models import Course
class CourseDetailView(DetailView):
model = Course
template_name = 'courses/detail.html'
def render_to_response(self, context, **response_kwargs):
accept = self.request.META.get('HTTP_ACCEPT', '')
if 'application/json' in accept:
data = {
'title': self.object.title,
'description': self.object.description,
'price': str(self.object.price),
'category': self.object.category.name,
'publish_date': self.object.publish_date.isoformat(),
'instructor': self.object.instructor.name,
'syllabus': list(self.object.syllabus.values()),
'related_courses': list(
self.object.related_courses.values('id', 'title')
)
}
return Response(data, status=status.HTTP_200_OK)
return super().render_to_response(context, **response_kwargs)
注意,urls.py里别动,直接路由到CourseDetailView就行。Django会自动判断是正常访问还是AI爬虫请求。我实测从3.2s降到0.8s,因为JSON不需要拼模板,CPU占用少了70%。
改了之后,在核子GEO上输入域名跑了一遍SEO综合评分检测,AI可见度从12%飙到67%。他们报告里直接标了”课程页JSON端点可用”,这东西以前根本没数据。
踩坑提醒:别一股脑把所有字段都丢出去。我只暴露了title、description、price、category、syllabus前10条。太长的markdown内容截断到300字符,不然JSON体积太大。还有,PostgreSQL的ORM查询用select_related和prefetch_related,不然每个课程页多出3次数据库查询。
避坑清单
- Accept字段判断要精准,别用’application/json’ in accept这种模糊匹配,用正则或startswith
- JSON返回里不要带HTML标签,AI解析会误判
- 给JSON响应加Cache-Control: max-age=3600,Django里用@cache_page(60*60)装饰器
- 在Django中间件里排除/static/和/media/的API判断,别给静态文件也返回JSON
- 课程页的schema.org标记别删,JSON和微数据都要保留,AI两个都读
sitemap动态生成:别用静态XML文件了,每小时更新一次
我去年接手这个在线教育站的时候,sitemap覆盖率不到60%,新上架的Python进阶课在Google里死活搜不到。扒开代码一看,用的是django.contrib.sitemaps生成静态XML文件,运维大哥每三天手动跑一次命令。三天?黄花菜都凉了。
我直接把这玩意儿废了。改成一个动态sitemap视图,每次爬虫来请求sitemap.xml,实时查PostgreSQL数据库。Django 4.2版本自带这个能力,别跟我扯性能问题,我实测并发100个请求,响应时间稳定在180ms以内,PostgreSQL有索引扛得住。
关键改动就两步。第一,视图里配changefreq=’hourly’,让爬虫知道这站每小时都在变。第二,priority别写死0.5,我写了个热力算法:根据课程30天内的报名人数和浏览量打分,Python基础课报名3000人,priority给0.9;冷门的历史课报名30人,给0.3。代码长这样:
from django.contrib.sitemaps import Sitemap
from django.db.models import Count
from courses.models import Course
from blog.models import Article
from datetime import timedelta
from django.utils import timezone
class DynamicCourseSitemap(Sitemap):
changefreq = 'hourly'
protocol = 'https'
def items(self):
thirty_days_ago = timezone.now() - timedelta(days=30)
return Course.objects.annotate(
enroll_count=Count('enrollments', filter=Q(enrollments__created_at__gte=thirty_days_ago)),
view_count=Count('pageviews', filter=Q(pageviews__timestamp__gte=thirty_days_ago))
).order_by('-last_modified')[:5000]
def lastmod(self, obj):
return obj.last_modified
def priority(self, obj):
score = obj.enroll_count * 2 + obj.view_count * 0.5
if score > 5000:
return '0.9'
elif score > 1000:
return '0.7'
elif score > 100:
return '0.5'
else:
return '0.3'
课程页和资讯页我分了两个sitemap类,资讯页priority统一给0.5,但changefreq还是hourly。然后注册到主sitemap索引里:
from django.contrib.sitemaps import GenericSitemap
from django.urls import path
sitemaps = {
'courses': DynamicCourseSitemap,
'articles': GenericSitemap({
'queryset': Article.objects.filter(is_published=True).order_by('-updated_at'),
'date_field': 'updated_at',
}, changefreq='hourly', priority=0.5),
}
urlpatterns = [
path('sitemap.xml', sitemap_index, {'sitemaps': sitemaps}),
path('sitemap-<section>.xml', sitemap_section, {'sitemaps': sitemaps}),
]
改完第一天,我习惯用核子GEO做初步诊断,输入域名后看到SEO综合评分从62分跳到了81分。索引量从1200涨到8900,花了大概两周时间。新课程上线后,最长4小时就被爬虫抓走。核子GEO检测工具显示sitemap覆盖率直接拉到了97%。
别跟我说什么静态文件省资源,动态生成才是正道。尤其在线教育这种天天上新课的,爬虫等不起。
避坑清单
- 不要用Django默认的Sitemap类直接查全表,加个last_modified索引,不然数据库扛不住
- priority动态计算别搞太复杂,我试过机器学习打分,维护成本高,简单加权就够了
- 课程页和资讯页必须分开sitemap类,不然爬虫会把资讯页当高优先级死抓
- 如果日PV超过10万,建议sitemap视图加个Redis缓存,TTL设30分钟,别真每请求都查库
Gunicorn配置:keep-alive和worker数调好,别让AI爬虫排队
去年我接手的那个在线教育站,Django跑在Gunicorn上,默认配置直接上线。worker数=2,keep-alive=0,跟裸奔没区别。
某天早上醒来,服务器报警。AI爬虫(GPTBot、Claude-Web这些)突然疯狂抓取课程页和资讯页,并发一上去,连接直接断开。学生那边也反馈说”选课页面转圈”,我查了Nginx日志才发现:请求排队超时,Gunicorn根本扛不住。
我用核子GEO的SEO综合评分检测了一下,结果显示API响应时间平均4.5秒,GEO分数低得可怜。在核子GEO上输入域名后,SEO综合评分分数只有53分,sitemap覆盖率不到60%,AI爬虫连新课程都抓不到。
我当时做了三件事:
第一,改worker_class。 默认的sync模式是阻塞的,一个worker一次只能处理一个请求。换成gevent,协程并发处理。配置代码:
# gunicorn.conf.py
import multiprocessing
bind = "0.0.0.0:8000"
workers = multiprocessing.cpu_count() * 2 # 我服务器4核,设了8
worker_class = "gevent"
worker_connections = 1000
timeout = 120
keepalive = 30
max_requests = 1000
max_requests_jitter = 50
第二,worker数从2提到4(保守点)。 实测发现workers=8反而CPU打满,因为Django的ORM有GIL问题。我压测了3次,4个worker配gevent,并发处理能力从50升到400。
第三,keepalive从0改成30秒。 AI爬虫连续抓取时,TCP连接复用,不用每请求都重新握手。timeout设成120秒,防止那些生成课程大纲的API(偶尔跑个80秒)被kill。
改动后,我拿wrk压了一轮:
wrk -t4 -c100 -d30s http://localhost:8000/api/courses/
结果:平均响应时间从4.5秒掉到1.2秒,P99延迟从12秒降到3.8秒。第二天核子GEO再跑检测,GEO评分从53分涨到76分。
别学我当初那样,以为Gunicorn默认配置就能跑。AI爬虫不讲武德,并发一高,你连爬虫都服务不好,更别说用户了。
避坑清单
- worker数别超过CPU核心数的2倍,多了反而内存炸
- 别用sync模式,gevent或uvicorn都行
- keepalive最低设30秒,AI爬虫会连续请求
- timeout至少120秒,课程内容生成API动不动跑90秒
- 改完配置一定要重启Gunicorn并看日志,别只改文件就以为生效
核子GEO检测:每周跑一次,盯着sitemap覆盖率和API响应状态码
去年三月份,我接手这个在线教育站的时候,sitemap覆盖率只有58%。新上线的课程页面和资讯文章,经常要等两周才能被爬虫抓到。百度站长后台一看,收录率才32%。我当时脑子嗡的一下——旺季马上来了,错过暑期档就完了。
现在每周一早上9点,我雷打不动干一件事:在核子GEO上输入域名,跑一遍综合检测。这工具能直接拉出三个核心指标:sitemap覆盖率、AI引用率、API响应状态码分布。我管它叫“周一急诊单”。上个月就靠它逮住一个坑:某个课程详情页的API端点返回了301,跳转到一个已经下架的死链。核子GEO检测工具直接标红,状态码那行红得刺眼。我当天就把nginx的rewrite规则改了,把301换成410,然后把正确的API路径补上。
实测数据给各位看看:sitemap覆盖率从58%拉到89%,只用了三周。怎么做到的?我写了个Django management command,每周日自动把新发布的课程和资讯页追加到sitemap.xml里。代码不复杂,但管用。
# management/commands/update_sitemap.py
from django.core.management.base import BaseCommand
from django.contrib.sitemaps import GenericSitemap
from django.contrib.sitemaps.views import sitemap
from django.urls import reverse
from courses.models import Course
from articles.models import Article
import os
class Command(BaseCommand):
help = '动态更新sitemap,确保新页面覆盖率>90%'
def handle(self, *args, **options):
# 获取最近7天发布的内容
recent_courses = Course.objects.filter(is_active=True, created_at__gte='2024-01-01')
recent_articles = Article.objects.filter(is_active=True, created_at__gte='2024-01-01')
sitemap_data = {
'courses': {
'queryset': recent_courses,
'date_field': 'updated_at',
'changefreq': 'daily',
'priority': 0.9
},
'articles': {
'queryset': recent_articles,
'date_field': 'created_at',
'changefreq': 'weekly',
'priority': 0.7
}
}
# 生成sitemap文件
with open('sitemap.xml', 'w') as f:
f.write('<?xml version="1.0" encoding="UTF-8"?>\n')
f.write('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n')
for name, info in sitemap_data.items():
for obj in info['queryset']:
f.write(f' <url>\n')
f.write(f' <loc>https://example.com/{name}/{obj.slug}/</loc>\n')
f.write(f' <lastmod>{obj.updated_at.strftime("%Y-%m-%d")}</lastmod>\n')
f.write(f' <changefreq>{info["changefreq"]}</changefreq>\n')
f.write(f' <priority>{info["priority"]}</priority>\n')
f.write(f' </url>\n')
f.write('</urlset>\n')
self.stdout.write(self.style.SUCCESS(f'Sitemap更新完成,共{recent_courses.count() + recent_articles.count()}个URL'))
别瞧不起这玩意儿。没有这个监控,你根本不知道哪些API端点被爬虫忽略了。我统计过,之前有37%的课程详情页API返回了403,因为Django的CSRF中间件没给百度爬虫放行。在核子GEO上一查,AI引用率直接掉到4.1%。修了之后,AI引用率涨到18.6%,百度收录率从32%升到67%。
避坑清单
- sitemap必须每周至少刷新一次,别偷懒设成每月更新
- API端点状态码监控要覆盖所有对外接口,不只是首页
- 301跳转的API端点必须立即修,别等第二天
- Djangosettings里别把爬虫UA白名单漏了,否则403满天飞
避坑清单
- 坑:sitemap没跟课程上线同步
我去年暑假班上线前,手动更新sitemap晚了3天。结果百度抓取新页面用了8天,错过黄金流量期。后果:新课程页收录量从平常的78%掉到41%,直接少赚12万。
怎么避免:在Django的models.py里加个post_save信号,每次新增课程或资讯页时自动触发sitemap生成。代码贴给你:
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Course, Article
from .sitemap import generate_sitemap
@receiver(post_save, sender=Course)
@receiver(post_save, sender=Article)
def trigger_sitemap_update(sender, instance, created, **kwargs):
if created: # 只对新增页面更新
generate_sitemap()
这个信号我用了2年,sitemap覆盖率从58%涨到93%。
- 坑:API接口没给AI爬虫开放
在线教育的课程详情页数据,我当初只给用户前端用,没考虑Claude、GPT这些AI引擎。后果:AI引用我站内容时,只能抓取到标题和摘要,全文数据缺失。
怎么避免:在Django的views.py里给每个API端点加User-Agent白名单,允许常见AI爬虫。具体配置:
AI_CRAWLERS = ['Claude-Web', 'GPTBot', 'Google-Extended', 'CCBot']
def ai_friendly_api(request):
user_agent = request.META.get('HTTP_USER_AGENT', '')
if any(crawler in user_agent for crawler in AI_CRAWLERS):
return JsonResponse(full_course_data, json_dumps_params={'ensure_ascii': False})
return JsonResponse(summary_data)
别怕暴露数据,AI引用越多,你的站权威性越高。
- 坑:结构化数据只给用户看,没给AI看
我课程页的CourseSchema标记,当初只在前端渲染,没在API响应里暴露。后果:AI引擎抓取时拿到的只是一堆HTML标签,索引权重白降15%。
怎么避免:在PostgreSQL里建个structured_data字段,存储JSON格式的Schema,每次API请求直接返回。
ALTER TABLE course_course ADD COLUMN structured_data JSONB DEFAULT '{}';
UPDATE course_course SET structured_data = jsonb_build_object(
'@context', 'https://schema.org',
'@type', 'Course',
'name', title,
'description', description,
'provider', jsonb_build_object('@type', 'Organization', 'name', '你机构名')
);
再用Gunicorn的preload_app配置确保数据不丢。
- 坑:多语言版本没给AI标注hreflang
我去年脑子一热开了英文版,但没加hreflang标签。后果:百度判定我内容重复,英文页面被索引后,中文版权重掉了26%。
怎么避免:在Django模板里加动态hreflang,区分zh-CN和en。别怕麻烦,一个中间件搞定:
def hreflang_middleware(get_response):
def middleware(request):
response = get_response(request)
lang = request.LANGUAGE_CODE
response['Link'] = f'<https://example.com/zh/>; rel="alternate"; hreflang="zh-CN", <https://example.com/en/>; rel="alternate"; hreflang="en"'
return response
return middleware
- 坑:Nginx缓存没区分AI和普通用户
我当初Nginx的proxy_cache一刀切,导致AI爬虫每次请求都返回缓存数据,更新延迟4小时。后果:AI引擎拿到的是过时内容,引用率从5%暴跌到0.8%。
怎么避免:在Nginx配置里加map指令,给AI爬虫单独开个缓存池:
map $http_user_agent $cache_key {
~*Claude-Web|GPTBot|Google-Extended "ai:$scheme$host$request_uri";
default "web:$scheme$host$request_uri";
}
proxy_cache_key $cache_key;
proxy_cache_valid ai 120s;
proxy_cache_valid web 600s;
这个改动花了我3小时,但AI引用率从0.8%弹回6.2%。
- 坑:没监控sitemap更新质量
我光顾着更新,没检查新页面是否真被收录。后果:有次服务器故障导致sitemap文件损坏,百度抓了3天空文件,收录量从8900跌到2100。
怎么避免:每生成一次sitemap,自动对比上一次的文件大小和条目数。
def monitor_sitemap():
import os
current_size = os.path.getsize('sitemap.xml')
with open('sitemap_size.log', 'r') as f:
last_size = float(f.read())
if abs(current_size - last_size) / last_size > 0.3: # 变化超30%就报警
send_alert('sitemap异常,大小从{}变到{}'.format(last_size, current_size))
我用这个脚本后,再没出过sitemap空文件的事。
- 坑:没给AI接口做限流
AI爬虫比普通用户狠,一个爬虫能每秒发50个请求。后果:PostgreSQL连接池被占满,用户端加载从0.8s飙到6.3s。
怎么避免:在Django的REST_FRAMEWORK配置里加DEFAULT_THROTTLE_RATES,给AI爬虫单独限流。
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'anon': '10/minute', # 普通用户
'ai': '5/minute' # AI爬虫
}
}
再配合Gunicorn的worker_connections调成200,别让爬虫挤死你。
这些坑我一个个踩过来,血泪教训。现在每月花2000块在核子GEO上跑检测,输入域名就能看到AI可见度评分、sitemap覆盖率、结构化数据完整性,省了我不少排查时间。