Post

从 Google Analytics 获取 Pageviews

人的欲望总是不断膨胀的,笔者是凡人,也难逃此劫。近来,它滋生成为对博客功能的一个新需求:获取 GA (Google Analytic) 的 Pageviews。

本站开建早期,就嵌入了 GA 的数据收集代码。它的功能仅限搜集跟踪记录并上传,没法同时返回统计信息。 于是调研 Google 相关开发手册,得知 GA 中一个称为 Reporting 的组件,内含几个 API 对外提供处理后的数据,其中 Core Reporting API 支持按特定的维度、指标查询记录。所以使用这部分 API 就能得到所需要的 Pageviews 了。

根据 Core Reporting API 的教程。通过 JS 获取的 GA 报告,需要在代码提供 Google APIs 项目的一个信任凭证、OAuth client ID,以及 GA 的 Account Explorer 的 viewID。这三个都是站点拥有者登陆控制台才能看到的私人信息,即便可以在设置域名过滤来限定这几个值只能在自己名下的站点使用,但是直接暴露在 JS 中对外可见,似乎不太优雅。因此,从 Web 调用 Core Reporting API 读取 Pageviews 的可行性是直接否定了。

不久后,找到了一个对外安全获取 GA 数据的方案:GA Super Proxy。SuperProxy 涵盖了所有 GA Reporting 组件的 API,上述的凭证、ID 之类的敏感信息可以写入 SuperProxy 的配置,由它代为请求数据。将 SuperProxy 部署在 GAE (Google App Engine) 上,敏感信息对外不可见,这样就成了一个持续公开指定 GA 数据的广播站了,网站再从 GAE 读取数据并展示即可,美滋滋。

SuperProxy 配置与部署

第一步是攻克 SuperProxy。项目仓库 README 有介绍安装步骤以及解说 Live,强烈建议动手前看一下作者的 Live 视频,从项目架构原理到实际操作都有详细表述。请注意项目发布距今已有 5 年(2013 年发布),GCP (Google Cloud Platform) 布局已经不可同日而语,项目 README 以 Live 的部分操作内容已经过时,所以更新描述一下当前最新环境的操作。

Google APIs 创建项目

用 Google 账户登陆 Google APIs DashboardCreate Project新建一个 Project,如起名cotes-blog-ga,”Location” 项默认为 No organization

新建完毕后,为项目开启 API 和服务。+ ENABLE APIS AND SERVICES进入 API Library,搜索栏中搜关键词 “analytic” 即可找到 Analytics API,点击图标进去 Enable 此 API 服务即可。

开启 API 后页面会自动回到 Dashboard,根据 ⚠️ 信息提示点击 Create credentials 为 API 创建 credentials。创建页面作如下操作:

  1. Find out what kind of credentials you need
    • Where will you be calling the API from? 下拉选择 Web brower(Javascript)
    • What data will you be accessing? 选择 User data
  2. Create an OAuth 2.0 client ID
    • Client ID 自定义命名,笔者为 blog-oauth
    • Restrictions 两项暂时留空,往后将会写入 GAE 的项目地址。
  3. Set up the OAuth 2.0 consent screen
    • Email 保持默认值
    • 产品名称自定义命名,不与其他公司产品重名即可,例笔者为 cotes-blog-ga
  4. Download credentials
    • 视个人需要决定下载与否,供 SuperProxy 使用的 Client IDClient secret 都可以在 Dashboard 直接查看。

完成后即可生成新 OAuth 2.0 client ID:

OAuth 2.0 client ID

上述 API Dashboard 的操作就算过程中的填写信息有纰漏也不用担心,API Dashboard 相互之间的各个按钮开关总是可以跳转到所需页面,十分友好。

下载配置 SuperProxy

在本地满足环境依赖:

墙内的童鞋可能在本地 Cloud SDK 登陆 Google 账户会受限,可以考虑使用 Cloud SDK 网络代理 解决。

接着下载 SuperProxy 项目,SuperProxy 的配置基本按照作者的 README 介绍修改即可。

1. 修改 src/config.py

  • OAUTH_CLIENT_ID - 填入上一步创建的 Client ID
  • OAUTH_CLIENT_SECRET - 填入 Client secret
  • OAUTH_REDIRECT_URI - 填入 GAE 派发的免费域名 https://PROJECT_ID.appspot.com,其中 PROJECT_IDGoogle APIs Dashboard 或者其他任一 GCP 页面中,点击顶栏项目名即可查看。
  • OAUTH_REDIRECT_URI - 配置内默认在地址尾部添加了 /admin/auth,所以 URI 全貌为:https://PROJECT_ID.appspot.com/admin/auth

此时再返回上一步的 Credentials,点击 OAuth 2.0 client IDs 中的 OAuth ID,在设置页面的 Authorized redirect URIs 填入 SuperProxy 中 OAUTH_REDIRECT_URI 的完整地址,例如,笔者是:https://cotes-blog-ga-214617.appspot.com/admin/auth

2. 修改 src/app.yaml

src/app.yaml 文件首部两行:applicationversion,在 Cloud SDK 213.0.0 中已经标记为无效字段了,需要将其删除,否则部署时会出现警告而导致中断。

1
2
- application: your-project-id
- version: 1

上传 SuperProxy 至 GAE

进入 SuperProxy 源码 src/ 目录,使用 Cloud SDK 命令上传:

1
$ gcloud app deploy app.yaml index.yaml --project PROJECT_ID

PROJECT_ID 替换成项目 ID,笔者的是:cotes-blog-ga-214617

GAE 上创建查询

