やりたいこと
インターフェースを重視して、Google Analyticsからデータを取得するPythonプログラムをつくりました。
個人でデータを取りたい場合はPythonを書いて運用すれば良いのですが、会社内で行う際はPythonに明るい人しかメンテナンスできなかったり、昨今ノーコードツール等を使う方も多いです。
なので、「Pythonはわからないけど、とりあえずプログラムを実行しちゃえば、勝手にデータが取れる!」 と、パーツとして使えるようなプログラムを作成しました。
記事の最後に、ノーコードETLツールであるDataSpiderを使ってどのように実行できるのかを紹介しておきます。
事前準備
ライブラリのインストール
Google AnalyticsのAPIを含め、4つのライブラリを使います。
pip install --upgrade google-api-python-client
pip install --upgrade oauth2client
pip install python-dateutil
pip install pandas
認証キーの準備
認証キーとは、Google Analyticsからデータを取得するためのjsonファイルです。
Google 公式のページからダウンロード可能です。また、サービスアカウントの設定等が必要ですが、本記事では割愛します。
中身はこんな感じです。
{
"type": "service_account",
"project_id": "aaaaaaaaaaaaaaaaaaa",
"private_key_id": "bbbbbbbbbbbbbbbbbbbbbb",
"private_key": "-----BEGIN PRIVATE KEY-----\nccccccccccccccccccccc\n-----END PRIVATE KEY-----\n",
"client_email": "dddddddddddd@eeeeeeeeeeee.iam.gserviceaccount.com",
"client_id": "0123456789",
"auth_uri": "ffffffffffffff",
"token_uri": "https://gggggg",
"auth_provider_x509_cert_url": "https://hhhhh",
"client_x509_cert_url": "https://iiiiiiii"
}
設定ファイル(config.ini)の作成
テキストエディタなどで新しく作成します。
認証キー(json)のパスや、VIEW_ID、ログファイルの書き込み先を指定するファイルです。
「config.ini」などという名前で作成ください。
[DEFAULT]
SCOPES = ["https://www.googleapis.com/auth/analytics.readonly"]
KEY_FILE_LOCATION = C:\Users\ga\json\auth.json
VIEW_ID = 123456789
PAGE_SIZE = 100000
[LOG]
PATH = C:\\logs\\ga_project
FILENAME = ga_project
Pythonプログラム
下記の2つのPythonファイルから構成していますが、実行するのは「get_GA_data_main.py」だけでOKです。
- get_GA_data_main.py
- get_GAdata.py
ポイントは、設定ファイル(config.ini)のパスやデータの取得期間をユーザー側が指定できるようにしている点です。
過去1週間分取得するようにしていたけれど、サーバーの障害などでデータの抜け漏れが発生することは往々に起こります。
そのときに、過去2週間分取得するシーンを考えると、特にデータの取得期間といったパラメータをユーザーが簡単に変更できることが大切です。
import get_GAdata
from datetime import datetime, date
import sys
import argparse
parser = argparse.ArgumentParser(description='This script gets GA data and insert data. ')
parser.add_argument('-configfile_GA_path', help = "Set path including config file name e.g. C:/Users/hogehoge/config.ini ", required = True)
parser.add_argument('-startdate', help = "Set startdate to insert into database as hyphen dilimiter yyyy-mm-dd e.g. 2021-01-01 ", required = True)
parser.add_argument('-enddate', help = "Set enddate to insert into database as hyphen dilimiter yyyy-mm-dd e.g. 2021-01-07 ", required = True)
parser.add_argument('-csv_path', help = "Set path to save csv path e.g. C:/Users/hogehoge", required = True)
parser.add_argument('-csv_prefix_name', help = "Set prefix csv name as [prefix name]yyyy-mm-dd~yyyy-mm-dd[suffix name]. ", default = "", required = False)
parser.add_argument('-csv_suffix_name', help = "Set suffix csv name as [prefix name]yyyy-mm-dd~yyyy-mm-dd[suffix name]. ", default = "", required = False)
args = parser.parse_args()
def main():
config_GA_path = args.configfile_GA_path
csv_path = args.csv_path
csv_prefix_name = args.csv_prefix_name
csv_suffix_name = args.csv_suffix_name
try:
con_GA = get_GAdata.get_GAdata(config_GA_path)
con_GA.initialize_analyticsreporting()
except Exception as e:
sys.exit(f"{e}")
try:
datetime.strptime(args.startdate, "%Y-%m-%d")
datetime.strptime(args.enddate, "%Y-%m-%d")
except ValueError as e:
sys.exit(f"Error massage: {e}\nCheck parameters you set:\n startdate: {args.startdate} \n enddate : {args.enddate}")
startdate = date(int(args.startdate.split("-")[0]), int(args.startdate.split("-")[1]), int(args.startdate.split("-")[2]))
enddate = date(int(args.enddate.split("-")[0]), int(args.enddate.split("-")[1]), int(args.enddate.split("-")[2]))
if(startdate > enddate):
sys.exit(f"'startdate' must be before (or the same) 'enddate':\nCheck parameters you set:\n startdate: {args.startdate} \n enddate : {args.enddate}")
try:
con_GA.get_report(startdate=str(startdate), enddate=str(enddate))
filename = csv_path + "/" + csv_prefix_name + str(startdate) + "~" + str(enddate) + csv_suffix_name +".csv"
con_GA.write_csv(filename)
except Exception as e:
sys.exit(f"{e}")
if __name__ == "__main__":
main()
from googleapiclient.discovery import build
from oauth2client.service_account import ServiceAccountCredentials
import logging
import os
import datetime
import configparser
import json
from dateutil.relativedelta import relativedelta
import copy
import pandas as pd
import traceback
config_ini = configparser.ConfigParser()
class get_GAdata:
def __init__(self, config_ini_path):
if not os.path.exists(config_ini_path):
raise FileNotFoundError(config_ini_path + " is not found. ")
config_ini.read(config_ini_path, encoding="utf-8")
# ------------------------ ログ出力パラメータ ------------------------
month = datetime.datetime.now().strftime('%m')
formatter = '%(levelname)s : %(asctime)s : %(message)s'
logFilePath = config_ini['LOG']['PATH'] + '\\' + config_ini['LOG']['FILENAME'] + '_' + month + '.log'
logging.basicConfig(filename=logFilePath,level=logging.INFO,format=formatter)
try:
self.SCOPES = json.loads(config_ini['DEFAULT']['SCOPES'])
self.KEY_FILE_LOCATION = config_ini['DEFAULT']['KEY_FILE_LOCATION']
self.VIEW_ID = config_ini['DEFAULT']['VIEW_ID']
self.PAGE_SIZE = int(config_ini['DEFAULT']['PAGE_SIZE'])
except KeyError as e:
# print(f"Key {e} is not found. Please check config file. \nErrorMassage: {traceback.format_exception_only(type(e), e)}")
logging.error(f"Key {e} is not found. Please check config file. \nErrorMassage: {traceback.format_exception_only(type(e), e)}")
raise KeyError(f"Key {e} is not found. Please check config file. \nErrorMassage: {traceback.format_exception_only(type(e), e)}")
except ValueError as e:
# print(f"Key PAGE_SIZE: '{config_ini['DEFAULT']['PAGE_SIZE']}' cannot be parsed integer. \nErrorMassage: {traceback.format_exception_only(type(e), e)}")
logging.error(f"Key PAGE_SIZE: '{config_ini['DEFAULT']['PAGE_SIZE']}' cannot be parsed integer. \nErrorMassage: {traceback.format_exception_only(type(e), e)}")
raise ValueError(f"Key PAGE_SIZE: '{config_ini['DEFAULT']['PAGE_SIZE']}' cannot be parsed integer. \nErrorMassage: {traceback.format_exception_only(type(e), e)}")
else:
# print("Config file is successfully opened. ")
logging.info("Config file is successfully opened. ")
self.yesterday = datetime.datetime.today() + relativedelta(days=-1)
def initialize_analyticsreporting(self):
try:
credentials = ServiceAccountCredentials.from_json_keyfile_name(
self.KEY_FILE_LOCATION, self.SCOPES)
# Build the service object.
self.analytics = build('analyticsreporting', 'v4', credentials=credentials)
except Exception as e:
logging.error(f"Error occured at function 'initialize_analyticsreporting'. \nErrorMassage: {e}")
raise FileNotFoundError(f"Error occured at function 'initialize_analyticsreporting'. \nErrorMassage: {e}")
else:
logging.info(f"initialize_analyticsreporting is succeeded. ")
def get_report_all_pages(self, pageToken=None, startdate='7daysAgo', enddate='today'):
metrics = [
{'expression': 'ga:users'},
{'expression': 'ga:sessions'},
{'expression': 'ga:pageviews'},
{'expression': 'ga:timeOnPage'},
{'expression': 'ga:avgTimeOnPage'}
]
dimensions = [
{'name': 'ga:date'},
{'name': 'ga:dateHourMinute'},
{'name': 'ga:pagePath'},
{'name': 'ga:source'},
{'name': 'ga:deviceCategory'},
{'name': 'ga:operatingSystem'},
{'name': 'ga:pageTitle'},
{'name': 'ga:pagePathLevel1'}
]
self.response = self.analytics.reports().batchGet(
body={
'reportRequests': [{
'viewId' : self.VIEW_ID,
'dateRanges' : [{'startDate': startdate, 'endDate': enddate}],
'metrics' : metrics,
'dimensions' : dimensions,
'pageToken' : pageToken,
'pageSize' : self.PAGE_SIZE, # Default = 1000行, 最大 100,000 行。 Project: 1 日 50,000 件のリクエスト; ビュー 1 日 10,000 件のリクエスト,
'samplingLevel' : 'LARGE'
}]
}
).execute()
def parse_data(self, raw_datas=None):
response = self.response
if raw_datas is None:
raw_datas = []
for report in response.get('reports', []):
columnHeader = report.get('columnHeader', {})
dimensionHeaders = columnHeader.get('dimensions', [])
metricHeaders = columnHeader.get('metricHeader', {}).get('metricHeaderEntries', [])
nextPageToken = report.get('nextPageToken')
for row in report.get('data', {}).get('rows', []):
raw_data = []
dimensions = row.get('dimensions', [])
dateRangeValues = row.get('metrics', [])
for header, dimension in zip(dimensionHeaders, dimensions):
raw_data.append(dimension)
for i, values in enumerate(dateRangeValues):
for metricHeader, value in zip(metricHeaders, values.get('values')):
raw_data.append(value)
raw_datas.append(raw_data)
# カラム名を作成: dimension names & metric names
column_names = copy.deepcopy(dimensionHeaders)
column_names = column_names + [metric.get('name') for metric in metricHeaders]
self.data_output = [raw_datas, column_names, nextPageToken]
def get_report(self, startdate, enddate):
pageToken = "0"
try:
while pageToken != None:
self.get_report_all_pages(pageToken=pageToken, startdate=str(startdate), enddate=str(enddate))
self.parse_data()
if('ga_data' not in locals()):
ga_data = pd.DataFrame(data=self.data_output[0], columns=[ column_name.replace(":" ,"_") for column_name in self.data_output[1] ])
else:
tmp = pd.DataFrame(data=self.data_output[0], columns=[ column_name.replace(":" ,"_") for column_name in self.data_output[1] ])
ga_data = ga_data.append(tmp)
pageToken = self.data_output[2]
self.ga_data = ga_data
except Exception as e:
logging.error(f"Error occured at function 'get_report'. \nErrorMassage: {e}")
raise Exception(f"Error occured at function 'get_report'. \nErrorMassage: {e}")
else:
logging.info(f"Getting GA data is succeeded. ")
def write_csv(self, filename):
self.ga_data.to_csv(filename, index=False)
※ 補足
get_GAdata.pyのmetricsとdimensionsに要素を追加して、取得するデータ項目を追加できます。
どうやって実行する?
素直にPythonで実行する方法と、exe化して実行する方法の2つを紹介します。
exe化するメリットは、プログラムを実行するサーバーにPythonが入っていなくても利用できることです。
そのため、冒頭のpipでのインストールなども不要になり、デプロイが非常に簡単です。
Pythonで実行する
必要なパラメータを指定して、CUIから実行できます。
python .\get_GA_data_main.py -configfile_GA_path "C:/Users/hogehoge/config.ini" -startdate 2023-03-01 -enddate 2023-03-07 -csv_path "C:/Users/hogehoge/data"
パラメータについて紹介します。
- configfile_GA_path・・・config.iniのファイルパスを指定します。
- startdate・・・データの取得開始期間を指定します。yyyy-mm-ddの形式で指定してください。
- enddate・・・データの取得終了期間を指定します。yyyy-mm-ddの形式で指定してください。
- -csv_path・・・csvの吐き出し先を指定します。
- -csv_prefix_name・・・【任意指定】CSVの接頭辞を指定できます。
- -csv_suffix_name・・・【任意指定】CSVの接尾辞を指定できます。
exe化して実行する
pyInstallerというライブラリを使って、Pythonファイルをexe化できます。
(ライブラリにpandasが入っているとexe化に比較的時間がかかるので注意してください。)
pip install pyinstaller
pyinstaller get_GA_data_main.py --onefile
上記を実行すると、「build」と「dist」という2つのフォルダが作成されます。
distフォルダの直下に「get_GA_data_main.exe」が作成されています。
こちらを使って同じように実行します。
.\dist\get_GA_data_main.exe -configfile_GA_path "C:/Users/hogehoge/config.ini" -startdate 2023-03-01 -enddate 2023-03-07 -csv_path "C:/Users/hogehoge/data"
ノーコードツールからの実行
今回は、サンプルとしてDataSpiderというノーコードETLツールでの使い方を簡単に紹介します。
上記の設定で、指定した期間のデータを指定したフォルダにCSVとして出力することが可能です。
CSV出力後は、データベースに格納するとBIツールなどを使った可視化に繋げやすいかなと思います。
おわりに
今回は、Pythonを知らない人も使い回せるスクリプトを紹介しました。
ノーコードツールを使って、いろいろなサービスから自動でデータを取る仕組みは今後増えると思います。
ツール側がすべてのサービスと連携できるようになるのは現実的でなく、その場合PythonなどからAPIを叩いてデータを取ることになるため、本記事で紹介したようなI/F部分を意識した、パーツとして使えるプログラムを作るとより便利なのかなと思います。