文章目录
前言:WordPress 的天然优势
WordPress 的最大优势是它本身就知道每个页面是什么类型——文章、页面、分类页、标签页、首页、搜索结果……这些类型信息可以直接从 PHP 模板层获取,不需要用脆弱的 URL 正则去猜。
本文会提供两套方案:
- 方案 A(推荐):从 WordPress 模板层注入页面类型,数据更准确,维护成本更低
- 方案 B(备选):纯 GTM 前端规则,不改动主题代码,适合无法编辑主题的场景
两套方案最终都指向同一个目标:在 page_view 事件中发送 content_group 参数,然后在 GA4 界面完成维度注册。
第一部分:URL 匹配逻辑(开发篇)
方案 A:WordPress 模板层注入(强烈推荐)
A-1:理解 WordPress 条件标签
WordPress 提供了一套条件标签(Conditional Tags),可以精确判断当前页面类型,比任何 URL 正则都可靠:
is_front_page() // 首页(无论是静态首页还是文章列表)
is_home() // 博客文章列表页(「阅读设置」中配置的文章页)
is_single() // 单篇文章(post type = post)
is_page() // 静态页面(post type = page)
is_singular('product') // WooCommerce 产品详情页
is_post_type_archive('product') // WooCommerce 产品列表页
is_category() // 文章分类归档页
is_tag() // 标签归档页
is_tax() // 自定义分类法归档页
is_search() // 搜索结果页
is_404() // 404 页面
is_cart() // WooCommerce 购物车(wc_is_checkout() 的变体)
is_checkout() // WooCommerce 结账页
is_account_page() // WooCommerce 账户页
is_order_received_page() // WooCommerce 订单确认页
A-2:在 WordPress 中输出分组数据到 dataLayer
操作方式:编辑子主题的 functions.php,或者使用代码片段插件(如 WPCode、Code Snippets)。
强烈建议使用子主题或代码片段插件,避免主题更新时代码被覆盖。
<?php
/**
* GA4 内容分组 - 将页面类型推送到 dataLayer
* 添加到子主题 functions.php 或代码片段插件
*/
function push_content_group_to_datalayer() {
// 判断内容分组
$content_group = ga4_get_content_group();
// 同时获取二级分组(可选,适合内容体系复杂的网站)
$content_group2 = ga4_get_content_group2();
// 输出到页面 <head> 内的 dataLayer
?>
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'contentGroup': '<?php echo esc_js($content_group); ?>',
'contentGroup2': '<?php echo esc_js($content_group2); ?>'
});
</script>
<?php
}
// 在 <head> 最顶部、GTM/gtag 代码之前输出
add_action('wp_head', 'push_content_group_to_datalayer', 1);
/**
* 一级内容分组:页面类型
*/
function ga4_get_content_group() {
// ===== WooCommerce 页面(优先判断,避免被通用规则覆盖)=====
if (function_exists('is_woocommerce')) {
if (is_order_received_page()) return '订单确认页';
if (is_checkout()) return '结账页';
if (is_cart()) return '购物车';
if (is_account_page()) return '账户中心';
if (is_singular('product')) return '产品详情页';
if (is_post_type_archive('product') || is_product_category() || is_product_tag()) {
return '产品列表页';
}
}
// ===== 首页 =====
if (is_front_page()) return '首页';
// ===== 博客 =====
if (is_single()) return '博客文章页';
if (is_home()) return '博客列表页';
if (is_category()) return '分类归档页';
if (is_tag()) return '标签归档页';
// ===== 静态页面(根据 slug 细分)=====
if (is_page()) {
$slug = get_post_field('post_name', get_the_ID());
// 常见落地页 slug,根据你的网站实际情况修改
$landing_pages = ['about', 'about-us', 'about-me', 'about-company'];
$contact_pages = ['contact', 'contact-us', 'get-in-touch'];
$service_pages = ['services', 'service', 'solutions', 'offerings'];
if (in_array($slug, $landing_pages)) return '关于我们页';
if (in_array($slug, $contact_pages)) return '联系我们页';
if (in_array($slug, $service_pages)) return '服务介绍页';
// 其他静态页面统一归类
return '静态页面';
}
// ===== 特殊页面 =====
if (is_search()) return '搜索结果页';
if (is_404()) return '404页面';
if (is_tax()) return '自定义分类页';
if (is_author()) return '作者归档页';
if (is_date()) return '日期归档页';
return '其他';
}
/**
* 二级内容分组:内容品类(适合博客文章和产品)
*/
function ga4_get_content_group2() {
// 博客文章:取第一个分类名称
if (is_single()) {
$categories = get_the_category();
if (!empty($categories)) {
return $categories[0]->name;
}
}
// WooCommerce 产品:取第一个产品分类
if (function_exists('is_woocommerce') && is_singular('product')) {
$terms = get_the_terms(get_the_ID(), 'product_cat');
if (!empty($terms) && !is_wp_error($terms)) {
// 过滤掉顶级分类「Uncategorized」
foreach ($terms as $term) {
if ($term->name !== 'Uncategorized' && $term->slug !== 'uncategorized') {
return $term->name;
}
}
}
}
return '';
}
为什么要用 esc_js()?
esc_js() 是 WordPress 内置的安全函数,会转义单引号、双引号、换行符等特殊字符,防止分组名称中含有这些字符时破坏 JavaScript 语法,同时避免 XSS 注入风险。这在内容分组值可能来自用户输入(如分类名称)时尤为重要。
A-3:在 GTM 中读取 dataLayer 变量
完成 PHP 代码后,dataLayer 中已经有了数据,接下来在 GTM 里创建变量来读取它。
创建 GTM 变量:contentGroup
- GTM 工作区 → 左侧「变量」→「新建」
- 变量类型选择:数据层变量(Data Layer Variable)
- 配置如下:
变量名称:DL - contentGroup
数据层变量名称:contentGroup
数据层版本:版本 2
默认值:其他
- 保存
同理,创建第二个变量用于二级分组:
变量名称:DL - contentGroup2
数据层变量名称:contentGroup2
数据层版本:版本 2
默认值:(留空)
方案 B:GTM 纯前端规则(不改主题代码)
如果你没有权限修改主题,或者不想改动 PHP,可以完全在 GTM 内用「自定义 JavaScript」变量实现分组判断。
代价是:规则依赖 URL 结构,WordPress 固定链接格式改变时需要同步更新,且无法区分同 URL 前缀下的不同页面类型。
B-1:WordPress 常见固定链接结构
在写规则之前,先确认你的 WordPress 固定链接格式(设置 → 固定链接):
| 固定链接格式 | 文章 URL 示例 |
|---|---|
| 文章名(最常见) | /how-to-use-ga4/ |
| 分类/文章名 | /tutorial/how-to-use-ga4/ |
| 日期/文章名 | /2026/04/09/how-to-use-ga4/ |
| 数字型 | /?p=123 |
WooCommerce 默认 URL 结构:
| 页面类型 | 默认 URL |
|---|---|
| 商店首页 | /shop/ |
| 产品详情 | /product/产品名/ |
| 产品分类 | /product-category/分类名/ |
| 购物车 | /cart/ |
| 结账 | /checkout/ |
| 订单确认 | /checkout/order-received/ |
| 账户 | /my-account/ |
B-2:GTM 自定义 JavaScript 变量
GTM 工作区 → 变量 → 新建 → 变量类型:自定义 JavaScript
function() {
var path = window.location.pathname;
// ===== WooCommerce 页面(越具体的规则越靠前)=====
if (/\/checkout\/order-received/.test(path)) return '订单确认页';
if (/\/checkout/.test(path)) return '结账页';
if (/\/cart/.test(path)) return '购物车';
if (/\/my-account/.test(path)) return '账户中心';
// 产品详情页:/product/ 后跟产品 slug
if (/^\/product\/[^/]+\/?$/.test(path)) return '产品详情页';
// 产品列表:/shop/ 或 /product-category/
if (/^\/(shop|product-category)/.test(path)) return '产品列表页';
// ===== 首页 =====
if (path === '/' || path === '/index.php') return '首页';
// ===== 博客(取决于你的固定链接格式)=====
// 如果博客文章在 /blog/ 子目录下
if (/^\/blog\/[^/]+\/?$/.test(path)) return '博客文章页';
if (/^\/blog\/?$/.test(path)) return '博客列表页';
// 如果博客文章直接在根目录(需要区分静态页面和文章,较难,建议用方案A)
// 分类页:/category/
if (/^\/category\//.test(path)) return '分类归档页';
// 标签页:/tag/
if (/^\/tag\//.test(path)) return '标签归档页';
// ===== 静态页面(按 slug 列举重要页面)=====
if (/^\/(about|about-us)\/?$/.test(path)) return '关于我们页';
if (/^\/(contact|contact-us)\/?$/.test(path))return '联系我们页';
if (/^\/services\/?$/.test(path)) return '服务介绍页';
// ===== 特殊页面 =====
if (/\/\?s=/.test(window.location.search)) return '搜索结果页';
return '其他';
}
注意:搜索结果页的判断要用
window.location.search(查询字符串),而不是pathname,因为 WordPress 搜索 URL 是/?s=关键词。
第二部分:发送 content_group 参数
通过 GTM 发送(推荐)
无论你用的是方案 A 还是方案 B,GTM 的后续配置步骤是一样的。
步骤 1:创建 GA4 事件代码
GTM → 代码 → 新建
基础配置:
代码名称:GA4 - page_view(含内容分组)
代码类型:Google Analytics: GA4 事件
详细配置:
| 字段 | 填写内容 |
|---|---|
| 测量 ID | G-XXXXXXXXXX(你的 GA4 媒体资源 ID) |
| 事件名称 | page_view |
事件参数(点击「添加行」逐一添加):
| 参数名称 | 值 |
|---|---|
content_group |
{{DL - contentGroup}} |
content_group2 |
{{DL - contentGroup2}} |
page_title |
{{Page Title}} |
page_location |
{{Page URL}} |
注意:
{{Page Title}}和{{Page URL}}是 GTM 内置变量,需要在「变量」→「配置」中确认它们已启用。
触发器配置:
- 触发器类型:网页浏览 → 所有网页浏览
- 触发器名称:所有页面浏览
步骤 2:检查是否已有 GA4 配置代码
如果你的网站已经通过其他方式(如 Google Site Kit 插件、主题内置集成、另一个 GTM 代码)发送 GA4 页面浏览事件,直接添加上面的代码会导致重复计算页面浏览。
处理方式:
情况一:已有 GA4 配置代码(GA4 Configuration tag)
在已有代码的「字段」选项卡中,直接添加 content_group 字段,不要新建事件代码。
情况二:使用 Google Site Kit 插件自动安装 GA4 Site Kit 直接在网页 HTML 中注入 gtag,不经过 GTM。此时有两个选择:
- 在 Site Kit 设置中禁用其 GA4 代码,改为完全由 GTM 管理(推荐)
- 或者用下方的「直接在主题中修改 gtag」方案
直接在 WordPress 主题中修改 gtag(不用 GTM 的情况)
如果你通过手动粘贴 gtag 代码或插件(如 Site Kit)安装了 GA4,可以在主题中修改 gtag 调用。
找到现有 gtag 安装位置
常见安装方式及对应修改位置:
| 安装方式 | 修改位置 |
|---|---|
手动粘贴到 header.php |
直接编辑 header.php |
手动粘贴到 functions.php |
在 wp_head 钩子中找到对应函数 |
| Google Site Kit 插件 | 用 googlesitekit_gtag_opt 过滤器注入参数 |
| MonsterInsights 插件 | 使用其提供的自定义维度接口 |
在 functions.php 中修改 gtag 调用
如果你是手动在 functions.php 里注入 gtag 代码,将 page_view 事件修改如下:
<?php
function add_ga4_with_content_group() {
$measurement_id = 'G-XXXXXXXXXX'; // 替换为你的测量 ID
$content_group = ga4_get_content_group(); // 复用上文定义的函数
$content_group2 = ga4_get_content_group2();
?>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=<?php echo esc_attr($measurement_id); ?>"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
// 配置 GA4,发送 page_view 时携带内容分组
gtag('config', '<?php echo esc_js($measurement_id); ?>', {
'content_group': '<?php echo esc_js($content_group); ?>',
'content_group2': '<?php echo esc_js($content_group2); ?>'
});
</script>
<?php
}
add_action('wp_head', 'add_ga4_with_content_group');
注意:
gtag('config', ...)默认会自动发送一次page_view事件,其中会携带你传入的所有参数,包括content_group。不需要额外调用gtag('event', 'page_view', ...),否则会重复发送。
使用 Google Site Kit 插件时注入参数
Site Kit 提供了 googlesitekit_gtag_opt 过滤器,可以在不修改插件代码的情况下注入额外的 gtag 配置参数:
<?php
/**
* 通过 Site Kit 的过滤器注入内容分组参数
*/
add_filter('googlesitekit_gtag_opt', function($gtag_opt) {
$gtag_opt['content_group'] = ga4_get_content_group();
$gtag_opt['content_group2'] = ga4_get_content_group2();
return $gtag_opt;
});
将这段代码加到子主题 functions.php 或代码片段插件中即可。这是使用 Site Kit 时最干净的实现方式,无需禁用 Site Kit 的自动代码注入。
第三部分:GA4 界面注册自定义维度
数据发出去之后,需要在 GA4 管理界面「告诉」GA4 这个参数的含义,它才会出现在报告维度列表中。
3-1:进入自定义定义界面
完整路径:
GA4 管理界面(齿轮图标)
└── 媒体资源列(中间列,确认是你的网站媒体资源)
└── 数据显示
└── 自定义定义
└── 自定义维度 标签页
点击右上角蓝色按钮「创建自定义维度」。
3-2:注册 content_group 维度
弹出的配置面板按如下填写:
| 字段 | 填写内容 | 说明 |
|---|---|---|
| 维度名称 | 内容分组 |
报告中显示的名称,填中文完全没问题 |
| 范围 | 事件 |
content_group 是随 page_view 事件发送的,选「事件」 |
| 说明(可选) | 页面内容类型分组,用于聚合分析 |
给团队其他成员看的备注 |
| 事件参数 | content_group |
⚠️ 必须与代码中的参数名完全一致,区分大小写 |
填写完成后点击「保存」。
3-3:注册 content_group2 维度(如果有)
重复上述步骤,创建第二个维度:
| 字段 | 填写内容 |
|---|---|
| 维度名称 | 内容品类 |
| 范围 | 事件 |
| 事件参数 | content_group2 |
3-4:理解「范围」的含义
GA4 自定义维度有两种范围,选错了数据会出问题:
| 范围 | 适用场景 | 示例 |
|---|---|---|
| 事件 | 参数随每次事件发送,不同事件可以有不同值 | content_group(每次 page_view 都可能不同) |
| 用户 | 参数描述用户属性,整个用户生命周期内相对稳定 | user_type(会员/访客)、subscription_plan |
content_group 必须选事件范围——因为同一个用户在一次会话中会访问不同类型的页面,每次 page_view 的分组值都不同。
3-5:等待数据生效
注册完成后,维度不会立即在所有报告中出现。时间线如下:
| 操作 | 生效时间 |
|---|---|
| DebugView 中可见 | 立即(实时) |
| 「实时」报告中可见 | 几分钟内 |
| 「探索」报告中可见 | 通常 24 小时内 |
| 标准报告中可见 | 24–48 小时 |
| 历史数据回填 | 不支持,仅对注册后的数据生效 |
第四部分:验证数据是否正确发送
方法 1:GA4 DebugView(最直观)
前提:安装 Chrome 插件 Google Analytics Debugger,开启插件后刷新页面。
操作路径:
GA4 管理界面
└── 管理(齿轮)
└── 媒体资源
└── DebugView
你会看到一个实时事件流。点击左侧的 page_view 事件,在右侧展开参数列表,确认:
✅ content_group = 产品详情页
✅ content_group2 = 运动鞋
✅ page_location = https://yoursite.com/product/nike-air-max/
✅ page_title = Nike Air Max 2024 - Your Store
逐页验证清单:
| 页面类型 | 测试 URL 示例 | 期望的 content_group 值 |
|---|---|---|
| 首页 | yoursite.com/ |
首页 |
| 博客文章 | yoursite.com/blog/post-title/ |
博客文章页 |
| 博客列表 | yoursite.com/blog/ |
博客列表页 |
| 产品详情 | yoursite.com/product/item/ |
产品详情页 |
| 产品列表 | yoursite.com/shop/ |
产品列表页 |
| 购物车 | yoursite.com/cart/ |
购物车 |
| 结账 | yoursite.com/checkout/ |
结账页 |
| 联系 | yoursite.com/contact/ |
联系我们页 |
| 搜索 | yoursite.com/?s=test |
搜索结果页 |
| 不存在的页面 | yoursite.com/aabbcc/ |
404页面 |
方法 2:GTM 预览模式
在 GTM 中点击「预览」,输入你的网站 URL,按回车。
Tag Assistant 会在浏览器底部打开一个调试面板。
- 访问任意页面
- 在左侧「Tags Fired」列表中找到你配置的 GA4 代码
- 点击该代码 → 查看「Variables」标签页
- 确认
DL - contentGroup变量的值符合预期
方法 3:浏览器控制台快速验证
如果你用的是方案 A(dataLayer 注入),可以在浏览器控制台运行:
// 查看 dataLayer 中的内容分组数据
window.dataLayer.filter(e => e.contentGroup)
// 预期输出示例:
// [{ contentGroup: "产品详情页", contentGroup2: "运动鞋" }]
第五部分:在 GA4 探索报告中使用内容分组
数据准确后,就可以在报告中发挥它的价值了。
创建内容分组分析报告
路径:GA4 → 探索 → 新建探索 → 自由格式
配置步骤:
- 在左侧「维度」区域,点击「+」
- 搜索「内容分组」,勾选后点击「导入」
- 将「内容分组」拖入「行」区域
- 在「指标」区域添加:会话数、参与会话数、参与率、关键事件(转化)
- 如需对比,可将「内容分组」同时拖入「列」,用「日期范围」对比
推荐的分析视角:
-- 用 SQL 思维理解你要做的分析
-- Q1:各内容组的参与质量排名
SELECT content_group, sessions, engaged_sessions, engagement_rate
ORDER BY engaged_sessions DESC
-- Q2:各内容组的转化贡献
SELECT content_group, conversions, revenue
WHERE content_group IN ('产品详情页', '活动促销页')
-- Q3:用户在各内容组之间的流转
-- 用「路径探索」报告,起点选「内容分组 = 博客文章页」
-- 查看用户下一步去了哪里
常见问题
Q:我的博客文章 URL 直接在根目录(如 /post-title/),和静态页面 URL 格式一样,方案 B 无法区分怎么办?
这是方案 B 的根本局限。解决方案是用方案 A——PHP 的 is_single() 和 is_page() 天然能区分两者,即使 URL 结构完全相同。
Q:WooCommerce 产品分类页(/product-category/)应该归入「产品列表页」还是「分类归档页」?
从业务视角看,用户在产品分类页的行为意图(浏览商品、准备购买)与博客分类页完全不同。建议归入「产品列表页」,或者单独设置「产品分类页」作为独立分组,这样可以分析它相对于 /shop/ 主列表页的转化差异。
Q:多语言网站(如用 WPML 或 Polylang)需要特殊处理吗?
URL 层面通常会有语言前缀(/en/、/zh/),方案 B 的正则需要更新以兼容这些前缀,例如:
// 匹配 /product/ 或 /en/product/ 或 /zh/product/
if (/^(\/[a-z]{2})?\/product\/[^/]+\/?$/.test(path)) return '产品详情页';
方案 A 不受影响,因为 WordPress 条件标签与 URL 语言前缀无关。
Q:发布到生产环境后 DebugView 看不到数据,但预览模式是对的?
原因通常是 GTM 容器没有发布。预览模式测试的是草稿版本,生产环境需要点击「提交」发布容器更改。
总结:两套方案对比
| 对比维度 | 方案 A(模板注入) | 方案 B(GTM 前端规则) |
|---|---|---|
| 准确性 | ⭐⭐⭐⭐⭐ 使用 WordPress 官方 API | ⭐⭐⭐ 依赖 URL 结构 |
| 维护成本 | 低(URL 改变不影响) | 高(URL 结构改变需更新) |
| 需要改代码 | 需要改 functions.php | 不需要改主题 |
| WooCommerce 支持 | 完整(含动态购物车状态) | 基本支持 |
| 区分文章/页面 | ✅ is_single() vs is_page() | ❌ 无法区分同路径结构 |
| 推荐场景 | 有开发权限、长期维护 | 快速验证、临时方案 |
核心建议:用方案 A 做数据层,用 GTM 读取数据层发送事件。这样既获得了 WordPress 原生 API 的准确性,又保持了 GTM 管理代码的灵活性,两全其美。
评论