W weiserv
← 返回博客

给个人 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"

全跑通后,Chrome 打开站点弹"已阻止第三方 Cookie / 网站无法正常运行?"。这不是网站报错,也不是百度统计没装好——它是 Chrome 的隐私保护特性,拦的是百度统计在 hm.baidu.com 域下的第三方 Cookie。本站自己的功能 Cookie(登录态、会话)都是第一方同站 Cookie,完全正常。

影响只是百度统计"跨会话回访识别/去重"精度下降,PV(页面浏览量)上报完全正常。无需任何代码改动,忽略即可;要消提示只能在你自己浏览器对该站"允许第三方 Cookie"(会降低隐私保护,不推荐)。

方法论总结(可复用)

  1. 改模板/代码/中间件,必须精确重启单实例,别依赖 reload——多实例下 reload 会漏掉真正在服务的进程。
  2. 第三方脚本三道关:CSP 放行(script-src/connect-src)+ Referrer-Policy 放宽 + 接受第三方 Cookie 提示是正常现象。
  3. 验证以线上真实响应为准(curl 真实返回),不要只信服务器端文件 grep。
  4. 运维命令用 pgrep -x <进程名>,避免 pgrep -f <特征串> 误杀执行命令的 shell 自身。
  5. 客观修正结论:曾以为"多实例=根因",后来发现 CSP 才是真正拦路虎,多实例只是叠加表象——发现问题要允许自己推翻 earlier 判断。

接入第三方分析工具看起来是"贴一行代码"的小事,但在有 CSP、Referrer-Policy、uWSGI 多实例、缓存层叠的个人站架构里,每一步都可能成为拦路虎。把验证建立在真实响应上、把根因追到响应头层面,才能少走弯路。

广告位占位 · post-inline