跳转到正文
文章目录

前言:WordPress 的天然优势

WordPress 的最大优势是它本身就知道每个页面是什么类型——文章、页面、分类页、标签页、首页、搜索结果……这些类型信息可以直接从 PHP 模板层获取,不需要用脆弱的 URL 正则去猜。

本文会提供两套方案:

  • 方案 A(推荐):从 WordPress 模板层注入页面类型,数据更准确,维护成本更低
  • 方案 B(备选):纯 GTM 前端规则,不改动主题代码,适合无法编辑主题的场景

两套方案最终都指向同一个目标:在 page_view 事件中发送 content_group 参数,然后在 GA4 界面完成维度注册。


第一部分:URL 匹配逻辑(开发篇)

方案 A:WordPress 模板层注入(强烈推荐)

A-1:理解 WordPress 条件标签

WordPress 提供了一套条件标签(Conditional Tags),可以精确判断当前页面类型,比任何 URL 正则都可靠:

</> PHP
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
<?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

  1. GTM 工作区 → 左侧「变量」→「新建」
  2. 变量类型选择:数据层变量(Data Layer Variable)
  3. 配置如下:
</> PLAINTEXT
变量名称:DL - contentGroup
数据层变量名称:contentGroup
数据层版本:版本 2
默认值:其他
  1. 保存

同理,创建第二个变量用于二级分组:

</> PLAINTEXT
变量名称: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

</> 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 → 代码 → 新建

基础配置:

</> PLAINTEXT
代码名称: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
<?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
<?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:进入自定义定义界面

完整路径:

</> PLAINTEXT
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,开启插件后刷新页面。

操作路径:

</> PLAINTEXT
GA4 管理界面
  └── 管理(齿轮)
        └── 媒体资源
              └── DebugView

你会看到一个实时事件流。点击左侧的 page_view 事件,在右侧展开参数列表,确认:

</> PLAINTEXT
✅ 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 会在浏览器底部打开一个调试面板。

  1. 访问任意页面
  2. 在左侧「Tags Fired」列表中找到你配置的 GA4 代码
  3. 点击该代码 → 查看「Variables」标签页
  4. 确认 DL - contentGroup 变量的值符合预期

方法 3:浏览器控制台快速验证

如果你用的是方案 A(dataLayer 注入),可以在浏览器控制台运行:

</> JAVASCRIPT
// 查看 dataLayer 中的内容分组数据
window.dataLayer.filter(e => e.contentGroup)

// 预期输出示例:
// [{ contentGroup: "产品详情页", contentGroup2: "运动鞋" }]

第五部分:在 GA4 探索报告中使用内容分组

数据准确后,就可以在报告中发挥它的价值了。

创建内容分组分析报告

路径:GA4 → 探索 → 新建探索 → 自由格式

配置步骤:

  1. 在左侧「维度」区域,点击「+」
  2. 搜索「内容分组」,勾选后点击「导入」
  3. 将「内容分组」拖入「行」区域
  4. 在「指标」区域添加:会话数、参与会话数、参与率、关键事件(转化)
  5. 如需对比,可将「内容分组」同时拖入「列」,用「日期范围」对比

推荐的分析视角:

</> SQL
-- 用 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 的正则需要更新以兼容这些前缀,例如:

</> JAVASCRIPT
// 匹配 /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 管理代码的灵活性,两全其美。

评论

搜索站内内容

输入关键词开始搜索