Ни для кого не секрет как важна аналитика. Она не только дает информацию о паттернах поведения пользователей, но и может помочь отследить частоту использования той или иной фичи которую вы могли недавно интегрировали. Однако, регулярное посещение сайта аналитики и протыкивание нужных эвентов может превратиться в утомительную рутину, которую можно избежать. Для того чтобы упростить задачу я набросал 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/ и найти наиболее удобный для себя
Заключение
Полный результат можно посмотреть в репозитории. Конечно же решение требует от себя доработок, например, рисование графиков или интеграцию с другими серверами для выдачи более детализированной информации. Но как прототип он показал себя очень хорошо.
ссылка на оригинал статьи https://habr.com/ru/articles/716538/
Добавить комментарий