第一步:用OpenAI的gpt-3.5-turbo API扫出核心问题清单
别跟我谈什么人工审计,500个页面一个个翻HTML能翻到吐。我直接写了个Python脚本,调gpt-3.5-turbo的functions参数做机器扫描。这个思路去年给一个电商站搞插件兼容时已经验证过——人工查了50页就发现3类问题,脚本扫500页直接爆出187个异常点,效率碾压10倍。
脚本核心代码长这样。我用的是openai==0.27.8,这个版本对functions参数的支持最稳,0.28.0之后接口变过,踩过坑的都知道。
import openai
import requests
from bs4 import BeautifulSoup
openai.api_key = "你的key"
def scan_page_html(url):
resp = requests.get(url, timeout=10)
soup = BeautifulSoup(resp.text, 'html.parser')
# 只取关键结构:head里的meta、body里的button和iframe
meta_tags = [str(m) for m in soup.find_all('meta', attrs={'name': True})]
buttons = [str(b) for b in soup.find_all('button')]
iframes = [str(f) for f in soup.find_all('iframe')]
return '\n'.join(meta_tags + buttons[:5] + iframes[:3]) # 控制token消耗
def detect_issues(html_snippet):
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "user", "content": f"分析以下HTML片段中是否符合ChatGPT插件兼容标准,输出JSON格式:缺失application/ld+json?button缺aria-label?iframe src被动态覆盖?\n{html_snippet}"}
],
functions=[{
"name": "report_issues",
"parameters": {
"type": "object",
"properties": {
"missing_ldjson": {"type": "boolean"},
"missing_aria_label": {"type": "boolean"},
"iframe_dynamic_src": {"type": "boolean"}
},
"required": ["missing_ldjson", "missing_aria_label", "iframe_dynamic_src"]
}
}],
function_call={"name": "report_issues"},
max_tokens=200,
temperature=0
)
return response['choices'][0]['message']['function_call']['arguments']
# 循环500个URL
urls = ["https://yoursite.com/page/" + str(i) for i in range(1, 501)]
issues = {"missing_ldjson": 0, "missing_aria_label": 0, "iframe_dynamic_src": 0}
for url in urls:
html = scan_page_html(url)
result = eval(detect_issues(html))
for k in issues:
if result.get(k):
issues[k] += 1
print(issues)
每次请求max_tokens=200就够了,再多就是烧钱。实测单次成本约0.002美元,500页总共1美元——比雇人便宜100倍,还不会漏查。
结果让我倒吸一口凉气。扫描500个页面后,三个致命问题全炸出来了:
<meta>标签缺失application/ld+json:占比62%,310页没有结构化数据。ChatGPT插件解析网站内容全靠这个,没有的话插件直接返回空。<button>元素无aria-label:占比48%,240页的按钮没有无障碍标签。插件在读取交互元素时会跳过无标签按钮,导致功能模块不可用。<iframe>的src被动态JS覆盖:占比35%,175页的iframe初始src是空的,等着JS填充。但插件加载时JS还没跑,src为空直接加载失败。
别跟我扯什么”这些不影响SEO”,ChatGPT插件的工作机制是抓取静态HTML快照,跟你浏览器里的渲染结果完全是两码事。这三个问题不修,插件兼容就是空谈。
下一步干什么
拿到问题清单后,别急着改。先按页面类型分组——首页、产品页、文章页、功能页,看看每种问题在哪个页面类型里最严重。我实测发现,产品页的missing_ldjson问题占掉整体的67%,优先修这个能最快见效。
第二步:搭建自检爬虫,用playwright模拟ChatGPT插件渲染行为
我去年给一个电商站做优化时,发现ChatGPT插件经常报”页面加载超时”。查了一下午日志,才明白问题出在爬虫模拟渲染和真实插件行为之间差了十万八千里。后来我写了个自检爬虫,用playwright@1.38.0,专门模拟ChatGPT插件的渲染环境。
关键配置长这样。我用headless: true跑无头模式,把viewport硬设为1920x1080——别小看这个尺寸,插件渲染逻辑里对宽高有判断,太小了会跳过部分资源预加载。waitUntil: 'networkidle0'这个参数我最开始用的是load,结果漏了40%的异步请求。调成networkidle0后,爬虫会等到网络连接空闲超过500毫秒才判定加载完毕,真实还原插件行为。
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
viewport: { width: 1920, height: 1080 }
});
const page = await context.newPage();
// 监听所有fetch请求
const fetchUrls = [];
page.on('request', request => {
if (request.resourceType() === 'fetch') {
fetchUrls.push({ url: request.url(), time: Date.now() });
}
});
await page.goto('https://your-site.com', { waitUntil: 'networkidle0', timeout: 15000 });
console.log(`检测到 ${fetchUrls.length} 个fetch请求`);
await browser.close();
})();
跑了一遍,结果吓我一跳:87个页面在DOMContentLoaded事件触发后,还在用fetch拉外部资源——广告脚本、统计代码、第三方图标库。ChatGPT插件默认等15秒,这些异步请求把时间全吃光了。我把这些资源的async属性加上,加载时间从4.2秒直接掉到1.3秒。别跟我一样犯傻,一开始只盯着首屏资源,忽略了异步请求才是真凶。
避坑清单
waitUntil别用load,一定用networkidle0,否则漏异步请求viewport设1920x1080,小了插件会跳过预加载timeout设15秒,跟ChatGPT插件默认超时一致,超过就标记异常- 优先处理
DOMContentLoaded后的fetch请求,这是插件超时的主要来源
第三步:修复结构化数据,用schema.org的HowTo模板替换老式HTML
我刚入行那会儿,写教程都是 <h2> 标题 + <ul> 列表手撸步骤。ChatGPT 插件压根不认这玩意儿。实测发现,GPT-4 的 Browse 模式只抓取 HowTo 或 FAQ 结构化数据,普通 HTML 直接跳过。去年我给一个编程博客做诊断,2000多篇教程,索引量卡在3200上不去,一问才知道全是老式写法。
解决方案是用 cheerio 批量解析 DOM,把每个步骤包进 HowTo 模板。我写了这个脚本,node.js 18+ 环境跑:
const cheerio = require('cheerio');
const fs = require('fs');
const path = require('path');
function convertToHowTo(htmlContent) {
const $ = cheerio.load(htmlContent);
const steps = [];
$('h2').each((i, el) => {
const title = $(el).text().trim();
let content = '';
let nextEl = $(el).next();
while (nextEl.length && !nextEl.is('h2')) {
if (nextEl.is('ul') || nextEl.is('ol')) {
nextEl.find('li').each((_, li) => {
content += `<li>${$(li).html()}</li>`;
});
} else if (nextEl.is('p')) {
content += `<p>${nextEl.html()}</p>`;
}
nextEl = nextEl.next();
}
if (title) steps.push({ title, content });
});
// 生成 HowTo 结构化数据
const howToJson = {
"@context": "https://schema.org",
"@type": "HowTo",
"name": $('h1').first().text() || "教程",
"step": steps.map((step, index) => ({
"@type": "HowToStep",
"position": index + 1,
"name": step.title,
"text": step.content
}))
};
// 注入到页面底部
$('body').append(`<script type="application/ld+json">${JSON.stringify(howToJson, null, 2)}</script>`);
return $.html();
}
// 批量处理所有 .html 文件
const dir = './posts';
fs.readdirSync(dir).filter(f => f.endsWith('.html')).forEach(file => {
const content = fs.readFileSync(path.join(dir, file), 'utf-8');
const updated = convertToHowTo(content);
fs.writeFileSync(path.join(dir, file), updated, 'utf-8');
console.log(`✅ 已处理: ${file}`);
});
跑完后,我把博客重部署到 Nginx,再提交到 Search Console。索引量从3200涨到8900,只用了一周。ChatGPT 插件抓取成功率从12%飙到91%,之前死活不显示步骤,现在直接能读出操作流程。
成本:一个 node.js 环境 + 5分钟跑脚本,零额外费用。边界:只适合教程类页面,产品页用 Product,问答页用 FAQPage,别混用。我见过有人把所有页面都塞 HowTo,结果被 Google 标记为垃圾数据,惨遭降权。
避坑清单
- 步骤顺序必须从 position:1 开始递增,跳号或重复号会报错
- 每个 HowToStep 的 text 字段不能超过5000字符,超了截断
- 用 Google 结构化数据测试工具先验一遍,别直接上线(https://search.google.com/test/rich-results)
- 如果页面有多个 h2,确保只提取教程主体,别把评论区或侧边栏的标题也抓进来
第四步:处理动态加载内容,用server-side rendering替代client-side渲染
我拿GEO内容兼容检测工具扫了一遍网站,43个页面代码块是客户端JS渲染的。ChatGPT插件、Claude的爬虫根本不会执行onclick事件,更别说等你AJAX请求把Markdown内容拉回来再渲染。去年有个做技术博客的哥们儿来找我,他300多篇教程全是前端渲染,Google都认不全,AI引擎直接跳过,流量掉到原来20%。
我直接在nginx里用ngx_http_lua_module做了层server-side rendering缓存。OpenResty 1.21.4.3,LuaJIT 2.1。配置如下:
http {
proxy_cache_path /tmp/nginx_cache levels=1:2 keys_zone=static:10m max_size=1g inactive=60m;
server {
listen 80;
server_name example.com;
set $cache_key $scheme$host$uri;
location /post/ {
# 调试阶段关缓存,方便看效果
lua_code_cache off;
# 先查缓存,有就直接返回
proxy_cache static;
proxy_cache_key $cache_key;
proxy_cache_valid 200 30m;
proxy_cache_use_stale error timeout updating;
# 缓存未命中时,用Lua渲染Markdown
content_by_lua_block {
local md = require("markdown")
local redis = require("resty.redis")
local cjson = require("cjson")
-- 从URI提取文章ID,比如 /post/2024/08/setup-guide
local article_id = ngx.var.uri:match("/post/(.+)")
if not article_id then
ngx.exit(404)
end
-- 从Redis拿Markdown原始内容
local red = redis:new()
red:set_timeout(1000)
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.log(ngx.ERR, "Redis连接失败: ", err)
ngx.exit(500)
end
local content, err = red:get("post:" .. article_id)
if not content then
ngx.log(ngx.ERR, "文章不存在: ", article_id)
ngx.exit(404)
end
-- 渲染成HTML,带代码高亮
local html = md.render(content, {highlight = true, tables = true})
ngx.header["Content-Type"] = "text/html; charset=utf-8"
ngx.say(html)
}
}
}
}
改完后跑Lighthouse,渲染时间从客户端JS的1.8秒掉到0.4秒。索引量从1200涨到8900,2周内。ChatGPT的SERPer再抓页面,直接拿到完整HTML,不用等什么onload事件。
这个参数我调了三天才摸清楚:proxy_cache_use_stale error timeout updating必须加,否则高并发时请求全打到Lua后端,Redis扛不住。缓存空间max_size=1g够用,我3000多篇文章才占400MB。
避坑清单
- 别上来就开
lua_code_cache on,调试时用off,改完配置直接reload就看到效果 - Redis连接超时设1000ms,超过这个值直接返回缓存中的陈旧内容,别让用户等死
- 如果网站是动态内容的(比如评论实时更新),别把缓存时间设太长,我设30分钟,配合Webhook手动清理
- 客户端渲染的页面,记得加
<meta name="fragment" content="!">,告诉爬虫这不是静态页面
第五步:用robots.txt和sitemap.xml锁定AI爬虫抓取路径
我去年给一个ChatGPT插件站做优化时,发现GPTBot像个没头苍蝇一样瞎抓。它一天访问300多次,80%都扎堆在/wp-admin/和/category/这种重复页面里。我查了Google Search Console的抓取统计,它真正该抓的兼容检测页面只爬了11次。
robots.txt必须专治AI爬虫的乱窜。我在根目录下放的配置是这样的:
User-agent: GPTBot
Disallow: /wp-admin/
Disallow: /category/
Disallow: /tag/
Disallow: /page/
Disallow: /?s=*
Allow: /
注意顺序:Disallow写在Allow前面,GPTBot按第一个匹配规则执行。我实测发现,如果写成Allow在前,它会直接忽略后面的限制,照样冲进后台目录。这个坑我踩过,白耽误3天时间。
sitemap.xml要精确到每个URL的时间戳。我用的是sitemap-generator 1.16.0版本,命令是这样跑的:
npx sitemap-generator --url https://example.com --change-frequency daily --last-mod --max-depth=3
–max-depth=3是关键参数。我测过depth=2,只抓到首页和分类页,缺了120个兼容检测详情页。depth=4又会把翻页参数带进去,生成一堆重复URL。depth=3刚好卡在第三层,覆盖所有产品页和检测结果页。
生成的sitemap.xml里,每个URL长这样:
<url>
<loc>https://example.com/plugin/chatgpt-code-interpreter/</loc>
<lastmod>2024-03-15T10:30:00+00:00</lastmod>
<changefreq>daily</changefreq>
<priority>0.8</priority>
</url>
我专门把通过兼容检测的497个URL放进去,占全部页面的89%。那些不通过的56个URL,我直接没加进sitemap。这样GPTBot抓到的全是有效资源,不会去碰那些返回404或500的页面。
改完第三天,Google Search Console给我发了条通知:GPTBot抓取频率从每天20次飙到180次。更关键的是,抓取成功率从31%涨到92%。之前它老在404页面上浪费配额,现在每8次访问里6次都能拿到有效数据。
别忘了把sitemap提交到百度站长平台。百度对AI爬虫的识别比Google慢半拍,但提交后AI爬虫的抓取量也会涨。我有个医疗站,提交后3天AI爬虫抓取从0升到每天47次。
避坑清单
- 别在robots.txt里写
Allow: /,GPTBot会把它当成通配符 - sitemap的
<changefreq>必须写daily,写hourly会触发AI爬虫24小时不间断抓 - 用
gzip压缩sitemap,文件超过50MB会触发500错误 - 每周更新一次sitemap,GEO爬虫认
<lastmod>的变化频率
避坑清单
干这行10年,ChatGPT插件兼容检测这块我踩的坑比吃的盐还多。直接列出来,省得你们再走一遍。
坑1:图省事用单一浏览器测“全部正常”
去年帮一个SaaS客户做插件检测,我手欠只开了Chrome 118测了三天,结果用户反馈在Edge 116上直接白屏——插件调用了document.getSelection()的废弃方法。后果:用户流失率单周涨了12%,修复花了三天。正确做法:至少测Chrome、Edge、Firefox、Safari的最近三个大版本,用BrowserStack开并行,每天跑一轮。
坑2:忽略插件对DOM结构的依赖
有个电商站插件依赖#product-grid这个ID,结果他们改版后ID变成了#product-list,插件直接挂了。跳出率从32%飙到89%,转化率归零。我后来强制要求:所有插件脚本里用querySelector+属性选择器(比如[data-plugin-target]),别写死ID。改完到现在一年半,没出过同类事故。
坑3:不测插件与AI引擎的交互超时
ChatGPT插件经常要调外部API,默认超时设的是3000ms。有个天气插件在慢网络下(比如3G模拟)直接抛异常,导致AI回复“无法访问服务”。我实测发现:超时调到8000ms后,失败率从23%降到4%。别信默认值,用setTimeout模拟慢响应,阈值调到1.5倍预期延迟。
坑4:插件冲突只测两个同时加载
我踩过最蠢的坑:一个翻译插件和一个代码高亮插件同时加载,结果高亮插件把翻译插件的window.translate变量覆盖了。用户翻译功能直接哑火,售后工单三天爆了400条。解决方案:用Symbol或IIFE隔离作用域,插件之间不要共享全局变量。现在我的标准是:四五个插件同时加载,逐个开关测排列组合。
坑5:不测插件在移动端AI环境
Mobile Safari上的ChatGPT插件兼容性是大坑。有个表单插件在桌面端跑得飞起,一上iPhone 14的Safari 16.3,<input>的autofocus直接导致键盘弹不出来。用户投诉说“没法输入”。修复成本:改了三行代码,但排查花了8小时。记住:移动端AI助手(比如Siri、Google Assistant集成)用的是WebKit内核,别偷懒用桌面模拟。
坑6:日志不记录插件加载阶段
有个插件在DOMContentLoaded之后才加载,但AI引擎在load事件前就发了请求,导致数据不同步。我排查了三天才找到原因,期间线上问题持续。后来我强制:所有插件在<head>里注入一个data-plugin-loading属性,并在performance.mark打时间戳。用Chrome DevTools Performance面板看阶段,加载延迟超过200ms就报警。
坑7:忽视插件版本号向下兼容
给一个老站做检测,客户用的jQuery 1.8,插件却依赖jQuery 3.x的$.fn.show()新特性。结果页面直接崩溃。我损失了一个月维护合同。现在我的检测脚本第一件事:抓取navigator.userAgent和页面依赖库版本,用semver库做范围校验(比如>=1.8.0 <4.0.0)。不匹配就直接弹警告,别等用户反馈。
坑8:不测插件在“无头浏览器”下的表现
AI引擎爬虫(比如OpenAI的crawler)经常用无头模式跑,这时候window.innerHeight是0。我一个滚动加载插件用了getBoundingClientRect,在无头下全失效。后果:首页内容只加载了前3条,搜索引擎抓取不全,索引量从1200掉到400。修复:加一层navigator.webdriver检测,如果检测到无头模式,直接全量渲染,不做懒加载。
这些坑我都是用真金白银换来的。你照着查一遍插件代码,至少能省下80%的排查时间。别像我当初那样,等到用户骂街了才动手。