InfraGuard

访客与搜索洞察

使用按站点保存的服务账号及其资源映射,直接从网站卡片读取 GA4 访客分析与 Search Console 搜索流量。

概述 —— 这个页面做什么

在 服务 ▸ Web 的每张卡片上,独立访客 数字旁边并排放着两个图标,各自打开一个弹窗。

图标 弹窗 数据来源
人形 访客分析 Google Analytics 4(Analytics Data API)
放大镜 搜索流量 Google Search Console(Search Analytics API)

卡片的 nginx_meta_ct 访问情报告诉你的是 你的服务器收到了什么,而这两个弹窗告诉你的是 访客从哪里来、是哪些搜索词把他们带来的 —— 这类信息你的服务器日志里根本没有。

Site Kit 在 WordPress 上做的事,归根结底就是 替你组装报表请求。在 Laravel 上你自己组装同样能得到一模一样的数字,所以我们 既不用插件,也不用 google/apiclient —— 整件工作无非是「用私钥给 JWT 签名,再换成访问令牌」,而服务器原生就能完成签名。为了两个 REST 端点背上一整棵庞大的依赖树并不划算。

何时使用

页面构成

两个弹窗都分为 概览 / 关键词 / 流量统计 三个标签页(关键词标签页只存在于搜索弹窗中),因此是切换而不是一路下拉。期间选择器提供 7 / 28 / 90 天 —— 与服务端白名单的集合完全相同。

访客分析(GA4)

渠道和来源不是一回事。 渠道是归组之后的答案(Organic Search),来源是具体的那一个(google、naver、github.com)。尤其对韩国的站点来说,是 naver 还是 google,意味着两段截然不同的获客故事。

搜索流量(Search Console)

平均排名是按展示次数加权的。 若取简单平均,排名第 1 的一次展示就能抵消排名第 40 的一万次展示,把数字美化得离谱。

配置步骤

1. 创建服务账号(Google Cloud)

  1. Google Cloud Console → 选择或新建一个项目 → IAM 和管理 ▸ 服务账号 → 创建服务账号。不需要任何项目级角色。
  2. 打开该账号 → 密钥 → 添加密钥 ▸ 创建新密钥 ▸ JSON → 下载文件。
  3. 在 API 和服务 ▸ 库 中启用 Google Analytics Data API 与 Google Search Console API。

2. 在 InfraGuard 中登记

进入 服务 ▸ Web ▸ 网站管理 ▸ 该站点的编辑标签页,在 Google 集成 之下:

字段 值 从哪里找到
GA4 媒体资源 ID 仅数字(例如 550688232) GA4 管理 ▸ 媒体资源详情,或网址 #/a<account>p<property>/ 中 p 之后 的部分
Search Console 资源 sc-domain:example.com 或 https://example.com/ 与 Search Console 资源选择器中显示的字符串完全一致
服务账号 JSON 粘贴下载文件的 全部内容 —

GA4 媒体资源 ID 不是账号 ID。 在 #/a405285491p550688232/ 中,a 之后的是账号,而 p 之后的才是媒体资源。Google 有意不区分「没有这个资源」和「没有权限」(资源是否存在本身就是信息),因此号码填错时看起来与权限问题一模一样。请先核对媒体资源 ID。

切勿从域名推断 Search Console 资源。 只有完成所有权验证的那个人才知道它是域名资源(sc-domain:)还是网址前缀(https://…/),而选错时返回的是 空结果而不是错误。

3. 把服务账号邀请到每个资源

JSON 只是一个身份;访问权限是 按资源 授予的。复制保存后显示的 client_email,然后:

资源 位置 角色
GA4 管理 ▸ 媒体资源 访问权限管理 ▸ + 查看者
Search Console 设置 ▸ 用户和权限 ▸ 添加用户 完全

在 GA4 中这是 媒体资源 的访问权限管理,而不是 账号 的访问权限管理(左边一栏是账号,中间一栏才是媒体资源)。Search Console 必须是 完全 而不是受限 —— Search Analytics API 会拒绝受限用户。服务账号没有收件箱,所以请取消勾选「通过电子邮件通知」。

4. 测试连接

管理弹窗中的 [连接测试] 会用已保存的凭据把两个 API 各调用一次(最近 7 天),并分别报告每一项的结果。

状态 含义
ok Google 有响应。零行也仍然是 ok —— 新站点和 GSC 的延迟都不是错误
fail Google 自己的措辞,外加需要邀请的 client_email
skip 未配置资源,因此跳过(不算失败)

完全没有凭据时它什么都不会调用(外发请求为零)。未保存的编辑不会被测试 —— 请先保存。

正确性规则 —— 这些数字为何这样算

以下都遵循 Site Kit 早已验证过的那些陷阱,每一条都有依据。

规则 原因
时间窗 截止到昨天 今天只是部分汇总,把它算进来会让右端永远塌陷
GA4 从 metricAggregations:['TOTAL'] 读取 totals[0] 把带 limit 的行加总,会让总览数字偏低
每次 GA4 请求都带 hostName 的 in-list 过滤 一个媒体资源常常横跨多个主机,没有它卡片就会显示整个资源的流量。我们会把 该服务拥有的每一个域名(主域名、附加域名、www)都传进去,以便与卡片的 vts 合计完全一致
排行使用 keepEmptyRows:false + notExpression{emptyFilter} GA4 的 (not set) 常常压过真实的城市与来源
时间序列使用 keepEmptyRows:true 零流量的那一天不能从坐标轴上消失
GSC 使用 dataState:'all' 并计入约 2 天延迟 否则最近几天会读成 0,看上去像是流量崩塌
域名资源用 page contains 限定范围 sc-domain: 覆盖所有子域名,兄弟站点的关键词会混进来

数据来源

对象 连接 / 位置 作用
infr_web_service.ga4_property_id · gsc_site_url default(000771) 按站点的资源映射
infr_web_service.google_cred_enc · google_cred_email default(000772) 按站点的服务账号(Crypt,只写)
Analytics Data API properties/{id}:runReport 外部 访客报表
Search Console searchAnalytics/query 外部 搜索流量报表

服务:Services\Google\{GoogleTokenService, GoogleReportService, SiteInsightService};端点为 Service\WebController@insight{Visitors,Search,Test}。

缓存:报表响应会被缓存,且 缓存键包含执行身份(client_email)。同样的 URL 与请求体,换一个账号发起就是另一次授权;共享缓存键会把某个站点的缓存响应交给另一个站点。

凭据为何按站点保存

单一的全局 .env 密钥在这里 行不通。 已登记的站点分属不同的所有者,因而也属于不同的 Google 账号,而一个服务账号只能读取它被邀请进去的那些资源 —— 所以只要登记了两家客户的站点,就必然至少有一方一直失败。正因如此才有按站点粘贴的字段。

凭据字段 只有你动过它才会被发送。表单永远不会取回私钥,所以若把「没送来」当成「清除」,那么只改一条备注也会把凭据抹掉。只有显式清空才是删除。

访问权限

目标 只能通过已登记站点的 id 指定 —— 媒体资源 ID 不能作为参数传入。如果调用方可以指定资源,我们服务账号的权限就会变成读取 别人的 GA4/Search Console 资源的通道(与 目录的实时测试 相同的防 SSRF 规矩)。期间同样在服务端被白名单限定为 7/28/90。

疑难排查 · FAQ

相关条目

순 방문자 옆의 방문자 · 검색 아이콘
순 방문자 옆의 방문자 · 검색 아이콘