Google Analytics4(GA4)のAPI対応
Google Analytics4のAPI利用方法を紹介します。
Googleアナリティクスのバージョン変更に伴う対応をしたのでその時のメモです。
現在のGoogleアナリティクスは**ユニバーサルアナリティクス(UA)**と呼ばれるバージョンですが、 これが2023年7月1日から使えなくなるよと管理画面からも警告されていたので対応しました。
対応概要
ゴール
- GA4のAPIを使用してWebサイトのアクセス数などの情報を取得する
- 取得した情報はAWSのS3に保存する
- S3に保存したデータをJavaScript(jQuery)でサイトに表示する
図で書くのは省略しますがAWSリソースはこんな流れのイメージです。 CloudWatchで定期的にLambdaを実行してGA4のAPIを呼び出しています。 APIで取得したデータはS3に保存します。
CloudWatch → Lambda → S3 → CloudFront → Webブラウザ参照 (JavaScript)
なおこの実装は運用しているまとめサイトの人気記事ランキングの表示に使っています。
作業概要
- GoogleアナリティクスでGA4プロパティを作成してWebサイトにタグを埋め込む →Googleタグマネージャーで対応。内容は省略。
- GCPでAPI有効化、権限設定
- AWS LambdaでGA4 API実行コード (python)を作成、デプロイ
- フロントエンド処理を作成 (JavaScript)
参考サイト
- Google Analytics Data API (GA4) API Quickstart →公式サイト。まずここをチェック。
- Python Client for Analytics Data API →PythonのAPIライブラリ仕様が載ってます。
- GA4|Google Analytics 4のデータをPythonで取得する →サービスアカウント使用例。
以下はOAuthを使用した例。今回は割愛。
- How to Create Google API Credentials for Use in Python
- Connecting to Google Analytics 4 (GA4) API with Python
- Using Python to Pull Google Analytics 4 (GA4) API Reports
1.GA4 API有効化手順
API利用にあたって今回はサービスアカウントを作成します。 以下の画面に従って設定を行っていきます。
GCP設定
GCPにアクセスして設定を実施。
Googleアナリティクス設定
GCPで作成したサービスアカウントを対象のGoogleアナリティクスに追加。 権限は閲覧でデータを参照できる。
2.AWSリソース設定(Lambda、CloudFormation)
- CloudFormationでリソースは作成。
- LambdaはDocker利用、実行コードはpython。
Lambda
srcディレクトリ配下に以下のファイルを置く。
- app.py: メイン処理実行
- credential.json: GCPでダウンロードした秘密鍵ファイル。名前を変更して利用。
- Dockerfile: Docker定義
- requirements.txt: 使用ライブラリ記述
- SETTING.py: GA4のプロパティなど設定情報記述
app.py
"""Lambda実行ファイル
* Google Analytics4(GA4)対応のレポート取得コード
* SETTING.pyファイルにGA4のIDやレポート取得条件を定義
"""
import os
import json
import SETTING
from google.analytics.data_v1beta import BetaAnalyticsDataClient
from google.analytics.data_v1beta.types import BatchRunReportsRequest
def ga4_request(ga_id, requests):
"""GA4レポートのリクエスト実行
Args:
ga_id (str): GA4プロパティID
requests (obj): GA4リクエスト
Returns:
google.analytics.data_v1beta.types.BatchRunReportsResponse:
The batch response containing multiple reports.
"""
client = BetaAnalyticsDataClient()
# BatchRunReportsRequest: 複数リクエストを投げたいときに使用する
# RunReportsRequest: リクエストが一つの場合はこれを使う
request = BatchRunReportsRequest(
property=f"properties/{ga_id}",
requests=requests
)
# RunReportsRequestを使った場合はrun_reportを使用する
res = client.batch_run_reports(request)
return res
def parse_result(ga4_response):
"""GA4リクエストのレスポンスを加工
GA4レポートデータの加工を行う。
BatchRunReportsResponseを使って複数レポートを取得した場合の処理を記述。
RunReportsResponseを使った場合はレスポンスの形式が少し異なるため注意。
Args:
ga4_response (google.analytics.data_v1beta.types.BatchRunReportsResponse):
BatchRunReportsRequestで取得する結果を設定する。
Returns:
list of obj:
レポートを格納したリスト。
ディメンション名・メトリック名をつけた形式へ加工する。
"""
result_all = []
for i, report in enumerate(ga4_response.reports):
result_row = []
for row in report.rows:
data = {}
# 取得したレポートはディメンション名・メトリック名がないためkeyとして付与する
for j, key in enumerate(report.dimension_headers):
# ページタイトルに不要文字があるためここで削除しておく
data[key.name] = row.dimension_values[j].value.replace(' - 漫画まとめ速報', '')
for j, key in enumerate(report.metric_headers):
data[key.name] = row.metric_values[j].value
result_row.append(data)
result_all.append(result_row)
return result_all
def lambda_handler(event, context):
"""Lambda function"""
# Google Analyticsアカウント設定
os.environ[SETTING.GOOGLE_APPLICATION_CREDENTIALS] = SETTING.KEY_FILE_LOCATION
# リクエスト(メトリック=PV)
response = ga4_request(SETTING.GA_PROPERTY_ID, SETTING.REQUESTS_PV)
# レポート加工
result = parse_result(response)
# S3へレポート結果登録
SETTING.CLIENT.put_object(
Body=json.dumps(result),
Bucket=SETTING.S3_BUCKET_NAME,
CacheControl=SETTING.CACHE_CONTROL,
ContentType=SETTING.CONTENT_TYPE,
Expires=SETTING.EXPIRES,
Key=SETTING.SAVE_FILE_PV
)
# リクエスト(メトリック=ユーザー数)
response = ga4_request(SETTING.GA_PROPERTY_ID, SETTING.REQUESTS_USERS)
# レポート加工
result = parse_result(response)
# S3へレポート結果登録
SETTING.CLIENT.put_object(
Body=json.dumps(result),
Bucket=SETTING.S3_BUCKET_NAME,
CacheControl=SETTING.CACHE_CONTROL,
ContentType=SETTING.CONTENT_TYPE,
Expires=SETTING.EXPIRES,
Key=SETTING.SAVE_FILE_USERS
)
return
SETTING.py
データ取得条件はJSONで記述従ったが変換処理に手間がかかるため、API仕様そのままで記述している。 ちなみにサービスアカウントではなく、OAuth認証もしくはGCPのCLIを使用すればJSON形式でリクエストを そのまま食わせられるが調査に時間がかかるため断念…
途中まで調べたので気が向いたら記事を書く予定。
"""設定ファイル
* Google Analytics4(GA4)およびAWS設定値を定義
* GA4レポートデータの保存先もここで定義しておく
"""
import boto3
import os
from datetime import datetime
from google.analytics.data_v1beta.types import (
DateRange,
Dimension,
Filter,
FilterExpression,
FilterExpressionList,
Metric,
OrderBy,
RunReportRequest
)
# =====================
# AWS
# =====================
CLIENT = boto3.client("s3")
# データ保存先のS3バケット名はtemplate.yamlで定義
S3_BUCKET_NAME = os.environ.get("S3_BUCKET_NAME")
# S3ファイル保存時のメタデータ
CACHE_CONTROL = "public, max-age=0"
CONTENT_TYPE = "application/json"
EXPIRES = datetime(1990, 1, 1)
# S3保存先 <環境に合わせて変更する>
SAVE_FILE_PV = "ga4/manga/report_pv.json"
SAVE_FILE_USERS = "ga4/manga/report_users.json"
# =====================
# GA4
# =====================
# GA4プロパティID
GA_PROPERTY_ID = "<環境に合わせて変更する>"
# ダウンロードしてきた認証情報のファイル名
KEY_FILE_LOCATION = "credential.json"
# 認証設定
GOOGLE_APPLICATION_CREDENTIALS = "GOOGLE_APPLICATION_CREDENTIALS"
# リクエスト時に使用するレポートの取得条件。
# date_rangesに複数期間を指定してレポートを取得することはできるが、
# レスポンスのデータ加工に手間がかかるためここでは期間をわけて変数を用意。
REQUEST_PV_POST_30DAYS_AGO = RunReportRequest(
dimensions=[
Dimension(name="pagePath"),
Dimension(name="pageTitle"),
],
metrics=[
Metric(name="screenPageViews"),
],
date_ranges=[
DateRange(start_date="30daysAgo", end_date="today"),
],
dimension_filter=FilterExpression(
filter=Filter(
field_name="pagePath",
string_filter=Filter.StringFilter(
value="/post/",
match_type=Filter.StringFilter.MatchType.CONTAINS,
case_sensitive=False
),
)
),
order_bys=[
OrderBy(desc=True, metric=OrderBy.MetricOrderBy(metric_name="screenPageViews"))
],
limit=30
)
REQUEST_PV_POST_7DAYS_AGO = RunReportRequest(
dimensions=[
Dimension(name="pagePath"),
Dimension(name="pageTitle"),
],
metrics=[
Metric(name="screenPageViews"),
],
date_ranges=[
DateRange(start_date="7daysAgo", end_date="today"),
],
dimension_filter=FilterExpression(
filter=Filter(
field_name="pagePath",
string_filter=Filter.StringFilter(
value="/post/",
match_type=Filter.StringFilter.MatchType.CONTAINS,
case_sensitive=False
),
)
),
order_bys=[
OrderBy(desc=True, metric=OrderBy.MetricOrderBy(metric_name="screenPageViews"))
],
limit=30
)
REQUEST_PV_POST_1DAYS_AGO = RunReportRequest(
dimensions=[
Dimension(name="pagePath"),
Dimension(name="pageTitle"),
],
metrics=[
Metric(name="screenPageViews"),
],
date_ranges=[
DateRange(start_date="1daysAgo", end_date="today"),
],
dimension_filter=FilterExpression(
filter=Filter(
field_name="pagePath",
string_filter=Filter.StringFilter(
value="/post/",
match_type=Filter.StringFilter.MatchType.CONTAINS,
case_sensitive=False
),
)
),
order_bys=[
OrderBy(desc=True, metric=OrderBy.MetricOrderBy(metric_name="screenPageViews"))
],
limit=30
)
REQUEST_PV_TAGS_7DAYS_AGO = RunReportRequest(
dimensions=[
Dimension(name="pageTitle"),
],
metrics=[
Metric(name="screenPageViews"),
],
date_ranges=[
DateRange(start_date="7daysAgo", end_date="today"),
],
dimension_filter=FilterExpression(
and_group=FilterExpressionList(
expressions=[
FilterExpression(
filter=Filter(
field_name="pagePath",
string_filter=Filter.StringFilter(
value="/tags/",
match_type=Filter.StringFilter.MatchType.CONTAINS,
case_sensitive=False
),
)
),
FilterExpression(
not_expression=FilterExpression(
filter=Filter(
field_name="pageTitle",
string_filter=Filter.StringFilter(
value="Tags - 漫画まとめ速報",
match_type=Filter.StringFilter.MatchType.CONTAINS,
case_sensitive=False
),
)
)
),
]
)
),
order_bys=[
OrderBy(desc=True, metric=OrderBy.MetricOrderBy(metric_name="screenPageViews"))
],
limit=30
)
REQUEST_USERS_SOURCE = RunReportRequest(
dimensions=[
Dimension(name="sessionSource"),
],
metrics=[
Metric(name="activeUsers"),
],
date_ranges=[
DateRange(start_date="7daysAgo", end_date="today"),
],
order_bys=[
OrderBy(desc=True, metric=OrderBy.MetricOrderBy(metric_name="activeUsers"))
],
limit=100
)
REQUEST_USERS_CLICK = RunReportRequest(
dimensions=[
Dimension(name="linkDomain"),
],
metrics=[
Metric(name="activeUsers"),
],
date_ranges=[
DateRange(start_date="7daysAgo", end_date="today"),
],
order_bys=[
OrderBy(desc=True, metric=OrderBy.MetricOrderBy(metric_name="activeUsers"))
],
limit=100
)
# 各リクエストを配列に格納。
# BatchRunReportsRequestで一度にリクエストできる上限は5個までのため注意。
REQUESTS_PV = [
REQUEST_PV_POST_1DAYS_AGO,
REQUEST_PV_POST_7DAYS_AGO,
REQUEST_PV_POST_30DAYS_AGO,
REQUEST_PV_TAGS_7DAYS_AGO,
]
REQUESTS_USERS = [
REQUEST_USERS_SOURCE,
REQUEST_USERS_CLICK,
]
requirements.txt
google-analytics-data
google-api-python-client
boto3
Docerfile
FROM public.ecr.aws/lambda/python:3.9
# 使用するファイルを追記
COPY app.py SETTING.py requirements.txt credential.json ./
RUN python3.9 -m pip install -r requirements.txt -t .
# Command can be overwritten by providing a different command in the template directly.
CMD ["app.lambda_handler"]
CloudFormation
以下のコードでリソースを作成。 PyCharmでデプロイ。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
python3.9
Lambda and EventBridge SAM Template for getGA4Report
Globals:
Function:
Timeout: 3
Environment:
Variables:
TZ: Asia/Tokyo
S3_BUCKET_NAME: <S3バケット名を記入>
Resources:
LambdaRole:
Type: AWS::IAM::Role
Properties:
RoleName: GetGA4ReportLambdaRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: "/"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- arn:aws:iam::aws:policy/AmazonS3FullAccess
LambdaFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: GetGA4ReportLambda
Role: !GetAtt LambdaRole.Arn
Timeout: 30
MemorySize: 512
PackageType: Image
Architectures:
- x86_64
Metadata:
Dockerfile: Dockerfile
DockerContext: ./src
DockerTag: python3.9-v1
Rule:
Type: AWS::Events::Rule
Properties:
Description: Scheduled Rule
Name: GetGA4ReportLambdaRule
# 30分ごとに実行
ScheduleExpression: 'cron(0/30 * * * ? *)'
State: ENABLED
Targets:
- Arn: !GetAtt LambdaFunction.Arn
Id: lambda
FunctionPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !Ref LambdaFunction
Principal: events.amazonaws.com
SourceArn: !GetAtt Rule.Arn
Outputs:
LambdaFunction:
Description: "Lambda Function ARN"
Value: !GetAtt LambdaFunction.Arn
LambdaRole:
Description: "Implicit IAM Role created for function"
Value: !GetAtt LambdaRole.Arn
CloudFront
GA4のアクセス情報をS3に保存するが、CloudFront経由で参照できるように設定。 詳細は割愛。
3.フロントエンド処理 (JavaScript)
S3データを取得して画面に表示。 jQueryを使用。
// 人気記事ランキングのリンク生成
$(function () {
// console.log("_____rank.js: load");
// 人気記事の最大表示数
let rankCount = 10;
// JSONファイル取得先
let fileUrl = "/ga4/manga/report_pv.json";
$.getJSON(fileUrl)
.done(function (response) {
// console.log("_____rank.js: ajax success", response);
let count = 0;
response.forEach(function (data) {
// olタグを生成
let ol = $("<ol>").addClass("popular__list");
// 取得データは期間別に配列形式で格納されている。
// postページのデータが格納されているが、最終値はtagページのデータ。
// [0]: today~1day ago
// [1]: today~7days ago
// [2]: today~30day ago
// [3]: today~7day ago(tag page)
if (count < 3) {
// 挿入先のIDを生成
let id = "#tab-content-0" + count;
count = count + 1;
for (let i = 0; i < rankCount; i++) {
let title = data[i].pageTitle;
let url = data[i].pagePath;
// liタグを生成してaタグ追加
let li = $("<li>")
.addClass("popular__item")
.append(`<a href="${url}">${title}</a>`);
// olに生成したliタグを追加
ol.append(li);
}
// 指定したIDに生成したリンクを挿入
$(id).html(ol);
}
});
})
.fail(function (e) {
console.log("_____rank.js: ajax fail", e);
});
});
最後に
Google Analyticsで今までAPIを使っていた方は、そこまで使い方変わっていないので時間はかからないと思います。
Share this post
Twitter
Facebook
Email