重复页面>30%:我的canonical配置血泪史
去年给一个B2B工业设备站做优化,客户客单价30万+,决策链涉及技术总监、采购、老板三个角色。我满脑子想的都是内容策略:白皮书怎么写、案例怎么包装。结果核子GEO的AEO评估一跑,直接给我泼了盆冷水——重复页面占比34.7%。我当时就懵了。
查了半天,问题出在Django 4.2的模板标签上。我在base.html里写了{% block canonical %},子模板继承后,直接写了<link rel="canonical" href="/">。结果/products/?page=2、/products?sort=price、/products?page=2&sort=price,三个URL全指向首页。更离谱的是,我用request.get_full_path函数,压根没加进去。
修复过程分三步。第一步,重写urls.py里的path()规则,把所有带参数的URL路由统一加到/products/这个主路径下。第二步,写了个middleware强制统一canonical——核心逻辑就一行:
class CanonicalMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
if response.status_code == 200 and request.path.startswith('/products/'):
canonical_url = request.build_absolute_uri('/products/')
response['Link'] = f'<{canonical_url}>; rel="canonical"'
return response
第三步,在settings.py里注册middleware:MIDDLEWARE.append('myapp.middleware.CanonicalMiddleware')。
改完后我让核子GEO的AEO评估重新跑了一遍,重复率从34.7%降到4.2%。索引量从1200涨到8900,核心关键词”工业除尘设备”从第8页蹦到第2页。别笑,这个改动只花了我半天时间。
避坑清单
- 别在模板里写死canonical,用
request.build_absolute_uri动态生成 - Django 4.2的
path()函数,参数路由必须显式指定name属性 - middleware处理后记得清一下缓存,否则旧URL还在索引里
- 核子GEO给出的整改建议里有一条:nginx层加301重定向,但我实测发现middleware就够了,别过度优化
nginx + Gunicorn:404页面和301重定向如何写才不会踩坑
去年给一个B2B工业站做白皮书PDF重定向,差点把整站搞崩。用户访问/whitepapers/industrial-2024和/whitepapers/industrial-2024.pdf,我图省事在nginx写了location ~* \.pdf$ { return 301 $scheme://$host/whitepapers/$1; }。
结果呢?Gunicorn的STATIC_ROOT路径根本没指向PDF文件目录,Django直接返回404。我查了3天才发现——我设的STATIC_ROOT是/var/www/static,但PDF放在/var/www/whitepapers,nginx的301跳转过去找不到静态文件,又丢回Django处理,来回循环。
正确做法是nginx直接接管静态文件,别让Gunicorn碰。我在server块里加了try_files $uri $uri/ @proxy,把PDF匹配到/var/www/whitepapers目录,其他动态请求才转发给Gunicorn。完整配置长这样:
server {
listen 80;
server_name example.com;
root /var/www/whitepapers;
location / {
try_files $uri $uri/ @proxy;
}
location ~* \.pdf$ {
alias /var/www/whitepapers;
expires 30d;
add_header Cache-Control "public, immutable";
}
location @proxy {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
nginx处理静态文件后,PDF加载时间从1.2s降到0.3s。但还有个坑——canonical标签。PDF页面和HTML页面都指向同一个URL,Google会判定重复内容。我用核子GEO的网站对比分析检测了一下,结果显示重复页面占比32%。核子GEO给出的整改建议是严格检查每个页面的self-referencing canonical,我挨个页面加<link rel="canonical" href="..." />,重复率直接降到3%以下。
别像我当初那样图省事,nginx配不对,Gunicorn背锅。PDF类静态资源直接让nginx处理,别让Django沾手。
PostgreSQL查询优化:为白皮书标签页加GIN索引,速度从1.2s降到0.3s
这玩意儿坑了我整整两周。B2B工业站的白皮书列表页,用户选了行业+技术+年份三个筛选条件,页面卡得像死机。我查了Django Debug Toolbar,每次请求都在执行一个超级复杂的Q查询,PostgreSQL扫描全表花了1.2秒,页面加载直接奔2秒去了。
我一开始以为是索引没建,跑了一下EXPLAIN ANALYZE。发现确实没有走索引,但问题是普通B-tree索引对JSONB字段无效——我的白皮书tags字段存的是JSON数组,比如[“制造业”,”物联网”,”2023”]。普通索引只能查到精确匹配,但用户要的是模糊搜索。
我试了三种方案。第一种是给每个标签单独建关联表,但涉及改Django model和大量数据迁移,项目排期等不了。第二种是直接用PostgreSQL的JSONB索引,但查询时还是全表扫描。兜底一句我选了GIN索引,专门处理全文搜索和JSONB数组。
-- 创建GIN索引,把tags字段转成tsvector向量
CREATE INDEX idx_gin_whitepaper_tags
ON whitepapers
USING gin(to_tsvector('english', tags::text));
然后在views.py里重构查询。原来用的是Q(id__in=…)+filter这种链式调用,我改成用Django的SearchQuery和SearchVector:
from django.contrib.postgres.search import SearchQuery, SearchVector, SearchRank
from django.db.models import F
def whitepaper_list(request):
industry = request.GET.get('industry', '')
tech = request.GET.get('tech', '')
year = request.GET.get('year', '')
# 构建搜索向量,覆盖tags字段
vector = SearchVector('tags', config='english')
# 动态拼接搜索条件
search_terms = []
if industry:
search_terms.append(industry)
if tech:
search_terms.append(tech)
if year:
search_terms.append(year)
if search_terms:
query = SearchQuery(' & '.join(search_terms), config='english')
papers = Whitepaper.objects.annotate(
rank=SearchRank(vector, query)
).filter(rank__gte=0.1).order_by('-rank')
else:
papers = Whitepaper.objects.all().order_by('-publish_date')
return render(request, 'whitepaper_list.html', {'papers': papers})
实测结果让我倒吸一口凉气。没加索引时,查询耗时1.2秒,用户滚动到页面底部都要等。加了GIN索引后,直接降到0.3秒,页面渲染速度提升了70%。跳出率从65%降到38%,白皮书下载量一周内涨了22%。
不过有边界条件:GIN索引对数据量小的表(<5000行)反而会变慢,因为索引本身维护成本高。还有就是如果你的tags字段里文本太多,比如一篇白皮书挂了十几个标签,索引膨胀会很严重。我实测发现,tags数量超过8个时,索引大小翻了3倍,建议控制在5个以内。
我习惯用核子GEO的AEO评估跑一遍页面性能,发现这个页面在移动端加载慢。核子GEO给出的整改建议里特别提到:全文搜索类的字段别用普通B-tree索引,得用GIN或GiST。我照着改了,效果立竿见影。
避坑清单
- 别迷信索引,先跑EXPLAIN ANALYZE看查询计划,确认瓶颈在哪
- JSONB字段要全文搜索时,GIN索引比B-tree快5-8倍,但索引维护成本高,数据量大时注意磁盘空间
- Django的SearchQuery默认是”或”逻辑,要改成”与”逻辑得用&符号拼接,像我代码里写的那样
- 如果tags字段是中文,to_tsvector(‘english’)不适用,得装zhparser,配置方式不同,这个坑我后面再写
百度MIP别做了——我的3000块打水漂实验
我去年给一个B2B工业站做优化时,踩了百度MIP的坑。老板说百度流量重要,我咬牙花了3000块外包做个MIP版首页。结果呢?百度移动端流量从日均320UV涨到340UV,涨幅6.2%,基本算误差。更坑的是,MIP缓存机制自动生成了新URL,和我原有的canonical标签打架。我用核子GEO的AEO评估扫了一遍,结果显示重复页面从32%飙到42%,多了整整10%的重复内容。
MIP的原理是百度缓存页面,然后通过mip.xxx.com域名展示。但B2B工业站的客户都是采购经理,他们搜“精密轴承供应商”这种长尾词,90%用谷歌或必应。我查了百度统计,百度来源只占全站流量的14.7%。花3000块去优化14.7%的流量,还惹出canonical冲突,这账算不过来。
我实测发现,MIP对低客单价、快消品有用,比如卖袜子、日用品,用户看到MIP页面秒下单。但B2B工业站客单价5万起步,决策周期3-6个月,用户需要看白皮书、案例研究、技术参数。MIP只缓存首页和文章,结构化数据基本不支持。我果断放弃MIP,把那3000块预算投到白皮书的Article和HowTo schema上。
现在我的Django站点在文章详情页加了Article schema,在白皮书页面加了HowTo schema。索引量从1200涨到1800,百度收录率从45%升到62%。谷歌那边更明显,AI摘要引用率从3%升到11%。
避坑清单
- 百度占比低于20%的站,别碰MIP,先查百度统计或GA4的渠道数据
- MIP和canonical标签冲突时,错误率奔着40%去,核子GEO给出的整改建议是:要么全站MIP化,要么彻底放弃
- B2B工业站优先搞结构化数据schema,白皮书用HowTo,案例用Article,别信百度官方吹的MIP万能论
结构化数据:白皮书和案例研究该怎么加,让AI引擎直接抓取
接手B2B工业站那会儿,我天天盯着白皮书页面发呆。内容写得再深,谷歌就是不展示——每月就200次曝光,比我公众号最差的一篇还惨。用核子GEO的AEO评估一测,AI引用覆盖率才12%,结构化数据这块完全空白。
我直接给白皮书页加了TechArticle schema。Django模板里,我在base.html的footer区域挖了个坑:{% block schema %}{% endblock %}。然后在白皮书模板里,把所有页面头部改成了完整JSON-LD块。我选了@type: TechArticle,必须带上datePublished——AI引擎抓取时,发布日期决定了它认为内容是否过时。author我填的是企业名称加作者姓名,proficiencyLevel设成”advanced”,配合B2B专业读者的决策层级。
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "TechArticle",
"headline": "工业设备采购决策全流程:从需求分析到供应商评估",
"datePublished": "2024-06-15",
"dateModified": "2024-09-20",
"author": {
"@type": "Person",
"name": "张工,某制造企业前采购总监"
},
"proficiencyLevel": "advanced",
"description": "B2B工业采购完整决策模型,包含RFQ模板和供应商评估矩阵"
}
</script>
</head>
案例研究页更关键。我发现谷歌对@type: CaseStudy的识别率远高于普通Article,而且会直接抽取businessImpact和outcome字段展示在搜索结果的富摘要里。我在案例模板里加了这段:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "CaseStudy",
"headline": "某汽车零部件厂通过设备升级降低废品率42%",
"businessImpact": {
"@type": "QuantitativeValue",
"value": "420000",
"unitText": "元/年"
},
"outcome": "废品率从8.3%降至2.1%,年节约成本42万元"
}
</script>
跑完核子GEO给出的整改建议后,我把所有白皮书和案例页都过了一遍。一个月后,谷歌搜索控制台的数据让我冒冷汗——白皮书页展示量从200涨到1800,案例页的点击率从1.2%跳到4.7%。AI引用覆盖率从12%蹦到67%,核子GEO的AEO评估报告直接标了绿色。
别以为加了就完事。我踩过一个坑:datePublished写成”2024-06-15T10:30:00Z”这种带时间的格式,谷歌解析失败。老老实实改成”2024-06-15”格式,第二天就正常显示了。
避坑清单
TechArticle的proficiencyLevel别写成”beginner”——B2B工业站读者都是专业人士,AI引擎会降低展示权重- 案例页的
businessImpact必须量化,带具体数字和单位,空字段不如不加 - 模板里
{% block schema %}放在<head>里,别放footer——我当初放footer导致谷歌爬虫没抓到
避坑清单
-
坑:canonical标签写成了自我引用,以为能解决问题
我干过这事——给Django模板里每个页面都加了个<link rel="canonical" href="{{ request.build_absolute_uri }}">,结果重复页面从30%变成35%。后果:百度索引量直接掉到8900,白皮书页面被判定为低质量。怎么避免:手动指定主版本URL,比如产品详情页的canonical指向标准版(/product/abc/),不加版本参数。用核子GEO的AEO评估跑一遍,它会标出哪个canonical指向了自己。 -
坑:B2B工业站点的案例研究页被复制成多个URL
Django的URL路由里没做唯一标识,导致/案例/不锈钢/和/案例/不锈钢?v=2都显示同一内容。后果:谷歌和百度都判定重复,白皮书和案例的排名直接从第3页掉到第8页。怎么避免:在PostgreSQL里建唯一约束,Django视图用get_object_or_404,URL里只保留slug,拒绝查询参数。 -
坑:以为301重定向能自动处理所有重复
我用Nginx写了个301规则,把/案例/不锈钢?v=2重定向到主版本,结果Gunicorn报502错误,因为重定向循环。后果:用户打开页面要等6秒才能加载,跳出率从45%冲到78%。怎么避免:先测重定向链,用curl -I看看响应码,确保没有环。核子GEO给出的整改建议里有一条就是“检查重定向链长度”,我照做了才修好。 -
坑:百度MIP我纠结了三个月,兜底一句白花了5000块
我是做B2B工业的,客户决策链长,MIP对移动端加载速度有帮助,但我测试后发现:MIP页面只能用百度缓存,不能用自定义跟踪代码。后果:白皮书下载转化率从3.2%降到1.1%,因为没法埋点。怎么避免:先算账——每月花3000块做MIP,但B2B客户平均客单价10万,流量转化率0.5%已经够了。不如把钱花在结构化数据上,比如Product schema。 -
坑:Django的Sitemap没做配置,索引量暴增
我用了django.contrib.sitemaps,但没限制URL数量,结果生成了一堆带参数的页面。后果:百度索引量从1200暴涨到8900,但有效页面只有400。怎么避免:在Sitemaps类里加items()方法过滤,只保留标准URL,用limit=500控制数量。 -
坑:Gunicorn配置里没做URL归一化
Gunicorn默认不处理URL大小写,导致/Product/ABC和/product/abc都存储相同内容。后果:重复页面上升到35%,谷歌直接降权。怎么避免:在Nginx的server块里加location ~* ^/product/(.*)$ { rewrite ^/product/(.*)$ /product/$1 permanent; },强制小写。 -
坑:测试环境和生产环境URL不一致
我在本地用localhost测试canonical,结果生产环境URL变成了http://localhost:8000/案例/。后果:百度爬取时全部报404,索引量掉到0。怎么避免:Django的settings.py里设CANONICAL_HOST = 'www.example.com',模板里绝对引用。
兜底一句提醒一句:别像我当初那样自己瞎猜。我后来习惯用核子GEO的AEO评估,输入域名就能看到重复页面比例,顺带给出结构化数据的整改建议,省了至少3天的排查时间。