Ни для кого не секрет как важна аналитика. Она не только дает информацию о паттернах поведения пользователей, но и может помочь отследить частоту использования той или иной фичи которую вы могли недавно интегрировали. Однако, регулярное посещение сайта аналитики и протыкивание нужных эвентов может превратиться в утомительную рутину, которую можно избежать. Для того чтобы упростить задачу я набросал python сервер который бы раз в сутки забирал необходимые данные из аналитики и транслировал бы в чат. В рамках статьи я опишу базовый пример такой интеграции, однако возможности для расширения функционала довольно большие. Например, рисование графики или интеграцией с вашим сервером, если аналитика ограничивается id объекта.
Приготовления
Сперва нам конечно же нужны данные которые мы хотим отслеживать. В качестве примера я буду использовать Custom definition означающее частоту нажатия на категорию продукта. Для создания таких определений вам нужна роль как минимум редактора и не стоит ожидать что сразу после создания у вас будут данные. К сожалению, аналитика по каждому определению начинает собираться только после создания даже если по выбранному параметру уже полно данных. Подробнее о них можно почитать здесь Custom dimensions and metrics.
Вкратце как создавать такие определения:
Заходим в аналитику проекта.
Идем в панель администратора.
Выбираем нужный property.
Жмем ��а Custom definitions.
Создаем definition указав параметр события который хотим отслеживать.
Вторым шагом нам нужно получить credentials.json для нашего бота. Для этого нужно создать проект на Cloud Platform если у вас еще его нет и включить Google Analytics Data API v1 в нем. Чтобы максимально упростить этот процесс, можно нажать на кнопку подготовки проекта на страницы документации Google Analytics Data API (GA4).
Как описывается в выше указанной документации, нам нужно забрать email из полученного json-а и вставить его в панели администратора нашего проперти в аналитике. Viewer прав будет вполне достаточно.
С телеграмом все проще. Если у вас еще нет бота телеграм, то он создается у @BotFather который выдаст токен с доступом. Чтобы получить id чата в который вы хотите вещать, можно добавить бот в чат и написать ему что либо, потом запросить апдейты у API телеграма по токену и получить
curl https://api.telegram.org/<ваш_токен>/getUpdatesСервер
Сервер будет написан на python и для интеграции с ними нужно две библиотеки:
google-analytics-data
python-telegram-bot
pip install google-analytics-data python-telegram-botВ принципе можно было бы обойтись и без библиотеки телеграмма и обойтись REST-ом, но в перспективе хочется расширить функционал, посему почему бы и не добавить.
Чтобы изолировать конфигурацию проекта от кода, создадим папку data куда положим наш credentials.json и в последующем добавим конфигурацию самого проекта.
Google Аналитика
Сначала давайте получим данные с аналитики. Согласно примеру нам нужно выполнить что то вроде
from google.analytics.data_v1beta import BetaAnalyticsDataClient
from google.analytics.data_v1beta.types import (
DateRange,
Dimension,
Metric,
RunReportRequest,
)
def sample_run_report(property_id="YOUR-GA4-PROPERTY-ID"):
"""Runs a simple report on a Google Analytics 4 property."""
client = BetaAnalyticsDataClient()
request = RunReportRequest(
property=f"properties/{property_id}",
dimensions=[Dimension(name="city")],
metrics=[Metric(name="activeUsers")],
date_ranges=[DateRange(start_date="2020-03-31", end_date="today")],
)
response = client.run_report(request)
print("Report result:")
for row in response.rows:
print(row.dimension_values[0].value, row.metric_values[0].value)Однако, у нас есть свое dimension. Запрос на него выполняется немного по другому
dimensions=[Dimension(name="customEvent:<выш_сustom_definition>")],Диапозон дат лучше выбрать более динамичный. Допустим за прошедшие 7 дней
date_format = "%Y-%m-%d"
week_ago = (date.today() - timedelta(days = 7)).strftime(date_format)Ну и завернем все это в класс. Честно говоря я не так часто имею дело с python и не знаю основных практик, но что то подсказывает, что так будет лучше. Так как я планирую использовать различные property_id и не хочу таскать их толкать в функцию каждый раз. Результат выглядит так:
class Analytics:
def __init__(self, config):
self.property_id = config['property_id']
def run_report(self, event_name, limit):
"""Runs a simple report on a Google Analytics 4 property."""
client = BetaAnalyticsDataClient()
date_format = "%Y-%m-%d"
week_ago = (date.today() - timedelta(days = 7)).strftime(date_format)
request = RunReportRequest(
property=f"properties/{self.property_id}",
dimensions=[Dimension(name="customEvent:" + event_name)],
metrics=[Metric(name="activeUsers")],
date_ranges=[DateRange(start_date=week_ago, end_date="today")],
limit=limit,
)
response = client.run_report(request)
result = []
for row in response.rows:
demension = row.dimension_values[0].value
value = row.metric_values[0].value
if demension == "(not set)":
continue
result.append((demension, value))
return resultЗдесь мы собираем в массив результаты (значение, количество) исключая такого значения которое не выбрано. Их бывает довольно много и оно не особо интересно.
Telegram
У нас уже есть id чата, токен для бота и массив данных. Теперь можно их отправить. Сперва давайте сформатирует сообщение.
def format_report(title, data):
message = f'*{title}:*\n'
index = 1
for row in data:
message += f'{str(index)}. {row[0]} - _{row[1]}_\n'
index += 1
return messageФункция забирает заголовок и формирует пронумерованный список. Далее отправка
from telegram.constants import ParseMode
from telegram.ext import ApplicationBuilder, Defaults
application = (
ApplicationBuilder()
.token("<ваш_токен>")
.defaults(Defaults(parse_mode=ParseMode.MARKDOWN))
.build()
)
await application.bot.send_message(
chat_id="<id_чата>",
text=format_report("<заголовок>", report)
)
И так же завернем все это в класс
import logging
from telegram.constants import ParseMode
from telegram.ext import ApplicationBuilder, Defaults
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO
)
class TelegramBot:
def __init__(self, config):
self.chat_id = config['chat_id']
self.application = (
ApplicationBuilder()
.token(config['token'])
.defaults(Defaults(parse_mode=ParseMode.MARKDOWN))
.build()
)
def format_report(self, title, data):
message = f'*{title}:*\n'
index = 1
for row in data:
message += f'{str(index)}. {row[0]} - _{row[1]}_\n'
index += 1
return message
async def send_report(self, report, title):
message = self.format_report(title, report)
await self.application.bot.send_message(
chat_id=self.chat_id,
text=message
)
HTTP Сервер
Может показаться странным, но для того чтобы запускать те или иные события сервера я решил использовать простой HTTP сервер. Его события можно легко дергать cron-ом через curl да и в ответ на каждое действие можно выдавать какой то статус, что в каких то случаях может помочь отследить сбои по каждому частному случаю.
В качестве HTTP сервера был выбран AIOHTTP
pip install aiohttpСервер является связующим звеном обоих классов аналитики и бота, посему он будет держать оба объекта при себе. А так же конфигурацию проперти для выдачи.
class AnalyticsProperty:
def __init__(self, config):
self.title = config['title']
self.dimension = config['dimension']
self.limit = config['limit']
self.endpoint = config['endpoint']
class Server:
def __init__(self, config, telegram: TelegramBot, analytics: Analytics):
self.telegram = telegram
self.analytics = analytics
self.host = config['host']
self.port = config['port']
self.app = web.Application()
self.app.add_routes([
web.get('/analytics/{property}', self.handle_analytics)
])
self.properties = {}
def add_routes(self, properties):
for property in properties:
item = AnalyticsProperty(property)
self.properties[item.endpoint] = item
async def handle_analytics(self, request):
property_id = request.match_info['property']
if property_id in self.properties:
property = self.properties[property_id]
report = self.analytics.run_report(property.dimension, property.limit)
await self.telegram.send_report(report, title=property.title)
return web.Response(text='ok')
else:
raise web.HTTPNotFound()
def run(self):
web.run_app(self.app, host=self.host, port=self.port)По поводу конфигурации мы поговорим позже. Основное что здесь можно выделить, это инициализация сервера и определение метода который будет отвечать за забор аналитики и их отправку. Порядок использования будет такой.
Мы создаем объект Server с указанием бота и аналитики.
Добавляем в него нужны пути с информацией о параметрах которые сервер будет запрашивать и отправлять.
В результате, будет что то вроде
bot = TelegramBot(config['telegram'])
analytics = Analytics(config['analytics'])
server = Server(config['server'], bot, analytics)
server.add_routes(config['properties'])
server.run()Конфигурация
Для конфигурации я выбрал yaml-файл.
pip install pyyamlКонфигурация будет резделена на 4 основные части:
telegram - токен и id чата;
analytics - id проперти;
server - host и post для создания сервера;
properties - массив из аналитических данных:
title - заголовок измерения;
dimension - наш custom difinition;
limit - максимальное количество результатов;
endpoint - компонент пути по которому будет иницирована операция.
В результате получаем такой конфиг config.yaml который положим в папку data:
server:
host: 0.0.0.0 # Server host
port: 8080 # Server port
analytics:
property_id: <your_google_analytics_view_id>
telegram:
token: <your_telegram_bot_token>
chat_id: <your_telegram_chat_id>
properties:
-
title: 'Popular categories'
dimension: 'category_name'
limit: 15
endpoint: 'top_categories'Docker
Для запуска сервера было решено запускать его в докере и дергать тот или иной endpoint кроном раз в сутки. В качестве конфига был выбран вполне шаблонный Dockerfile:
FROM python:3.9-alpine
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY ["app.py", "analytics.py", "server.py", "telegram_bot.py", "./"]
ENV GOOGLE_APPLICATION_CREDENTIALS="/app/data/credentials.json"
EXPOSE 8080
ENTRYPOINT ["python", "app.py"]Для полноценной работы контейнер требует подключения папки data в /app/data где будет лежать файл для доступа к Google аналитке и наш конфиг.
Хочу заметить, что хорошо бы закрыть контейнер от внешнего интернета так как он не требует какой либо авторизации. Ну или же настроить cron не снаружи, а внутри контейнера и не пробрасывать порты наружу.
Cron
Стоит в кратце объяснить про запуск. Чтобы настроить запуск той или иной задачи в cron необходимо добавить его с помощью конфигурации файла который вызывается по команде
crontab -eТакой конфигурацией раз в сутки в 00:00 будет вызывается GET запрос на локальный адрес нашего сервера:
0 0 * * * curl localhost:8060/analytics/top_categories >/dev/null 2>&1Поиграться с различными конфигурациями можно здесь https://crontab.guru/ и найти наиболее удобный для себя
Заключение
Полный результат можно посмотреть в репозитории. Конечно же решение требует от себя доработок, например, рисование графиков или интеграцию с другими серверами для выдачи более детализированной информации. Но как прототип он показал себя очень хорошо.
