给个人 Django 站点接入百度统计:我踩的 5 个坑与根治方法
背景:一行 script 背后的五个坑
做个人技术站(Django + 纯服务端渲染 SSR)一段时间后,我想知道流量从哪来、哪些文章受欢迎,于是接入了百度统计(Baidu Tongji)——它免费、国内访问友好,对个人站很合适。官方文档说"把这段 script 贴到 </head> 前就行",我照做了。
结果:贴完代码后,百度后台「代码安装检查」反复报未安装代码 / referrer 被禁用,Chrome DevTools 里 hm.js 显示已屏蔽:csp。前后折腾了几轮才彻底跑通。本文把踩的坑和根因一次性讲清,避免你走同样的弯路。
坑一:uWSGI 多实例,reload 不生效
我最初以为是模板没刷新生效。生产用 uWSGI(非 systemd),部署时习惯 uwsgi --reload <pidfile>。但现场其实跑着多个 uWSGI 主进程(master)抢同一个 socket,而 pidfile 只记录了其中一个。reload 只重载了"登记在册"的那个实例,真正在对外服务的实例根本没 reload——所以磁盘上的 base.html 改了,线上返回的还是旧模板。
根治:改任何模板/代码/中间件后,按进程名精确 kill 全部旧实例再单实例启动,而不是直接 reload:
# 只用 pgrep -x(按进程名),切勿 pgrep -f uwsgi_new.ini
# 后者会把正在执行这条命令的 bash 自己也匹配进去并误杀,导致命令中断
pids=$(pgrep -x uwsgi)
for p in $pids; do kill -TERM $p; done
sleep 2
# 顽固进程补一刀 SIGKILL
remain=$(pgrep -x uwsgi) && [ -n "$remain" ] && kill -9 $remain
rm -f /home/jian/wwwroot/weiserv_new/weiserv.sock
# 单实例重启,--chown 修 socket 属组,否则 nginx 502
uwsgi --ini uwsgi_new.ini --chown jian:www-data --daemonize /tmp/uwsgi_new.log
坑二:CSP 把第三方脚本拦了("已屏蔽:csp")
模板确实生效、HTML 里也有 hm.js 了,但浏览器仍已屏蔽:csp。根因在 Content-Security-Policy(内容安全策略)响应头:它的 script-src 白名单只放了 Google 广告域名,根本没有 https://hm.baidu.com。浏览器严格执行 CSP,拒绝加载这个外部脚本。
根治:在 SecurityHeadersMiddleware(Django 中间件)里把百度统计域名加进白名单:
CSP = (
"default-src 'self'; "
"script-src 'self' 'unsafe-inline' "
"https://pagead2.googlesyndication.com "
"https://adservice.google.com https://hm.baidu.com; " # ← 加这行
"connect-src 'self' "
"https://googleads.g.doubleclick.net https://hm.baidu.com; " # ← 加这行(sendBeacon 上报)
"img-src 'self' data: https:; " # img-src 已 https: 通配,回传 gif 不受影响
# ... 其余略
)
改完同样要精确重启 uWSGI 才生效。这点很关键:CSP 是中间件动态加的响应头,不重启就不会更新。
坑三:Referrer-Policy 跨域剥 referrer("referrer被禁用")
脚本能加载了(Network 里 hm.js 变 200),百度后台却报referrer被禁用。原因是响应头 Referrer-Policy: same-origin:它规定只有同源请求才带 Referer,跨域请求一律剥离。百度统计的埋点从 https://www.weiserv.com/ 打到 https://hm.baidu.com/ 是跨域,浏览器把 referrer 整个吞掉,百度收不到来源页就报错。
根治:改成浏览器默认的 no-referrer-when-downgrade(跨域 https→https 带完整来源,仅降级到 http 时剥离,安全几乎无损):
response["Referrer-Policy"] = "no-referrer-when-downgrade"
顺带澄清一个常见误解:304 是 HTTP 缓存协商码(资源未变省流量),"已屏蔽(blocked)"是请求被策略拦、根本没发出——两者不是一回事。
坑四:验证方法论——以线上真实返回为准
最贵的教训在验证环节。我曾只 grep 服务器端磁盘上的 base.html 有统计代码,就判定"安装成功",结果线上用户拿到的页面并没有代码。真相是:多实例下对外服务的实例用了旧模板,磁盘和线上不一致。
根治:所有"是否生效"的判定,必须以线上真实 HTTP 响应为准,而不是服务器文件:
# 看线上返回的 HTML 是否含统计代码(命中数应 > 0)
curl -s https://www.weiserv.com/ | grep -c "hm.baidu.com"
# 看线上响应头 CSP / Referrer-Policy 是否更新
curl -sI https://www.weiserv.com/ | grep -i "content-security-policy\|referrer-policy"
坑五:浏览器"已阻止第三方 Cookie"是正常现象
全跑通后,Chrome 打开站点弹"已阻止第三方 Cookie / 网站无法正常运行?"。这不是网站报错,也不是百度统计没装好——它是 Chrome 的隐私保护特性,拦的是百度统计在 hm.baidu.com 域下的第三方 Cookie。本站自己的功能 Cookie(登录态、会话)都是第一方同站 Cookie,完全正常。
影响只是百度统计"跨会话回访识别/去重"精度下降,PV(页面浏览量)上报完全正常。无需任何代码改动,忽略即可;要消提示只能在你自己浏览器对该站"允许第三方 Cookie"(会降低隐私保护,不推荐)。
方法论总结(可复用)
- 改模板/代码/中间件,必须精确重启单实例,别依赖
reload——多实例下 reload 会漏掉真正在服务的进程。 - 第三方脚本三道关:CSP 放行(
script-src/connect-src)+ Referrer-Policy 放宽 + 接受第三方 Cookie 提示是正常现象。 - 验证以线上真实响应为准(curl 真实返回),不要只信服务器端文件 grep。
- 运维命令用
pgrep -x <进程名>,避免pgrep -f <特征串>误杀执行命令的 shell 自身。 - 客观修正结论:曾以为"多实例=根因",后来发现 CSP 才是真正拦路虎,多实例只是叠加表象——发现问题要允许自己推翻 earlier 判断。
接入第三方分析工具看起来是"贴一行代码"的小事,但在有 CSP、Referrer-Policy、uWSGI 多实例、缓存层叠的个人站架构里,每一步都可能成为拦路虎。把验证建立在真实响应上、把根因追到响应头层面,才能少走弯路。