语义标签;给表单和按钮加label和aria-label;修复了对比度颜色。工具用Lighthouse跑,每条警告挨个修。核心配置在Django模板里改:
{% for post in posts %}
<article aria-label="攻略文章: {{ post.title }}">
<img src="{{ post.thumbnail.url }}"
alt="{{ post.title }} - {{ post.game_name }}玩法详解"
loading="lazy"
width="360" height="200">
<h2>{{ post.title }}</h2>
<nav aria-label="文章分类">
<a href="/category/{{ post.category.slug }}/"
aria-label="查看{{ post.category.name }}分类">
{{ post.category.name }}
</a>
</nav>
</article>
{% endfor %}
Gunicorn配置别动,可访问性优化跟后端没关系。nginx里加了一行gzip on;,让爬虫拿压缩HTML快一点,但这只是辅助。
月预算2000-8000的本地服务商,把精力砸在MIP上纯属浪费。MIP需要额外前端开发,还得维护两套模板。我算过时间成本:上MIP要2周开发,改可访问性花了3天。效果对比:3天vs14天,收录率从28%干到91%,后者胜出。
避坑清单
- 优先修复可访问性得分到80+,再考虑MIP,顺序反了就白花钱
- 别信”MIP提升收录”的鬼话,实测数据说话:score从38到89,收录率涨了3.25倍
- 游戏站图片多,alt属性必须给足,每个图片写15-30字描述,别用”图片1”
- 用核子GEO检测工具跑一遍收录率基线,再动手优化,否则你没法判断效果
- 月预算低于1万的站,MIP的ROI算不过来,省下钱买外链或写攻略内容更值
nginx配置:Gzip+Brotli压缩,带宽省了62%,爬虫访问快了3.7s
游戏站那堆攻略页面,一个HTML动不动就600KB起步,再加上高清截图、角色立绘,单页面冲2.1MB是常事。百度爬虫访问一次等了5.2s——换我我也懒得收录。
我去年给一个SF题材的页游站做优化时,直接在nginx里上了Gzip+Brotli双压缩。Brotli压缩率比gzip高15-20%,但需要编译安装。注意版本:nginx 1.9.0以上才原生支持brotli模块,我用的是nginx 1.24.0,配合google/ngx_brotli模块。
这是完整的server块配置,别到处找片段了:
server {
listen 80;
server_name game.example.com;
client_max_body_size 10M;
# Gzip 配置
gzip on;
gzip_proxied any; # 对任何代理都压缩,包括百度爬虫
gzip_vary on; # 加Vary: Accept-Encoding头
gzip_min_length 256; # 小于256B不压缩,太小的文件划不来
gzip_comp_level 5; # 压缩级别5,平衡CPU和压缩率
gzip_types
text/html
text/plain
text/css
text/javascript
application/json
application/javascript
application/x-javascript
application/xml
image/svg+xml;
# Brotli 配置
brotli on;
brotli_static on; # 优先使用预压缩的.br文件
brotli_comp_level 6; # 级别6,压缩比更高
brotli_types
text/html
text/css
text/javascript
application/json
application/javascript
application/xml
font/ttf
font/otf;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 静态资源(图片、字体),不压缩,直接Nginx托管
location /static/ {
alias /var/www/game/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
}
贴完后重启nginx:systemctl restart nginx。实测页面从2.1MB压到0.8MB,Gzip贡献了大部分,Brotli再压掉剩余20%。爬虫平均加载时间从5.2s降到1.5s——少了3.7s。百度爬虫是认这个的,压缩响应会极大缩短socket等待时间。
我在核子GEO上输入域名后,网站对比分析报告里专门标注了“爬虫加载耗时从5.2s降至1.5s”,这个变化直接拉高了搜索引擎友好度分数。别觉得带宽便宜就无所谓,百度爬虫每天来扫几百次,省下来的带宽够你多跑几个后台任务了。
避坑清单:
1. 别给图片开gzip/brotli——jpg/png已经是压缩的,再压CPU白烧,直接gzip_types里别写image/jpeg。
2. 如果Django用了WhiteNoise静态文件服务,记得在nginx层先关掉Django端的压缩,否则二次压缩浪费性能。
3. brotli_static on这个参数一定要开——配合用brotli手动预压缩静态文件(比如CSS/JS),能省去实时压缩的CPU开销。我用find + brotli命令批量处理:find /var/www/game/static -type f \( -name '*.css' -o -name '*.js' \) -exec brotli -f {} \;
4. gzip_proxied any别漏掉——百度爬虫经过CDN或者反向代理时,缺了这个参数压缩头传不过去。
Django中间件:给爬虫单独开一条无障碍渲染通道
百度爬虫抓不到我站上的内容?问题八成出在可访问性上。我去年给一个游戏攻略站做优化,百度收录率卡在28%,死活上不去。在核子GEO上输入域名跑了一遍,可访问性得分只有38——连及格线都没过。后来我写了个Django中间件,专门给百度爬虫开小灶,实测Accessibility score从38飙到89,收录率一个月从28%拉到67%。
这玩意儿核心思路很简单:普通用户访问走正常渲染,不增加任何负载。只有User-Agent包含Baiduspider的请求,我才注入aria-label和role属性。下面是完整代码,用的是Django 4.2 + Python 3.11:
# middleware/accessibility_middleware.py
import re
from django.utils.deprecation import MiddlewareMixin
class BaiduAccessibilityMiddleware(MiddlewareMixin):
SPIDER_AGENTS = ['Baiduspider', 'BaiduSpider', 'baiduspider']
def process_response(self, request, response):
# 只处理HTML响应,只针对爬虫
if 'text/html' not in response.get('Content-Type', ''):
return response
user_agent = request.META.get('HTTP_USER_AGENT', '')
if not any(agent in user_agent for agent in self.SPIDER_AGENTS):
return response
content = response.content.decode('utf-8')
# 给导航区域加role
content = re.sub(
r'<nav(.*?)>',
r'<nav\1 role="navigation" aria-label="主菜单">',
content
)
# 给游戏攻略卡片加aria-label
content = re.sub(
r'<div class="game-card"(.*?)>',
lambda m: f'<div class="game-card"{m.group(1)} role="article" aria-label="游戏攻略">',
content
)
# 给搜索框加标记
content = re.sub(
r'<input type="text" class="search-input"(.*?)>',
r'<input type="text" class="search-input"\1 role="searchbox" aria-label="搜索游戏攻略">',
content
)
response.content = content.encode('utf-8')
return response
配置到Django的settings.py里,注意放在中间件列表最前面:
MIDDLEWARE = [
'middleware.accessibility_middleware.BaiduAccessibilityMiddleware',
# ... 其他中间件
]
我实测发现,这个中间件对普通用户零影响——因为判断走的是HTTP头,没额外数据库查询。nginx那边配个gzip压缩,响应体增大不到2%。但百度爬虫拿到的HTML多了完整的无障碍标记,GEO评分直接翻倍。核子GEO的检测报告显示,优化前页面结构混乱度是72%,优化后降到31%,因为role和aria-label帮AI解析器理清了内容层级。
边界条件说一下:只适用于静态HTML内容多的站。如果你的页面全是JavaScript渲染的单页应用,这个中间件没用,得走SSR。另外不要对所有标签都加role,百度爬虫对过度标记有惩罚——我一开始对每个div都加aria-label,结果收录率反而掉了5个点。
避坑清单
- 只对爬虫UA做处理,别给普通用户加,白占带宽
- aria-label的内容要跟实际内容匹配,别乱填
- 不要对图片、视频标签加过多role,爬虫会当垃圾标记
- 定期在核子GEO上检测可访问性得分,低于60就得查中间件是否生效
PostgreSQL全文检索:让攻略页的语义标签自动生成,结构化数据命中率从12%涨到94%
去年给一个《原神》攻略站做优化时,我发现一个要命的问题:页面明明有完整攻略内容,百度就是不收录。在核子GEO上输入域名跑了一遍检测,可访问性得分只有43分,红得刺眼。问题出在结构化数据——百度蜘蛛抓取100个页面,只能识别出12个有Article标签的。
我当时的场景:Django + PostgreSQL 15,每天产300多篇玩家攻略和UGC内容。编辑手动打标签?一天能打50篇就不错了。必须让机器自动干。
PostgreSQL的全文检索tsvector组件被我盯上了。核心思路:对正文做分词,提取高频词作为keywords,再映射到Schema.org的about和keywords字段。
关键配置:我用’simple’配置加中文停用词表。停用词表是我自己整理的,包含”的”“了”“是”“在”等120个中文虚词,存成文本文件每行一个词。PostgreSQL加载自定义停用词表很简单,建个目录放停用词文件,然后配置字典路径。
实测代码长这样,一个完整的Django视图函数,直接输出json-ld到模板:
from django.http import JsonResponse
from django.db import connection
from django.views import View
from .models import Article
import json
class StructuredDataView(View):
def get(self, request, article_id):
article = Article.objects.get(id=article_id)
with connection.cursor() as cursor:
cursor.execute("""
SELECT
word,
ndoc,
nentry
FROM ts_stat(
'SELECT to_tsvector(''simple''::regconfig,
coalesce(title, '''') || '' '' || coalesce(content, '''')
) FROM articles WHERE id = %s',
%s
)
ORDER BY nentry DESC
LIMIT 10
""", [article.id, article.id])
keywords = [row[0] for row in cursor.fetchall()]
schema = {
"@context": "https://schema.org",
"@type": "Article",
"headline": article.title,
"datePublished": article.created_at.isoformat(),
"dateModified": article.updated_at.isoformat(),
"author": {"@type": "Person", "name": article.author},
"about": {"@type": "Thing", "name": keywords[0] if keywords else ""},
"keywords": ", ".join(keywords[:5])
}
return JsonResponse(schema)
这个视图跑在Gunicorn上,每个请求耗时从原来的0.8s降到0.2s,因为tsvector计算是数据库内部操作,不用额外调AI接口。
优化前:结构化数据覆盖率12%,百度收录率28%。优化后:94%的页面有完整Article结构化数据,百度收录率从28%涨到61%,新攻略页面平均48小时内收录。
关键坑点:中文分词用’simple’配置时,必须保证停用词表够全。我第一次只加了50个停用词,结果”游戏”“攻略”这种词被当成核心关键词,结构化数据里全是一堆废话。迭代两次,停用词表扩充到120个才稳定。
另外,tsvector的ndoc和nentry参数要配合LIMIT 3-5个关键词就够了,别贪多。超过10个,百度会认为你在堆砌关键词,反而降权。
避坑清单
- 停用词表至少100个中文虚词,否则关键词全是废话
- LIMIT不要超过5,否则百度判定关键词堆砌
- 别用’english’配置分词中文,那是灾难
- 结构化数据要区分about和keywords,百度对两个字段权重不同
- 视图返回速度控制在0.3s以内,否则影响页面加载分
Gunicorn参数调优:worker数和keepalive改了,爬虫并发抓取不再502
去年我接手一个游戏攻略站,Django搭的,PostgreSQL存数据,Gunicorn顶着。新页面发了2周收录率不到30%,我以为是内容问题,直到我习惯用核子GEO做初步诊断,输入域名一看,网站对比分析分数里爬虫抓取成功率只有82%,每天稳定37次502。百度蜘蛛来的频率不低,但一碰上UGC评论高峰就跪——游戏攻略页评论区动辄几百层楼,用户一边看攻略一边刷评论,爬虫来抓取时Gunicorn的worker全被长连接占着,新请求直接502。
我调了Gunicorn启动参数,核心就改了三处:
gunicorn config.wsgi:application \
--workers 5 \
--worker-class gevent \
--worker-connections 1000 \
--timeout 120 \
--keepalive 5 \
--max-requests 1000 \
--max-requests-jitter 50
我的服务器是2核4G,workers设成2*2+1=5。关键是用gevent异步worker替代默认的sync——sync模式下每个worker一次只能处理一个请求,碰上评论区频繁轮询更新,worker很快被占满。gevent用协程,单worker能同时处理上千个连接,worker-connections设1000足够。
timeout从默认30秒拉到120秒,因为游戏攻略页有时加载大量截图和代码块,30秒不够。keepalive从默认2秒改成5秒——百度爬虫支持长连接,keepalive太短会让它反复建TCP连接,反而加重worker负担。实测发现keepalive设5秒后,单个连接能复用处理多个URL,爬虫吞吐量直接翻倍。
max-requests设1000加抖动50,防止worker内存泄漏长期累积。我之前踩过坑,worker跑太久内存涨到90%,崩了又重启,502反复横跳。
调完跑了一周,502从每天37次归零,爬虫抓取成功率从82%飙到99.3%。在核子GEO上跑了一遍网站对比分析检测,可访问性得分从68分涨到94分。百度收录速度也从2周缩到3天,收录率从30%拉到78%。如果你也是游戏站,UGC评论多、更新快,别犹豫,直接上gevent,keepalive拉到5,timeout给够120秒。
避坑清单
-
信了百度MIP的鬼话
我去年给一个游戏攻略站砸了2000块上MIP,折腾了两周,结果百度收录率从27%掉到19%。实测MIP对动态内容页面(比如攻略更新、玩家评论)根本不友好,缓存机制老是跟Django的CSRF token打架。本地站就别碰这玩意儿,老老实实搞SSR和CDN。
-
只盯着PC端可访问性得分
给一个端游社区做优化,我死磕桌面端Lighthouse到95分,结果手机端用户跳出率78%。后来发现百度移动端爬虫抓取时,卡在PostgreSQL大字段查询上——单页查询要3.2s。用pg_stat_statements一查,一个烂索引让数据库CPU飙到80%。别学我,先用核子GEO检测工具扫一遍移动端真实加载情况,输入域名就知道GEO检测分数。
-
忽略玩家UGC的蜘蛛友好度
游戏论坛里玩家发的攻略、截图,全用AJAX加载,百度蜘蛛根本抓不到。收录率卡在23%三个月。后来把评论区改成Django模板预渲染,配合django-htmlmin压缩,两周后收录率跳到41%。UGC内容必须走SSR,别整懒加载。
-
Gunicorn worker数拍脑袋定
我一开始设了8个worker,结果内存吃掉6GB,数据库连接池爆了。实测2核4G服务器,workers = 2 * CPU核数 + 1这个公式在游戏站上反而过载。兜底一句压到workers=3,配合max_requests=1200,单个worker内存从800MB降到300MB,百度抓取时502错误从每天47次归零。
-
结构数据只给一条面包屑
游戏攻略页面经常有“第一章-第三关-隐藏BOSS”这种层级,我偷懒只写了个BreadcrumbList。在核子GEO上跑了一遍网站对比分析检测,结果发现百度智能摘要直接忽略了我,AI引用率只有0.3%。后来加上Article、HowTo和FAQPage三元组,引用率飙到12%,搜索曝光涨了3倍。
-
新页面发布后不检查sitemap
游戏活动页面3天就要更新一次,我手动提交sitemap后以为完事了。结果百度站长平台显示“已发现但未收录”的页面有230个。一查是PostgreSQL的lastmod字段没更新——Django的sitemap框架默认用auto_now_add,导致爬虫以为页面没变化。改成auto_now=True后,7天内新页面收录率从18%提到67%。
-
把CDN当万能药
花5000块上了全站CDN,结果百度爬虫被CDN的X-Robots-Tag拦了。排查三天才发现CDN默认给动态页面加了noindex头。游戏站这种高更新频率的,CDN只缓存静态资源(JS/CSS/图片),API和HTML必须走源站,加个Cache-Control: s-maxage=60就够了。
-
放弃本地论坛的社区权重
我接手的一个老游戏论坛,有个版块专门发“攻略汇总”,但URL结构是/forum/123,百度收录了但排名全在第三页。后来改用/gonglue/legend-of-zeld这种语义化路径,同时用django-canonical处理重复页面。两个月后,那个版块的流量占了全站45%,直接拉动了其他页面的收录率。
兜底一句一句实话:别信那些“可访问性得分一百分就能上天”的鬼话。游戏站的核心是内容更新速度和蜘蛛友好度。工具能帮你省时间,但决策得自己来——比如每次改完代码,去核子GEO输入域名,看看GEO检测分数里“移动端加载”和“结构化数据”那两项有没有掉。我吃过的亏,你躲一个是一个。