登陆 https://PROJECT_ID.appspot.com/admin,验证账户后创建查询。GA Core Reporting API 查询请求可以在 Query Explorer 创建。因为要查询的是 Pageviews,所以笔者的查询参数如下:

  • start-date - 填写博客发布首日

  • end-date - 填 today (这是 GA Report 支持的参数,表示永远按当前查询日期为止)

  • metrics - 选择 ga:pageviews

  • dimensions - 选择 ga:pagePath

为了减少返回结果,减轻网络带宽,所以增加自定义过滤规则1

  • filters - 填写 ga:pagePath!@=;ga:pagePath!@(

    其中 ; 表示用 逻辑与 串联两条规则,!@= 表示不含 =!@( 表示不含 (

Run Query 之后在页面底部拷贝 API Query URI 生成内容,填至 GAE 上 SuperProxy 的 Encoded URI for the query 即可。

GAE 上保存查询后,会生成一个 Public Endpoint(公开的访问地址),用户访问它将返回 JSON 格式的查询结果。最后,在 Public Request Endpoint 点击 Enable Endpoint 使查询生效,Scheduling 中点击 Start Scheduling 开启定时任务。

Web 端处理 GA 数据

Web 中使用 AJAX 获取 pageviews 数据,动态刷新到页面指定位置。关键代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
$.ajax({
  url: 'PUBLIC_ENDPOINT', // Replace with SuperProxy Public endpoint
  dataType: 'jsonp', // for cross-origin access

  timeout: 1000 * 10, // 10 secs

  success: function(data) {
    displayPageviews(data.rows); // Do what ever you want.
  },
  error: function() {
    console.log('Failed to load Pageviews!');
  }
});

展示 Pageviews 的逻辑函数 displayPageviews 可以根据个人的实际需求实现,笔者需要的是每篇 Post 首部展示 Pageviews,所以在页面指定位置添加 HTML 元素 <span id="pageviews"></span>,JS 逻辑实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function displayPageviews(rows) {
  if (rows === undefined) {
    return;
  }

  var curPath = window.location.pathname;
  var curFile = curPath.slice(curPath.lastIndexOf('/') + 1); // Sometimes posts will be moved.

  var len = rows.length;
  var cnt = 0;

  for (var i = 0; i < len; ++i) {
    var gaPath = rows[i][0];
    var gaFile = gaPath.slice(gaPath.lastIndexOf('/') + 1);

    if (gaPath === curPath || gaFile === curFile) {
      cnt += parseInt(rows[i][1]);
    }
  }

  $('#pageviews').text(cnt);
}

不出意外的话,现在 GitHub 上部署的博客可以顺利展示 Pageviews 了。

扩展:墙内访问 GAE

经过上文一通猛如虎的操作,GitHub 托管的站点是解决了 Pageviews 问题。但是如果访问者身处「全球最大的局域网」内呢?那么上述的 Web 端处理手段就要失效了,发出的请求,在光明降临这片土地之前,永远无法得到返回。破此困局,须借道海外 VPS,另外如果还有个私人域名的话就更佳了。下面说一下笔者的解决方案:

VPS 运行在海外,且能被国内正常访问。VPS 上通过 Nginx 对 GAE Application 域名作反向代理,墙内用户即可通过 VPS 访问到 GAE 上的数据。笔者的 VPS 公网地址绑定了私人域名 api.cotes.in,并且申请了 SSL 证书。

那么,Nginx 的反代 GAE 的关键配置如下:

:Nginx 监听 SSL 需要事先安装相应的模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server {
   listen   443 ssl;
   ssl on;
   server_name api.cotes.in;

   ssl_certificate /etc/letsencrypt/live/api.cotes.in/fullchain.pem;
   ssl_certificate_key /etc/letsencrypt/live/api.cotes.in/privkey.pem;

   location /ga/ {
      proxy_redirect     off;
      proxy_set_header   Host               cotes-blog-ga-214617.appspot.com;
      proxy_set_header   X-Real-IP          $remote_addr;
      proxy_set_header   X-Forwarded-For    $proxy_add_x_forwarded_for;

      proxy_pass         https://cotes-blog-ga-214617.appspot.com;
      rewrite ^/ga/(.*)$  /$1  break;
   }
}

这份配置为域名 api.cotes.in 开启了 SSL,把笔者的 GAE Application 域名 https://cotes-blog-ga-214617.appspot.com 代理至墙内可见的地址 https://api.cotes.in/ga/。另外,proxy_set_header Host 很关键,GAE 需要根据它寻找你的 Application。

Web 端的 AJAX 也要扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$.ajax({
  url: 'https://cotes-blog-ga-214617.appspot.com/query?id=ahZifmNvdGVzLWJsb2ctZ2EtMjE0NjE3chULEghBcGlRdWVyeRiAgICA3pCBCgw',
  dataType: 'jsonp', // for cross-origin access

  timeout: 1000 * 5, // 5 secs

  success: function(data) {
    displayPageviews(data.rows);
  },
  error: function() {
    // Get GA-reports by proxy
    $.ajax({
      url: 'https://api.cotes.in/ga/query?id=ahZifmNvdGVzLWJsb2ctZ2EtMjE0NjE3chULEghBcGlRdWVyeRiAgICA3pCBCgw',
      dataType: 'jsonp',
      timeout: 1000 * 5,

      success: function(data) {
        console.log('Load gae from proxy.');
        displayPageviews(data.rows);
      },
      error: function() {
        console.log('Failed to get pageviews from proxy.');
      }
    })
  }
});

上述代码(第 13 行)增加对 GAE 代理地址的请求,如此一来,墙内用户可以享受到无差别的数据服务了。

参考资料

引用

This post is licensed under CC BY 4.0 by the author.

Comments powered by Disqus.