
Yandex Metrika
- 54 installs
- 177 repo stars
- Updated May 10, 2026
- artwist-polyakov/polyakov-claude-skills
yandex-metrika is a Claude skill that queries the Yandex Metrika Reporting API v1 for traffic, conversion, UTM, ecommerce, and Yandex Direct ad-spend reports.
About
This skill queries the Yandex Metrika Reporting API v1 to produce traffic, conversion, UTM, and search-engine reports. A developer uses it to analyze site traffic sources, goal conversions, ecommerce revenue, and Yandex Direct ad costs. It is cache-first, defaults to unsampled accurate data with robot filtering, and exports full results to CSV.
- Pulls traffic, conversion, UTM, and search-engine reports from the Yandex Metrika Reporting API v1
- Cache-first with accuracy=1 and robot filtering for exact, unsampled numbers
- Covers ecommerce revenue, goal conversions, and Yandex Direct ad-spend reports
Yandex Metrika by the numbers
- 54 all-time installs (skills.sh)
- Ranked #1,304 of 1,879 Marketing & SEO skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
yandex-metrika capabilities & compatibility
Requires a Yandex Metrika OAuth token; the Metrika Reporting API is free within ~200 requests per 5 minutes.
- Capabilities
- data analysis · seo
- Use cases
- data analysis · seo · marketing
- Pricing
- Bring your own API key
What yandex-metrika says it does
Работа с Yandex Metrika Reporting API v1. Отчёты по трафику, конверсиям, UTM-меткам, поисковым системам.
**Точные данные** — accuracy=1 (без сэмплирования), фильтр isRobot по умолчанию.
npx skills add https://github.com/artwist-polyakov/polyakov-claude-skills --skill yandex-metrikaAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 54 |
|---|---|
| repo stars | ★ 177 |
| Last updated | May 10, 2026 |
| Repository | artwist-polyakov/polyakov-claude-skills ↗ |
What it does
Pull traffic, conversion, UTM, ecommerce, and ad-spend reports for a site from Yandex Metrika.
Who is it for?
Pulling accurate Yandex Metrika traffic, conversion, and ecommerce reports.
When should I use this skill?
You need Yandex Metrika reports on traffic, conversions, UTM sources, ecommerce revenue, or Yandex Direct costs.
What you get
Accurate, cached traffic, conversion, and revenue reports are returned and exported to CSV.
By the numbers
- 12 report scripts
- Reporting API limit of ~200 requests per 5 minutes
- accuracy=1 unsampled by default
Files
yandex-metrika
Работа с Yandex Metrika Reporting API v1. Отчёты по трафику, конверсиям, UTM-меткам, поисковым системам.
Config
Требуется YANDEX_METRIKA_TOKEN в config/.env. Инструкция: config/README.md.
Philosophy
1. Cache-first — конфигурационные данные (счётчики, цели, инфо) кешируются надолго. Отчёты кешируются по ключу counter+dates+params. Перед API-запросом всегда проверяем кеш. 2. Context window hygiene — stdout ограничен 30 строками. Полные данные в CSV/файл. Кеш доступен через grep/rg для поиска без загрузки в контекст. 3. Точные данные — accuracy=1 (без сэмплирования), фильтр isRobot по умолчанию. 4. Атрибуция — дефолт lastsign (последний значимый источник). Спрашиваем пользователя при первом запуске.
Workflow
STOP! Перед любым анализом:
1. Получи список счётчиков:
bash scripts/counters.sh2. Спроси пользователя (если счётчик не очевиден из контекста):
"О каком счётчике/сайте идёт речь?
Укажите ID, название или домен."Если пользователь назвал сайт/домен — ищи через --search:
bash scripts/counters.sh --search "metallik"Это grep по TSV (id + name + site), поэтому находит и по домену.
3. Получи инфо о счётчике и его цели:
bash scripts/counter_info.sh --counter <ID>
bash scripts/goals.sh --counter <ID>4. Спроси про конверсионные цели:
"Какие из этих целей являются конверсионными для вашего бизнеса?
[список целей из goals.sh]
Сохраню выбранные для будущих отчётов."5. Сохрани конфигурацию в cache/counter_<id>/config.json:
{
"attribution": "lastsign",
"conversion_goals": [
{"id": 12345, "name": "Заказ оформлен"},
{"id": 67890, "name": "Заявка отправлена"}
]
}6. Запускай отчёты по задаче пользователя.
Scripts
Общий паттерн вызова:
bash scripts/<script>.sh --counter <ID> --date1 YYYY-MM-DD [--date2 ...] [--group month] [--csv path]| Script | Description | Special params |
|---|---|---|
counters.sh | Список счётчиков | --search "query" |
goals.sh | Цели счётчика | — |
counter_info.sh | Метаданные счётчика | — |
traffic_summary.sh | Трафик по источникам | — |
conversions.sh | Достижение целей | --goals "ID,ID" / --all-goals; по умолчанию из config.json |
utm_report.sh | UTM-разбивка | — |
search_engines.sh | Поисковые системы (organic) | — |
ecommerce.sh | Покупки, выручка, средний чек | `--currency RUB\ |
direct_clients.sh | Логины Директа | — |
direct_costs.sh | Расходы Директа (ym:ad:*) | --direct-client-logins "login"; нет --group/--device/--source |
comparison.sh | Сравнение двух периодов | --date1a/--date2a/--date1b/--date2b; --dimension, --metrics |
Не все скрипты поддерживают все общие параметры — см. Special params.
Общие параметры отчётных скриптов
| Param | Required | Default | Values |
|---|---|---|---|
--counter | yes | - | ID счётчика |
--date1 | yes | - | YYYY-MM-DD |
--date2 | no | today | YYYY-MM-DD |
--group | no | - | day, week, month |
--device | no | all | desktop, mobile, tablet |
--source | no | all | organic, ad, referral, direct, social |
--attribution | no | lastsign | lastsign, last, first |
--limit | no | API default | число строк |
--csv | no | - | путь для экспорта |
--no-cache | no | - | пропустить кеш |
Кеш-стратегия
Кеш хранится в cache/:
counters.json+counters.tsv— все счётчикиcounter_<id>/info.json— метаданные (permanent)counter_<id>/goals.json+goals.tsv— целиcounter_<id>/config.json— атрибуция, конверсионные целиcounter_<id>/direct_clients.json— логины Директаcounter_<id>/reports/*.csv— результаты отчётов
Для поиска по кешу: grep "text" cache/counters.tsv или rg "text" cache/.
Расширенные сценарии
- Популярные поисковые запросы
- Произвольные отчёты и JSON-запросы (drilldown, metrika_get и др.)
- Справочник dimensions/metrics
- Сравнение периодов год-к-году
- Расходы Директа и PnL
- Ограничения API (bytime, scope mixing, drilldown CSV)
Лимиты API
- Reporting API: ~200 запросов / 5 минут (при превышении — ждите ~5 минут)
- Скрипты автоматически обрабатывают 429 (Retry-After ≤ 60s → retry, иначе fail с сообщением)
config/.env
cache/*
!cache/.gitkeep
YANDEX_METRIKA_TOKEN=your_oauth_token_here
Получение токена Yandex Metrika API
Шаг 1: Зарегистрируйте приложение
1. Перейдите на https://oauth.yandex.ru/client/new 2. Укажите название приложения (например, "Claude Metrika") 3. В разделе "Платформы" выберите "Веб-сервисы" 4. В "Доступы" добавьте: metrika:read (Яндекс.Метрика — чтение) 5. Сохраните и запишите client_id
Подробнее: https://yandex.ru/dev/id/doc/ru/register-client
Шаг 2: Получите OAuth токен
Откройте в браузере:
https://oauth.yandex.ru/authorize?response_type=token&client_id=ВАШ_CLIENT_IDПосле авторизации токен будет в URL:
https://oauth.yandex.ru/#access_token=ВАШТОКЕН&token_type=bearer&expires_in=31536000Скопируйте значение access_token.
Шаг 3: Настройте токен
cp config/.env.example config/.envВставьте токен:
YANDEX_METRIKA_TOKEN=ваш_токен_здесьПроверка
bash scripts/counters.shДолжен показать список ваших счётчиков.
Лимиты API
- Reporting API: ~200 запросов / 5 минут
- Management API: мягкие лимиты
Срок жизни токена
Токен действует 1 год. После истечения получите новый по той же ссылке.
Документация
- Metrika API: https://yandex.ru/dev/metrika/ru/
- Reporting API: https://yandex.ru/dev/metrika/ru/stat/
- OAuth: https://yandex.ru/dev/id/doc/ru/
API Reference: Dimensions & Metrics
Endpoints
| Endpoint | Description |
|---|---|
/stat/v1/data | Table report |
/stat/v1/data/bytime | Time-series report (use with group) |
/stat/v1/data/drilldown | Hierarchical drill-down |
/stat/v1/data/comparison | Segment comparison |
Append .csv for CSV format: /stat/v1/data.csv
Visit Metrics (prefix: ym:s:)
| Metric | Description |
|---|---|
ym:s:visits | Total visits |
ym:s:users | Unique visitors |
ym:s:pageviews | Page views |
ym:s:bounceRate | Bounce rate |
ym:s:pageDepth | Pages per visit |
ym:s:avgVisitDurationSeconds | Avg visit duration (sec) |
ym:s:crossDeviceUsers | Cross-device unique visitors |
Goal Metrics (replace <goal_id>)
| Metric | Description |
|---|---|
ym:s:goal<goal_id>visits | Visits with goal achieved |
ym:s:goal<goal_id>reaches | Total goal achievements |
ym:s:goal<goal_id>conversionRate | Conversion rate (visits) |
ym:s:goal<goal_id>userConversionRate | Conversion rate (users) |
ym:s:goal<goal_id>users | Users who achieved goal |
Traffic Source Dimensions (replace <attribution>)
Attribution values: lastsign, last, first
| Dimension | Description |
|---|---|
ym:s:<attr>TrafficSource | Traffic source type |
ym:s:<attr>SourceEngine | Detailed source (search engine name, etc.) |
ym:s:<attr>AdvEngine | Ad system |
ym:s:<attr>ReferalSource | Referral website |
ym:s:<attr>RecommendationSystem | Recommendation system |
ym:s:<attr>Messenger | Messenger |
UTM Dimensions (replace <attribution>)
| Dimension | Description |
|---|---|
ym:s:<attr>UTMSource | utm_source |
ym:s:<attr>UTMMedium | utm_medium |
ym:s:<attr>UTMCampaign | utm_campaign |
ym:s:<attr>UTMContent | utm_content |
ym:s:<attr>UTMTerm | utm_term |
Ecommerce Metrics (prefix: ym:s:)
| Metric | Description |
|---|---|
ym:s:ecommercePurchases | Total purchases |
Revenue metrics (replace <CUR> with RUB, USD, EUR, etc.):
| Metric | Description |
|---|---|
ym:s:ecommerce<CUR>ConvertedRevenue | Revenue in specified currency |
ym:s:ecommerce<CUR>ConvertedRevenuePerPurchase | Avg check in specified currency |
ym:s:ecommerce<CUR>ConvertedRevenuePerVisit | Revenue per visit in specified currency |
Pageview Metrics (prefix: ym:pv:)
IMPORTANT: ym:pv: metrics/dimensions CANNOT be mixed with ym:s: in one request.
| Metric / Dimension | Description |
|---|---|
ym:pv:pageviews | Page views |
ym:pv:users | Unique users |
ym:pv:URLPathLevel1 | URL path level 1 (usually just domain) |
ym:pv:URLPathLevel2..N | Deeper URL path levels |
Ad Cost Metrics (prefix: ym:ad:)
IMPORTANT: ym:ad: metrics require direct_client_logins parameter. Cannot mix with ym:s: or ym:pv:.
| Metric | Description |
|---|---|
ym:ad:visits | Visits from Direct ads |
ym:ad:clicks | Clicks in Direct |
ym:ad:RUBConvertedAdCost | Ad cost in RUB |
ym:ad:USDConvertedAdCost | Ad cost in USD |
ym:ad:EURConvertedAdCost | Ad cost in EUR |
Ad Cost Dimensions (prefix: ym:ad:)
| Dimension | Description |
|---|---|
ym:ad:date | Date |
ym:ad:directOrder | Direct campaign |
ym:ad:directBanner | Ad creative |
ym:ad:directBannerGroup | Ad group |
ym:ad:directPhraseOrCond | Keyword / condition |
ym:ad:directPlatformType | Platform type (search / network) |
ym:ad:directPlatform | Specific platform |
Device & Technology Dimensions
| Dimension | Description |
|---|---|
ym:s:deviceCategory | desktop / mobile / tablet |
ym:s:operatingSystem | OS |
ym:s:browser | Browser |
ym:s:screenResolution | Screen resolution |
Geography Dimensions
| Dimension | Description |
|---|---|
ym:s:regionCountry | Country |
ym:s:regionCity | City |
ym:s:regionArea | Region/area |
Common Parameters
| Parameter | Description | Default |
|---|---|---|
ids | Counter ID(s), comma-separated | required |
date1 | Start date (YYYY-MM-DD) | required |
date2 | End date (YYYY-MM-DD) | today |
metrics | Metrics, comma-separated | required |
dimensions | Dimensions, comma-separated | - |
filters | Filter expression | - |
accuracy | 0-1, where 1 = no sampling | 0.5 |
group | day / week / month (bytime only) | - |
limit | Max rows | 100 |
offset | Row offset for pagination | 1 |
sort | Sort field (prefix - for desc) | - |
Filter Syntax
ym:s:isRobot=='No'
ym:s:deviceCategory=='desktop'
ym:s:lastsignTrafficSource=='organic'
ym:s:regionCountry=='Россия' AND ym:s:deviceCategory=='mobile'Operators: ==, !=, =@ (contains), !@ (not contains), =~ (regex), !~ (not regex) Combine with AND, OR.
Known API Limitations
- Drilldown does not support CSV: requesting
/stat/v1/data/drilldown.csv(the.csvvariant of the drilldown endpoint) returns HTTP 406 "Unsupported format" (verified by test). Use/stat/v1/data/drilldown(JSON) viametrika_getinstead. - Bytime column limit:
/stat/v1/data/bytimereturns max ~7 unique dimension values as columns. The rest are silently dropped. Workaround: query/stat/v1/dataseparately per period instead of using bytime. - searchPhrase + startURL = empty: combining
lastsignSearchPhraseandstartURLdimensions returns 0 rows. Query them separately and correlate. - URL Path Levels:
startURLPathLevel1returns only the domain. Deeper levels require drilldown (which doesn't support CSV). UsestartURLwith=@filter for section analysis instead. - Pageview vs Visit scopes: cannot mix
ym:pv:andym:s:prefixes in one query. - Ad cost scope:
ym:ad:*is a separate scope requiringdirect_client_logins. Cannot mix withym:s:orym:pv:in one query. Get logins via/management/v1/clients?counters=<id>. - Search queries: Yandex hides ~70% of real search phrases. Only ~30% are available via API.
Произвольные отчёты
Как построить произвольный отчёт с любыми dimensions и metrics.
Принцип
Все скрипты используют common.sh для API-вызовов. Можно легко собрать запрос вручную:
. scripts/common.sh
load_config
metrika_get_csv "/stat/v1/data.csv" "output.csv" \
--data-urlencode "ids=COUNTER_ID" \
--data-urlencode "date1=2025-01-01" \
--data-urlencode "date2=2025-12-31" \
--data-urlencode "metrics=METRICS" \
--data-urlencode "dimensions=DIMENSIONS" \
--data-urlencode "filters=FILTERS" \
--data-urlencode "accuracy=1" \
--data-urlencode "sort=-SORT_FIELD" \
--data-urlencode "limit=100"Примеры
Страницы входа с метриками
dimensions=ym:s:startURL
metrics=ym:s:visits,ym:s:bounceRate,ym:s:avgVisitDurationSeconds
sort=-ym:s:visitsГеография: города
dimensions=ym:s:regionCity
metrics=ym:s:visits,ym:s:users,ym:s:bounceRate
filters=ym:s:regionCountry=='Россия' AND ym:s:isRobot=='No'Устройства: ОС + браузер
dimensions=ym:s:operatingSystem,ym:s:browser
metrics=ym:s:visits,ym:s:usersРеферальные источники
dimensions=ym:s:lastsignReferalSource
metrics=ym:s:visits,ym:s:users,ym:s:bounceRate
filters=ym:s:lastsignTrafficSource=='referral' AND ym:s:isRobot=='No'Рекламные системы
dimensions=ym:s:lastsignAdvEngine
metrics=ym:s:visits,ym:s:users,ym:s:goal<ID>conversionRate
filters=ym:s:lastsignTrafficSource=='ad' AND ym:s:isRobot=='No'JSON-запросы (drilldown и др.)
Для endpoints, не поддерживающих CSV (drilldown), используйте metrika_get напрямую:
. scripts/common.sh
load_config
RESULT=$(metrika_get "/stat/v1/data/drilldown" \
--data-urlencode "ids=COUNTER_ID" \
--data-urlencode "date1=2025-01-01" \
--data-urlencode "date2=2025-12-31" \
--data-urlencode "metrics=ym:s:visits" \
--data-urlencode "dimensions=ym:s:startURLPathLevel1" \
--data-urlencode "accuracy=1")
echo "$RESULT"Результат — JSON. Парсите через grep/sed или сохраняйте в файл.
Правила
- Нельзя смешивать visit (ym:s:) и pageview (ym:pv:) префиксы в одном запросе
- Максимум ~10 dimensions и ~20 metrics в одном запросе
- Для больших выгрузок используйте
limit+offsetдля пагинации - bytime возвращает максимум ~7 уникальных значений dimension в колонках — для полного анализа используйте
/stat/v1/dataс фильтрами по каждому периоду отдельно - Полный справочник: API_REFERENCE.md
Расходы Яндекс Директа и PnL-анализ
Принцип работы
Метрики расходов (ym:ad:*) требуют параметр direct_client_logins — логин(ы) Директа, привязанные к счётчику. Без этого параметра API возвращает 403.
Как получить логины
Management API endpoint:
GET /management/v1/clients?counters=<counterId>Ответ содержит:
chief_login— логин клиента Директаall_clients_accessible_to_user—true/false, доступны ли все клиенты
Скрипт direct_clients.sh автоматизирует этот вызов и кеширует результат.
bash scripts/direct_clients.sh --counter 12345Важно: endpoint /management/v1/clients помечен как deprecated в OpenAPI-спецификации. Если он перестанет работать или вернёт пустой ответ, укажите логины вручную через --direct-client-logins:
bash scripts/direct_costs.sh --counter 12345 --date1 2025-01-01 \
--direct-client-logins "my-direct-login"Логин Директа можно найти в интерфейсе Яндекс Директа (Настройки → Логин) или спросить у владельца рекламного кабинета.
Быстрый старт
# 1. Получить логины Директа (кешируется)
bash scripts/direct_clients.sh --counter 12345
# 2. Отчёт по расходам
bash scripts/direct_costs.sh \
--counter 12345 \
--date1 2025-01-01 \
--date2 2025-12-31
# Экспорт в CSV для агрегации по неделям/месяцам
bash scripts/direct_costs.sh \
--counter 12345 \
--date1 2025-01-01 \
--csv /tmp/direct_costs.csvdirect_costs.sh автоматически берёт логины из кеша. Если кеша нет — вызывает direct_clients.sh. Данные всегда по дням (ym:ad:date); агрегация по неделям/месяцам — в CSV/Excel.
Доступные ym:ad:* метрики
| Metric | Description |
|---|---|
ym:ad:visits | Визиты с рекламы Директа |
ym:ad:clicks | Клики в Директе |
ym:ad:RUBConvertedAdCost | Расход в рублях |
ym:ad:USDConvertedAdCost | Расход в долларах |
ym:ad:EURConvertedAdCost | Расход в евро |
Доступные ym:ad:* dimensions
| Dimension | Description |
|---|---|
ym:ad:date | Дата |
ym:ad:directOrder | Рекламная кампания Директа |
ym:ad:directBanner | Объявление |
ym:ad:directBannerGroup | Группа объявлений |
ym:ad:directPhraseOrCond | Ключевая фраза / условие |
ym:ad:directPlatformType | Тип площадки (поиск / сети) |
ym:ad:directPlatform | Конкретная площадка |
PnL-анализ: расходы vs выручка
Для полного PnL нужны два отдельных отчёта (нельзя смешивать ym:ad:* и ym:s:* в одном запросе):
1. Расходы Директа
bash scripts/direct_costs.sh \
--counter 12345 \
--date1 2025-01-01 \
--date2 2025-01-31 \
--csv /tmp/costs.csv2. Выручка e-commerce (трафик из ad)
bash scripts/ecommerce.sh \
--counter 12345 \
--date1 2025-01-01 \
--date2 2025-01-31 \
--source ad \
--csv /tmp/revenue.csv3. Сопоставление
Джойн по дате или вручную: сравните RUBConvertedAdCost с ecommerceRUBConvertedRevenue за тот же период.
Важно: клики Директа (ym:ad:clicks) и визиты Метрики (ym:ad:visits) — разные числа. Один клик может не привести к визиту (bounce до загрузки счётчика), а один визит может быть результатом нескольких кликов.
Нюансы
Несколько логинов Директа
Один счётчик может быть привязан к нескольким аккаунтам Директа. direct_clients.sh получает все логины, direct_costs.sh передаёт их через запятую в direct_client_logins.
Неполный доступ
Если all_clients_accessible_to_user: false, токен не имеет доступа ко всем привязанным клиентам. Данные по расходам будут неполными. Скрипты выводят предупреждение в этом случае.
Scope ym:ad:*
Метрики ym:ad:* — отдельный scope. Нельзя смешивать с ym:s:* (visit) или ym:pv:* (pageview) в одном запросе.
Фильтры
Для ym:ad:* запросов не применяется стандартный фильтр ym:s:isRobot=='No' — это другой scope. Фильтры по устройству/источнику тоже не работают в ad scope.
Сравнение периодов год-к-году
Как анализировать долгосрочную динамику и сравнивать периоды.
Данные доступны с 2009 года
Metrika API хранит данные с 2009 года (ym:s:visits). Можно запрашивать за любой период.
Подход 1: bytime с group=month
Для длинной динамики (год+) — запрос по месяцам через /stat/v1/data/bytime.csv:
bash scripts/traffic_summary.sh \
--counter 12345 \
--date1 2023-01-01 \
--date2 2025-12-31 \
--group monthРезультат: CSV с колонками по месяцам, удобно для графиков и сравнений.
Подход 2: два отдельных запроса
Для прямого сравнения "этот год vs прошлый":
# Этот год
bash scripts/traffic_summary.sh \
--counter 12345 \
--date1 2025-01-01 \
--date2 2025-12-31 \
--csv traffic_2025.csv
# Прошлый год
bash scripts/traffic_summary.sh \
--counter 12345 \
--date1 2024-01-01 \
--date2 2024-12-31 \
--csv traffic_2024.csvДалее агент может проанализировать оба CSV и вычислить delta.
Подход 3: Comparison API
Metrika имеет эндпойнт /stat/v1/data/comparison.csv для автоматического сравнения двух сегментов.
Пример использования через common.sh:
. scripts/common.sh
load_config
metrika_get_csv "/stat/v1/data/comparison.csv" "comparison.csv" \
--data-urlencode "ids=12345" \
--data-urlencode "date1_a=2025-01-01" \
--data-urlencode "date2_a=2025-06-30" \
--data-urlencode "date1_b=2024-01-01" \
--data-urlencode "date2_b=2024-06-30" \
--data-urlencode "metrics=ym:s:visits,ym:s:users,ym:s:bounceRate" \
--data-urlencode "dimensions=ym:s:lastSignTrafficSource" \
--data-urlencode "accuracy=1" \
--data-urlencode "filters=ym:s:isRobot=='No'"Рекомендации
- group=month — оптимален для 1-3 лет, компактный CSV
- group=week — для анализа сезонности в рамках года
- group=day — для детального анализа коротких периодов (до 3 месяцев)
- При больших периодах CSV может быть объёмным — используйте
--csvдля экспорта в файл
Популярные поисковые запросы
Отчёт по поисковым запросам, с которыми пользователи приходят на сайт.
Важно
Данные по поисковым запросам доступны только из Яндекса. Google не передаёт ключевые слова в Метрику.
Dimension
ym:s:lastSignSearchPhraseПример запроса
# Через API напрямую (curl)
curl -s -G "https://api-metrika.yandex.net/stat/v1/data.csv" \
-H "Authorization: OAuth $YANDEX_METRIKA_TOKEN" \
--data-urlencode "ids=COUNTER_ID" \
--data-urlencode "date1=2025-01-01" \
--data-urlencode "date2=2025-12-31" \
--data-urlencode "metrics=ym:s:visits,ym:s:users,ym:s:bounceRate" \
--data-urlencode "dimensions=ym:s:lastSignSearchPhrase" \
--data-urlencode "filters=ym:s:isRobot=='No' AND ym:s:lastSignSearchPhrase!=''" \
--data-urlencode "accuracy=1" \
--data-urlencode "sort=-ym:s:visits" \
--data-urlencode "limit=100" \
-o search_queries.csvИспользование с common.sh
. scripts/common.sh
load_config
metrika_get_csv "/stat/v1/data.csv" "search_queries.csv" \
--data-urlencode "ids=$COUNTER" \
--data-urlencode "date1=$DATE1" \
--data-urlencode "date2=$DATE2" \
--data-urlencode "metrics=ym:s:visits,ym:s:users,ym:s:bounceRate" \
--data-urlencode "dimensions=ym:s:lastSignSearchPhrase" \
--data-urlencode "filters=ym:s:isRobot=='No' AND ym:s:lastSignSearchPhrase!=''" \
--data-urlencode "accuracy=1" \
--data-urlencode "sort=-ym:s:visits" \
--data-urlencode "limit=200"Фильтрация
Можно фильтровать по содержимому запроса:
filters=ym:s:lastSignSearchPhrase=@'купить' AND ym:s:isRobot=='No'Динамика по времени
Для отслеживания изменений популярности запросов используйте /stat/v1/data/bytime.csv с group=month.
#!/bin/sh
# Common functions for Yandex Metrika API skill
# POSIX sh compatible — no bashisms
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
CONFIG_FILE="$SCRIPT_DIR/../config/.env"
CACHE_DIR="$SCRIPT_DIR/../cache"
METRIKA_API="https://api-metrika.yandex.net"
# Ensure tmp directory exists
METRIKA_TMPDIR="${TMPDIR:-/tmp}"
mkdir -p "$METRIKA_TMPDIR"
# --------------- Config ---------------
load_config() {
if [ -f "$CONFIG_FILE" ]; then
# shellcheck disable=SC1090
. "$CONFIG_FILE"
fi
if [ -z "$YANDEX_METRIKA_TOKEN" ]; then
echo "Error: YANDEX_METRIKA_TOKEN not found." >&2
echo "Set in config/.env or environment. See config/README.md." >&2
exit 1
fi
}
# --------------- Cache helpers ---------------
# cache_dir_for_counter <counter_id>
cache_dir_for_counter() {
_cdc_dir="$CACHE_DIR/counter_$1"
mkdir -p "$_cdc_dir/reports"
echo "$_cdc_dir"
}
# cache_key <params_string> — deterministic hash via cksum
cache_key() {
printf '%s' "$1" | cksum | awk '{print $1}'
}
# cache_get <file_path> — prints cached file if exists and not empty
# Returns 0 if cache hit, 1 if miss
cache_get() {
if [ -f "$1" ] && [ -s "$1" ]; then
cat "$1"
return 0
fi
return 1
}
# cache_put <file_path> — reads stdin, writes to file
cache_put() {
mkdir -p "$(dirname "$1")"
cat > "$1"
}
# --------------- API helpers ---------------
# metrika_get <path> [extra_curl_args...]
# Makes authenticated GET request, returns body. Headers saved to temp file.
metrika_get() {
_mg_path="$1"
shift
_mg_url="${METRIKA_API}${_mg_path}"
_mg_headers="${METRIKA_TMPDIR}/metrika_headers_$$.txt"
_mg_body=$(curl -s -G -D "$_mg_headers" \
-H "Authorization: OAuth $YANDEX_METRIKA_TOKEN" \
-H "Accept-Charset: utf-8" \
"$@" \
"$_mg_url") || {
rm -f "$_mg_headers"
echo "Error: curl failed for $_mg_url" >&2
return 1
}
# Check for 429
_mg_status=$(head -1 "$_mg_headers" | grep -o '[0-9][0-9][0-9]' | head -1)
if [ "$_mg_status" = "429" ]; then
_mg_retry=$(grep -i 'Retry-After' "$_mg_headers" | sed 's/[^0-9]//g' | head -1)
rm -f "$_mg_headers"
# Only retry once (guard via env var)
if [ -z "${_METRIKA_RETRY_DONE:-}" ] && [ -n "$_mg_retry" ] && [ "$_mg_retry" -le 60 ] 2>/dev/null; then
_mg_jitter=$(awk 'BEGIN{srand(); printf "%d", rand()*3}')
_mg_wait=$(( _mg_retry + _mg_jitter ))
echo "Rate limited. Waiting ${_mg_wait}s (Retry-After: ${_mg_retry}s)..." >&2
sleep "$_mg_wait"
_METRIKA_RETRY_DONE=1 metrika_get "$_mg_path" "$@"
return $?
else
echo "Error: Rate limit exceeded (429). Metrika quota: ~200 req/5min." >&2
echo "Wait ~5 minutes and retry." >&2
return 1
fi
fi
# Check for HTTP errors
if [ -n "$_mg_status" ] && [ "$_mg_status" -ge 400 ] 2>/dev/null; then
rm -f "$_mg_headers"
echo "Error: HTTP $_mg_status from $_mg_url" >&2
echo "$_mg_body" >&2
return 1
fi
rm -f "$_mg_headers"
printf '%s' "$_mg_body"
}
# metrika_get_csv <path> <output_file> [extra_curl_args...]
# Downloads CSV report to file. Returns 0 on success.
metrika_get_csv() {
_mgc_path="$1"
_mgc_output="$2"
shift 2
_mgc_url="${METRIKA_API}${_mgc_path}"
_mgc_headers="${METRIKA_TMPDIR}/metrika_headers_$$.txt"
curl -s -G -D "$_mgc_headers" \
-H "Authorization: OAuth $YANDEX_METRIKA_TOKEN" \
-H "Accept-Charset: utf-8" \
-o "$_mgc_output" \
"$@" \
"$_mgc_url" || {
rm -f "$_mgc_headers"
echo "Error: curl failed for $_mgc_url" >&2
return 1
}
_mgc_status=$(head -1 "$_mgc_headers" | grep -o '[0-9][0-9][0-9]' | head -1)
if [ "$_mgc_status" = "429" ]; then
_mgc_retry=$(grep -i 'Retry-After' "$_mgc_headers" | sed 's/[^0-9]//g' | head -1)
rm -f "$_mgc_headers"
if [ -z "${_METRIKA_RETRY_DONE:-}" ] && [ -n "$_mgc_retry" ] && [ "$_mgc_retry" -le 60 ] 2>/dev/null; then
_mgc_jitter=$(awk 'BEGIN{srand(); printf "%d", rand()*3}')
_mgc_wait=$(( _mgc_retry + _mgc_jitter ))
echo "Rate limited. Waiting ${_mgc_wait}s..." >&2
sleep "$_mgc_wait"
_METRIKA_RETRY_DONE=1 metrika_get_csv "$_mgc_path" "$_mgc_output" "$@"
return $?
else
echo "Error: Rate limit exceeded (429). Wait ~5 minutes." >&2
return 1
fi
fi
if [ -n "$_mgc_status" ] && [ "$_mgc_status" -ge 400 ] 2>/dev/null; then
rm -f "$_mgc_headers"
echo "Error: HTTP $_mgc_status" >&2
cat "$_mgc_output" >&2
return 1
fi
rm -f "$_mgc_headers"
return 0
}
# metrika_mgmt_get <path> [extra_curl_args...]
# Management API with simple backoff (2/4/8s) for 429.
metrika_mgmt_get() {
_mmg_path="$1"
shift
_mmg_attempt=0
_mmg_max=3
_mmg_delay=2
while [ "$_mmg_attempt" -lt "$_mmg_max" ]; do
_mmg_result=$(metrika_get "$_mmg_path" "$@") && {
printf '%s' "$_mmg_result"
return 0
}
_mmg_attempt=$(( _mmg_attempt + 1 ))
if [ "$_mmg_attempt" -lt "$_mmg_max" ]; then
echo "Management API retry ${_mmg_attempt}/${_mmg_max}, waiting ${_mmg_delay}s..." >&2
sleep "$_mmg_delay"
_mmg_delay=$(( _mmg_delay * 2 ))
fi
done
return 1
}
# --------------- Filter/param builders ---------------
# build_filters <base_filter> [device] [source] [attribution]
# Combines filters with AND
build_filters() {
_bf_result="${1:-ym:s:isRobot=='No'}"
_bf_device="$2"
_bf_source="$3"
_bf_attr="${4:-lastsign}"
if [ -n "$_bf_device" ] && [ "$_bf_device" != "all" ]; then
case "$_bf_device" in
desktop) _bf_result="${_bf_result} AND ym:s:deviceCategory=='desktop'" ;;
mobile) _bf_result="${_bf_result} AND ym:s:deviceCategory=='mobile'" ;;
tablet) _bf_result="${_bf_result} AND ym:s:deviceCategory=='tablet'" ;;
esac
fi
if [ -n "$_bf_source" ] && [ "$_bf_source" != "all" ]; then
case "$_bf_source" in
organic) _bf_result="${_bf_result} AND ym:s:${_bf_attr}TrafficSource=='organic'" ;;
ad) _bf_result="${_bf_result} AND ym:s:${_bf_attr}TrafficSource=='ad'" ;;
referral) _bf_result="${_bf_result} AND ym:s:${_bf_attr}TrafficSource=='referral'" ;;
direct) _bf_result="${_bf_result} AND ym:s:${_bf_attr}TrafficSource=='direct'" ;;
social) _bf_result="${_bf_result} AND ym:s:${_bf_attr}TrafficSource=='social'" ;;
esac
fi
echo "$_bf_result"
}
# --------------- Output helpers ---------------
# print_csv_head <file> [n_lines]
# Prints first N lines of CSV (default 30) with line numbers
print_csv_head() {
_pch_file="$1"
_pch_n="${2:-30}"
if [ -f "$_pch_file" ]; then
head -n "$_pch_n" "$_pch_file"
_pch_total=$(wc -l < "$_pch_file" | tr -d ' ')
if [ "$_pch_total" -gt "$_pch_n" ]; then
echo "... ($(( _pch_total - _pch_n )) more rows, full data in: $_pch_file)"
fi
fi
}
# --------------- JSON minimal helpers (management API only) ---------------
# json_extract_field <json_string> <field_name>
# Extracts value of a simple key:value pair (not nested)
json_extract_field() {
echo "$1" | grep -o "\"$2\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | head -1 | sed 's/.*:[[:space:]]*"//;s/"$//'
}
# json_extract_number <json_string> <field_name>
json_extract_number() {
echo "$1" | grep -o "\"$2\"[[:space:]]*:[[:space:]]*[0-9]*" | head -1 | sed 's/.*:[[:space:]]*//'
}
# --------------- Date helpers ---------------
# date_is_today <YYYY-MM-DD> — returns 0 if date equals today
date_is_today() {
[ "$1" = "$(date +%Y-%m-%d)" ]
}
# --------------- Common param parsing ---------------
# parse_common_params "$@"
# Sets variables: COUNTER, DATE1, DATE2, GROUP, DEVICE, SOURCE, ATTRIBUTION, FILTERS, LIMIT, CSV_OUT, NO_CACHE
parse_common_params() {
COUNTER=""
DATE1=""
DATE2=""
GROUP=""
DEVICE=""
SOURCE=""
ATTRIBUTION="lastsign"
FILTERS=""
LIMIT=""
CSV_OUT=""
NO_CACHE=""
while [ $# -gt 0 ]; do
case "$1" in
--counter) COUNTER="$2"; shift 2 ;;
--date1) DATE1="$2"; shift 2 ;;
--date2) DATE2="$2"; shift 2 ;;
--group) GROUP="$2"; shift 2 ;;
--device) DEVICE="$2"; shift 2 ;;
--source) SOURCE="$2"; shift 2 ;;
--attribution) ATTRIBUTION="$2"; shift 2 ;;
--filters) FILTERS="$2"; shift 2 ;;
--limit) LIMIT="$2"; shift 2 ;;
--csv) CSV_OUT="$2"; shift 2 ;;
--no-cache) NO_CACHE="1"; shift ;;
*) shift ;;
esac
done
# Default date2 to today
if [ -z "$DATE2" ]; then
DATE2=$(date +%Y-%m-%d)
fi
# Build combined filters
FILTERS=$(build_filters "${FILTERS:-ym:s:isRobot=='No'}" "$DEVICE" "$SOURCE" "$ATTRIBUTION")
}
# require_counter — exits if COUNTER not set
require_counter() {
if [ -z "$COUNTER" ]; then
echo "Error: --counter <ID> is required." >&2
exit 1
fi
}
# require_dates — exits if DATE1 not set
require_dates() {
if [ -z "$DATE1" ]; then
echo "Error: --date1 YYYY-MM-DD is required." >&2
exit 1
fi
}
#!/bin/sh
# Period comparison report via /stat/v1/data/comparison.csv
# Usage: comparison.sh --counter <ID> \
# --date1a YYYY-MM-DD --date2a YYYY-MM-DD \
# --date1b YYYY-MM-DD --date2b YYYY-MM-DD \
# [--dimension <dim>] [--metrics <metrics>]
# [--device ...] [--source ...] [--attribution lastsign|last|first]
# [--limit N] [--csv path] [--no-cache]
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/common.sh"
load_config
# Custom param parsing (comparison has date1a/date2a/date1b/date2b instead of date1/date2)
COUNTER=""
DATE1A=""
DATE2A=""
DATE1B=""
DATE2B=""
DIMENSION=""
COMP_METRICS=""
DEVICE=""
SOURCE=""
ATTRIBUTION="lastsign"
FILTERS=""
LIMIT=""
CSV_OUT=""
NO_CACHE=""
while [ $# -gt 0 ]; do
case "$1" in
--counter) COUNTER="$2"; shift 2 ;;
--date1a) DATE1A="$2"; shift 2 ;;
--date2a) DATE2A="$2"; shift 2 ;;
--date1b) DATE1B="$2"; shift 2 ;;
--date2b) DATE2B="$2"; shift 2 ;;
--dimension) DIMENSION="$2"; shift 2 ;;
--metrics) COMP_METRICS="$2"; shift 2 ;;
--device) DEVICE="$2"; shift 2 ;;
--source) SOURCE="$2"; shift 2 ;;
--attribution) ATTRIBUTION="$2"; shift 2 ;;
--filters) FILTERS="$2"; shift 2 ;;
--limit) LIMIT="$2"; shift 2 ;;
--csv) CSV_OUT="$2"; shift 2 ;;
--no-cache) NO_CACHE="1"; shift ;;
*) shift ;;
esac
done
FILTERS=$(build_filters "${FILTERS:-ym:s:isRobot=='No'}" "$DEVICE" "$SOURCE" "$ATTRIBUTION")
if [ -z "$COUNTER" ]; then
echo "Error: --counter <ID> is required." >&2
exit 1
fi
if [ -z "$DATE1A" ] || [ -z "$DATE2A" ] || [ -z "$DATE1B" ] || [ -z "$DATE2B" ]; then
echo "Error: --date1a, --date2a, --date1b, --date2b are all required." >&2
echo "Usage: comparison.sh --counter ID --date1a ... --date2a ... --date1b ... --date2b ..." >&2
exit 1
fi
# Defaults
DIMENSION="${DIMENSION:-ym:s:${ATTRIBUTION}TrafficSource}"
COMP_METRICS="${COMP_METRICS:-ym:s:visits,ym:s:users,ym:s:bounceRate}"
# Cache key
_params_str="comparison_${COUNTER}_${DATE1A}_${DATE2A}_${DATE1B}_${DATE2B}_${DIMENSION}_${COMP_METRICS}_${DEVICE}_${SOURCE}_${ATTRIBUTION}_${LIMIT}"
_hash=$(cache_key "$_params_str")
COUNTER_DIR=$(cache_dir_for_counter "$COUNTER")
CACHE_FILE="$COUNTER_DIR/reports/comparison_${DATE1A}_${DATE2B}_${_hash}.csv"
# Skip cache if any period includes today
if date_is_today "$DATE2A" || date_is_today "$DATE2B"; then
NO_CACHE="1"
fi
# Check cache
if [ -z "$NO_CACHE" ] && [ -f "$CACHE_FILE" ] && [ -s "$CACHE_FILE" ]; then
echo "Comparison: ($DATE1A — $DATE2A) vs ($DATE1B — $DATE2B):"
print_csv_head "$CACHE_FILE" 30
[ -n "$CSV_OUT" ] && cp "$CACHE_FILE" "$CSV_OUT" && echo "Copied to: $CSV_OUT"
exit 0
fi
echo "Fetching comparison for counter $COUNTER..." >&2
echo " Period A: $DATE1A — $DATE2A" >&2
echo " Period B: $DATE1B — $DATE2B" >&2
TMPFILE="${METRIKA_TMPDIR}/metrika_comparison_$$.csv"
trap 'rm -f "$TMPFILE"' EXIT
metrika_get_csv "/stat/v1/data/comparison.csv" "$TMPFILE" \
--data-urlencode "ids=$COUNTER" \
--data-urlencode "date1_a=$DATE1A" \
--data-urlencode "date2_a=$DATE2A" \
--data-urlencode "date1_b=$DATE1B" \
--data-urlencode "date2_b=$DATE2B" \
--data-urlencode "metrics=$COMP_METRICS" \
--data-urlencode "dimensions=$DIMENSION" \
--data-urlencode "accuracy=1" \
--data-urlencode "filters=$FILTERS" \
${LIMIT:+--data-urlencode "limit=$LIMIT"}
cp "$TMPFILE" "$CACHE_FILE"
echo "Comparison: ($DATE1A — $DATE2A) vs ($DATE1B — $DATE2B):"
print_csv_head "$CACHE_FILE" 30
if [ -n "$CSV_OUT" ]; then
cp "$CACHE_FILE" "$CSV_OUT"
echo "Exported to: $CSV_OUT"
fi
#!/bin/sh
# Goal conversion report
# Usage: conversions.sh --counter <ID> --date1 YYYY-MM-DD [--date2 ...] [--group day|week|month]
# [--goals 123,456] [--all-goals] [--device ...] [--source ...] [--attribution ...]
# [--limit N] [--csv path] [--no-cache]
#
# By default shows only conversion_goals from cache/counter_<id>/config.json.
# Use --all-goals to show all goals, or --goals to specify goal IDs manually.
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/common.sh"
load_config
parse_common_params "$@"
require_counter
require_dates
GOALS=""
ALL_GOALS=""
# Re-parse for goals-specific params (parse_common_params already consumed "$@",
# but it passes unknown args through, so we re-scan the original args)
_prev=""
for _arg in "$@"; do
if [ "$_prev" = "--goals" ]; then
GOALS="$_arg"
_prev=""
continue
fi
case "$_arg" in
--goals) _prev="--goals" ;;
--all-goals) ALL_GOALS="1" ;;
*) _prev="" ;;
esac
done
ATTRIBUTION="${ATTRIBUTION:-lastsign}"
COUNTER_DIR=$(cache_dir_for_counter "$COUNTER")
CONFIG_JSON="$COUNTER_DIR/config.json"
# Determine goal IDs
if [ -n "$GOALS" ]; then
# Manual override
GOAL_IDS="$GOALS"
elif [ -n "$ALL_GOALS" ]; then
# All goals from cache
if [ -f "$COUNTER_DIR/goals.tsv" ]; then
GOAL_IDS=$(cut -f1 "$COUNTER_DIR/goals.tsv" | tr '\n' ',' | sed 's/,$//')
else
echo "Error: No cached goals. Run: goals.sh --counter $COUNTER" >&2
exit 1
fi
else
# Default: conversion goals from config
if [ -f "$CONFIG_JSON" ] && grep -q "conversion_goals" "$CONFIG_JSON" 2>/dev/null; then
GOAL_IDS=$(grep -o '"id"[[:space:]]*:[[:space:]]*[0-9]*' "$CONFIG_JSON" | sed 's/.*:[[:space:]]*//' | tr '\n' ',' | sed 's/,$//')
else
echo "Error: No conversion goals configured for counter $COUNTER." >&2
echo "Run goals.sh --counter $COUNTER to see available goals," >&2
echo "then save conversion goals to $CONFIG_JSON." >&2
echo "Or use --all-goals or --goals <ids>." >&2
exit 1
fi
fi
# Build metrics string with goal IDs
# For each goal: visits, conversionRate, reaches
_metrics=""
_IFS="$IFS"
IFS=","
for _gid in $GOAL_IDS; do
_gid=$(echo "$_gid" | tr -d ' ')
[ -z "$_gid" ] && continue
if [ -n "$_metrics" ]; then
_metrics="${_metrics},"
fi
_metrics="${_metrics}ym:s:goal${_gid}visits,ym:s:goal${_gid}reaches,ym:s:goal${_gid}conversionRate"
done
IFS="$_IFS"
if [ -z "$_metrics" ]; then
echo "Error: No valid goal IDs found." >&2
exit 1
fi
DIMENSIONS="ym:s:${ATTRIBUTION}TrafficSource"
# Cache key
_params_str="conv_${COUNTER}_${DATE1}_${DATE2}_${GROUP}_${GOAL_IDS}_${DEVICE}_${SOURCE}_${ATTRIBUTION}"
_hash=$(cache_key "$_params_str")
CACHE_FILE="$COUNTER_DIR/reports/conversions_${DATE1}_${DATE2}_${_hash}.csv"
# Skip cache if date2 is today (data still accumulating)
if date_is_today "$DATE2"; then
NO_CACHE="1"
fi
# Check cache
if [ -z "$NO_CACHE" ] && [ -f "$CACHE_FILE" ] && [ -s "$CACHE_FILE" ]; then
echo "Conversions for counter $COUNTER ($DATE1 — $DATE2), goals: $GOAL_IDS"
print_csv_head "$CACHE_FILE" 30
[ -n "$CSV_OUT" ] && cp "$CACHE_FILE" "$CSV_OUT" && echo "Copied to: $CSV_OUT"
exit 0
fi
# Build API path
if [ -n "$GROUP" ]; then
API_PATH="/stat/v1/data/bytime.csv"
else
API_PATH="/stat/v1/data.csv"
fi
echo "Fetching conversions for counter $COUNTER, goals: $GOAL_IDS..." >&2
TMPFILE="${METRIKA_TMPDIR}/metrika_conv_$$.csv"
trap 'rm -f "$TMPFILE"' EXIT
metrika_get_csv "$API_PATH" "$TMPFILE" \
--data-urlencode "ids=$COUNTER" \
--data-urlencode "date1=$DATE1" \
--data-urlencode "date2=$DATE2" \
--data-urlencode "metrics=$_metrics" \
--data-urlencode "dimensions=$DIMENSIONS" \
--data-urlencode "accuracy=1" \
--data-urlencode "filters=$FILTERS" \
${GROUP:+--data-urlencode "group=$GROUP"} \
${LIMIT:+--data-urlencode "limit=$LIMIT"}
cp "$TMPFILE" "$CACHE_FILE"
echo "Conversions for counter $COUNTER ($DATE1 — $DATE2), goals: $GOAL_IDS"
print_csv_head "$CACHE_FILE" 30
if [ -n "$CSV_OUT" ]; then
cp "$CACHE_FILE" "$CSV_OUT"
echo "Exported to: $CSV_OUT"
fi
#!/bin/sh
# Get counter metadata (name, site, create_time) with permanent cache
# Usage: counter_info.sh --counter <ID> [--no-cache]
# Also stores/reads conversion_goals in config.json
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/common.sh"
load_config
COUNTER=""
NO_CACHE=""
while [ $# -gt 0 ]; do
case "$1" in
--counter) COUNTER="$2"; shift 2 ;;
--no-cache) NO_CACHE="1"; shift ;;
*) shift ;;
esac
done
if [ -z "$COUNTER" ]; then
echo "Error: --counter <ID> is required." >&2
exit 1
fi
COUNTER_DIR=$(cache_dir_for_counter "$COUNTER")
CACHE_JSON="$COUNTER_DIR/info.json"
CONFIG_JSON="$COUNTER_DIR/config.json"
# Try cache first (permanent — counter metadata rarely changes)
if [ -z "$NO_CACHE" ] && [ -f "$CACHE_JSON" ] && [ -s "$CACHE_JSON" ]; then
_info=$(cat "$CACHE_JSON")
else
echo "Fetching counter $COUNTER info..." >&2
TMPFILE="${METRIKA_TMPDIR}/metrika_info_$$.json"
trap 'rm -f "$TMPFILE"' EXIT
metrika_mgmt_get "/management/v1/counter/$COUNTER" > "$TMPFILE"
cp "$TMPFILE" "$CACHE_JSON"
_info=$(cat "$TMPFILE")
fi
# Extract fields
_name=$(json_extract_field "$_info" "name")
_site=$(json_extract_field "$_info" "site2" || true)
[ -z "$_site" ] && _site=$(json_extract_field "$_info" "site" || true)
_create=$(json_extract_field "$_info" "create_time" || true)
_owner=$(json_extract_field "$_info" "owner_login" || true)
_code_status=$(json_extract_field "$_info" "code_status" || true)
_currency_code=$(json_extract_field "$_info" "currency_code" || true)
echo "Counter: $COUNTER"
echo "Name: $_name"
echo "Site: $_site"
echo "Created: $_create"
echo "Owner: $_owner"
echo "Code status: $_code_status"
if [ -n "$_currency_code" ]; then echo "Currency: $_currency_code"; fi
# Show config if exists
if [ -f "$CONFIG_JSON" ]; then
echo ""
echo "--- Saved config ---"
_attr=$(json_extract_field "$(cat "$CONFIG_JSON")" "attribution")
[ -n "$_attr" ] && echo "Attribution: $_attr"
# Show conversion goals
if grep -q "conversion_goals" "$CONFIG_JSON" 2>/dev/null; then
echo "Conversion goals:"
grep -o '"id"[[:space:]]*:[[:space:]]*[0-9]*' "$CONFIG_JSON" | sed 's/.*:[[:space:]]*/ - /'
fi
fi
echo ""
echo "(cached: $CACHE_JSON)"
if [ -f "$CONFIG_JSON" ]; then echo "(config: $CONFIG_JSON)"; fi
#!/bin/sh
# List Yandex Metrika counters with cache + TSV index
# Usage: counters.sh [--no-cache] [--search <text>]
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/common.sh"
load_config
SEARCH=""
NO_CACHE=""
while [ $# -gt 0 ]; do
case "$1" in
--no-cache) NO_CACHE="1"; shift ;;
--search) SEARCH="$2"; shift 2 ;;
*) shift ;;
esac
done
CACHE_JSON="$CACHE_DIR/counters.json"
CACHE_TSV="$CACHE_DIR/counters.tsv"
# Try cache first
if [ -z "$NO_CACHE" ] && [ -f "$CACHE_TSV" ] && [ -s "$CACHE_TSV" ]; then
if [ -n "$SEARCH" ]; then
echo "ID Name Site"
grep -i "$SEARCH" "$CACHE_TSV" || echo "(no matches for '$SEARCH')"
else
echo "ID Name Site"
cat "$CACHE_TSV"
fi
echo ""
echo "(cached: $CACHE_TSV)"
exit 0
fi
# Fetch from API
echo "Fetching counters from API..." >&2
TMPFILE="${METRIKA_TMPDIR}/metrika_counters_$$.json"
trap 'rm -f "$TMPFILE"' EXIT
metrika_mgmt_get "/management/v1/counters" \
--data-urlencode "per_page=1000" \
> "$TMPFILE"
# Save raw JSON to cache
mkdir -p "$CACHE_DIR"
cp "$TMPFILE" "$CACHE_JSON"
# Generate TSV index: id<TAB>name<TAB>site
# Step 1: strip nested JSON objects (webvisor, code_options, informer, site2)
# so that top-level "site" field stays on the same line as "id" and "name".
# Step 2: split by },{ to get one counter per line.
# Step 3: extract id/name/site via grep/sed.
_flat="${METRIKA_TMPDIR}/metrika_counters_flat_$$.json"
cp "$TMPFILE" "$_flat"
_pass=0
while [ "$_pass" -lt 5 ]; do
sed 's/,"[^"]*":{[^{}]*}//g; s/"[^"]*":{[^{}]*},//g; s/"[^"]*":{[^{}]*}//g' "$_flat" > "$_flat.2"
mv "$_flat.2" "$_flat"
_pass=$(( _pass + 1 ))
done
{
sed 's/},{/}\'$'\n''{/g' "$_flat" | while IFS= read -r _line || [ -n "$_line" ]; do
_id=$(echo "$_line" | grep -o '"id"[[:space:]]*:[[:space:]]*[0-9]*' | head -1 | sed 's/.*:[[:space:]]*//')
_name=$(echo "$_line" | grep -o '"name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*:[[:space:]]*"//;s/"$//' | tr ' \n' ' ')
_site=$(echo "$_line" | grep -o '"site"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*:[[:space:]]*"//;s/"$//' | tr ' \n' ' ')
if [ -n "$_id" ]; then
printf '%s\t%s\t%s\n' "$_id" "$_name" "$_site"
fi
done
} > "$CACHE_TSV"
rm -f "$_flat"
# Output
echo "ID Name Site"
if [ -n "$SEARCH" ]; then
grep -i "$SEARCH" "$CACHE_TSV" || echo "(no matches for '$SEARCH')"
else
print_csv_head "$CACHE_TSV" 30
fi
echo ""
echo "Total counters: $(wc -l < "$CACHE_TSV" | tr -d ' ')"
echo "Cached: $CACHE_TSV (use grep/rg to search)"
#!/bin/sh
# Get Yandex Direct client logins linked to a Metrika counter
# Usage: direct_clients.sh --counter <ID> [--no-cache]
#
# Calls GET /management/v1/clients?counters=<counterId>
# Saves chief_login to cache/counter_<id>/direct_clients.json
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/common.sh"
load_config
COUNTER=""
NO_CACHE=""
while [ $# -gt 0 ]; do
case "$1" in
--counter) COUNTER="$2"; shift 2 ;;
--no-cache) NO_CACHE="1"; shift ;;
*) shift ;;
esac
done
if [ -z "$COUNTER" ]; then
echo "Error: --counter <ID> is required." >&2
echo "Usage: direct_clients.sh --counter <ID> [--no-cache]" >&2
exit 1
fi
COUNTER_DIR=$(cache_dir_for_counter "$COUNTER")
CACHE_JSON="$COUNTER_DIR/direct_clients.json"
# Try cache first
if [ -z "$NO_CACHE" ] && [ -f "$CACHE_JSON" ] && [ -s "$CACHE_JSON" ]; then
echo "Direct clients for counter $COUNTER:"
# Extract logins from cache
_all_accessible=$(grep -o '"all_clients_accessible_to_user"[[:space:]]*:[[:space:]]*[a-z]*' "$CACHE_JSON" | head -1 | sed 's/.*:[[:space:]]*//')
grep -o '"chief_login"[[:space:]]*:[[:space:]]*"[^"]*"' "$CACHE_JSON" | sed 's/.*"chief_login"[[:space:]]*:[[:space:]]*"//;s/"$//' | while IFS= read -r _login || [ -n "$_login" ]; do
echo " - $_login"
done
if [ "$_all_accessible" = "false" ]; then
echo ""
echo "WARNING: Not all Direct clients are accessible. Cost data may be incomplete."
fi
echo ""
echo "(cached: $CACHE_JSON)"
exit 0
fi
# Fetch from API
echo "Fetching Direct clients for counter $COUNTER..." >&2
TMPFILE="${METRIKA_TMPDIR}/metrika_direct_clients_$$.json"
trap 'rm -f "$TMPFILE"' EXIT
metrika_mgmt_get "/management/v1/clients" \
--data-urlencode "counters=$COUNTER" > "$TMPFILE"
# Save raw JSON
cp "$TMPFILE" "$CACHE_JSON"
# Output
echo "Direct clients for counter $COUNTER:"
_all_accessible=$(grep -o '"all_clients_accessible_to_user"[[:space:]]*:[[:space:]]*[a-z]*' "$CACHE_JSON" | head -1 | sed 's/.*:[[:space:]]*//')
_count=0
grep -o '"chief_login"[[:space:]]*:[[:space:]]*"[^"]*"' "$CACHE_JSON" | sed 's/.*"chief_login"[[:space:]]*:[[:space:]]*"//;s/"$//' | while IFS= read -r _login || [ -n "$_login" ]; do
echo " - $_login"
_count=$(( _count + 1 ))
done
if [ "$_all_accessible" = "false" ]; then
echo ""
echo "WARNING: Not all Direct clients are accessible. Cost data may be incomplete."
fi
echo ""
echo "Cached: $CACHE_JSON"
#!/bin/sh
# Yandex Direct costs report: clicks, ad cost, visits by campaign/date
# Usage: direct_costs.sh --counter <ID> --date1 YYYY-MM-DD [--date2 ...]
# [--direct-client-logins "login1,login2"] [--limit N] [--csv path] [--no-cache]
#
# Requires direct_client_logins — auto-fetched from cache or via direct_clients.sh.
# Override with --direct-client-logins if cache is empty or endpoint deprecated.
# Uses ym:ad:* metrics (ad scope, not visit scope).
# No --group support: ym:ad:date dimension gives daily granularity,
# aggregate to week/month in CSV/Excel.
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/common.sh"
load_config
# Parse --direct-client-logins before parse_common_params (which ignores unknown flags)
DIRECT_LOGINS_OVERRIDE=""
_prev_dc=""
for _arg in "$@"; do
case "$_prev_dc" in
--direct-client-logins) DIRECT_LOGINS_OVERRIDE="$_arg"; _prev_dc=""; continue ;;
esac
_prev_dc="$_arg"
done
parse_common_params "$@"
require_counter
require_dates
# --- Get direct_client_logins ---
COUNTER_DIR=$(cache_dir_for_counter "$COUNTER")
if [ -n "$DIRECT_LOGINS_OVERRIDE" ]; then
DIRECT_LOGINS="$DIRECT_LOGINS_OVERRIDE"
else
_dc_cache="$COUNTER_DIR/direct_clients.json"
if [ ! -f "$_dc_cache" ] || [ ! -s "$_dc_cache" ]; then
echo "Direct clients cache not found. Fetching..." >&2
sh "$SCRIPT_DIR/direct_clients.sh" --counter "$COUNTER" >/dev/null || true
fi
if [ ! -f "$_dc_cache" ] || [ ! -s "$_dc_cache" ]; then
echo "Error: could not get Direct client logins for counter $COUNTER." >&2
echo "The /management/v1/clients endpoint may be unavailable." >&2
echo "Specify logins manually: --direct-client-logins \"login1,login2\"" >&2
exit 1
fi
# Extract comma-separated logins
_logins_tmp=$(grep -o '"chief_login"[[:space:]]*:[[:space:]]*"[^"]*"' "$_dc_cache" | sed 's/.*"chief_login"[[:space:]]*:[[:space:]]*"//;s/"$//')
echo "$_logins_tmp" | while IFS= read -r _login || [ -n "$_login" ]; do
if [ -n "$_login" ]; then
printf '%s\n' "$_login"
fi
done > "${METRIKA_TMPDIR}/metrika_logins_$$.txt"
# Build comma-separated string
DIRECT_LOGINS=""
_first=1
while IFS= read -r _login || [ -n "$_login" ]; do
if [ -n "$_login" ]; then
if [ "$_first" = "1" ]; then
DIRECT_LOGINS="$_login"
_first=0
else
DIRECT_LOGINS="${DIRECT_LOGINS},${_login}"
fi
fi
done < "${METRIKA_TMPDIR}/metrika_logins_$$.txt"
rm -f "${METRIKA_TMPDIR}/metrika_logins_$$.txt"
if [ -z "$DIRECT_LOGINS" ]; then
echo "Error: no Direct client logins found for counter $COUNTER." >&2
echo "This counter may not have linked Yandex Direct accounts." >&2
echo "Specify logins manually: --direct-client-logins \"login\"" >&2
exit 1
fi
# Check accessibility warning
_all_accessible=$(grep -o '"all_clients_accessible_to_user"[[:space:]]*:[[:space:]]*[a-z]*' "$_dc_cache" | head -1 | sed 's/.*:[[:space:]]*//')
if [ "$_all_accessible" = "false" ]; then
echo "WARNING: Not all Direct clients are accessible. Cost data may be incomplete." >&2
fi
fi
# --- Build report ---
METRICS="ym:ad:clicks,ym:ad:RUBConvertedAdCost,ym:ad:visits"
DIMENSIONS="ym:ad:date,ym:ad:directOrder"
# Cache key
_params_str="direct_costs_${COUNTER}_${DATE1}_${DATE2}_${DIRECT_LOGINS}_${LIMIT}"
_hash=$(cache_key "$_params_str")
CACHE_FILE="$COUNTER_DIR/reports/direct_costs_${DATE1}_${DATE2}_${_hash}.csv"
# Skip cache if date2 is today
if date_is_today "$DATE2"; then
NO_CACHE="1"
fi
# Check cache
if [ -z "$NO_CACHE" ] && [ -f "$CACHE_FILE" ] && [ -s "$CACHE_FILE" ]; then
echo "Direct costs for counter $COUNTER ($DATE1 — $DATE2):"
print_csv_head "$CACHE_FILE" 30
[ -n "$CSV_OUT" ] && cp "$CACHE_FILE" "$CSV_OUT" && echo "Copied to: $CSV_OUT"
exit 0
fi
# Always use /stat/v1/data.csv (no bytime — ym:ad:date gives daily granularity)
API_PATH="/stat/v1/data.csv"
echo "Fetching Direct costs for counter $COUNTER ($DATE1 — $DATE2)..." >&2
TMPFILE="${METRIKA_TMPDIR}/metrika_direct_costs_$$.csv"
trap 'rm -f "$TMPFILE"' EXIT
metrika_get_csv "$API_PATH" "$TMPFILE" \
--data-urlencode "ids=$COUNTER" \
--data-urlencode "date1=$DATE1" \
--data-urlencode "date2=$DATE2" \
--data-urlencode "metrics=$METRICS" \
--data-urlencode "dimensions=$DIMENSIONS" \
--data-urlencode "direct_client_logins=$DIRECT_LOGINS" \
--data-urlencode "accuracy=1" \
${LIMIT:+--data-urlencode "limit=$LIMIT"}
cp "$TMPFILE" "$CACHE_FILE"
echo "Direct costs for counter $COUNTER ($DATE1 — $DATE2):"
print_csv_head "$CACHE_FILE" 30
if [ -n "$CSV_OUT" ]; then
cp "$CACHE_FILE" "$CSV_OUT"
echo "Exported to: $CSV_OUT"
fi
#!/bin/sh
# Ecommerce report: purchases, revenue, avg check
# Usage: ecommerce.sh --counter <ID> --date1 YYYY-MM-DD [--date2 ...] [--group day|week|month]
# [--device ...] [--source ...] [--attribution lastsign|last|first]
# [--currency RUB|USD|EUR] [--limit N] [--csv path] [--no-cache]
#
# Currency: uses documented ecommerce<CUR>ConvertedRevenue* metrics.
# Default currency is read from counter info cache (currency_code field).
# Override with --currency RUB|USD|EUR.
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/common.sh"
load_config
# Parse --currency before parse_common_params (which ignores unknown flags)
CURRENCY=""
_prev_ec=""
for _arg in "$@"; do
case "$_prev_ec" in
--currency) CURRENCY="$_arg"; _prev_ec=""; continue ;;
esac
_prev_ec="$_arg"
done
parse_common_params "$@"
require_counter
require_dates
ATTRIBUTION="${ATTRIBUTION:-lastsign}"
# Auto-detect currency from counter info cache if not specified
if [ -z "$CURRENCY" ]; then
COUNTER_DIR=$(cache_dir_for_counter "$COUNTER")
_info_cache="$COUNTER_DIR/info.json"
if [ -f "$_info_cache" ]; then
CURRENCY=$(grep -o '"currency_code":"[^"]*"' "$_info_cache" | head -1 | sed 's/.*"currency_code":"//;s/"//' || true)
fi
fi
# Fallback to RUB if still empty
CURRENCY="${CURRENCY:-RUB}"
# Build metrics using documented ecommerce<CUR>ConvertedRevenue* names
_rev="ym:s:ecommerce${CURRENCY}ConvertedRevenue"
_rev_per_purchase="ym:s:ecommerce${CURRENCY}ConvertedRevenuePerPurchase"
_rev_per_visit="ym:s:ecommerce${CURRENCY}ConvertedRevenuePerVisit"
METRICS="ym:s:ecommercePurchases,${_rev},${_rev_per_purchase},${_rev_per_visit},ym:s:visits,ym:s:users"
DIMENSIONS="ym:s:${ATTRIBUTION}TrafficSource"
# Cache key
_params_str="ecommerce_${COUNTER}_${DATE1}_${DATE2}_${GROUP}_${DEVICE}_${SOURCE}_${ATTRIBUTION}_${CURRENCY}_${LIMIT}"
_hash=$(cache_key "$_params_str")
COUNTER_DIR=$(cache_dir_for_counter "$COUNTER")
CACHE_FILE="$COUNTER_DIR/reports/ecommerce_${DATE1}_${DATE2}_${_hash}.csv"
# Skip cache if date2 is today (data still accumulating)
if date_is_today "$DATE2"; then
NO_CACHE="1"
fi
# Check cache
if [ -z "$NO_CACHE" ] && [ -f "$CACHE_FILE" ] && [ -s "$CACHE_FILE" ]; then
echo "Ecommerce report for counter $COUNTER ($DATE1 — $DATE2):"
print_csv_head "$CACHE_FILE" 30
[ -n "$CSV_OUT" ] && cp "$CACHE_FILE" "$CSV_OUT" && echo "Copied to: $CSV_OUT"
exit 0
fi
# Build API path
if [ -n "$GROUP" ]; then
API_PATH="/stat/v1/data/bytime.csv"
else
API_PATH="/stat/v1/data.csv"
fi
echo "Fetching ecommerce report for counter $COUNTER ($DATE1 — $DATE2)..." >&2
TMPFILE="${METRIKA_TMPDIR}/metrika_ecommerce_$$.csv"
trap 'rm -f "$TMPFILE"' EXIT
metrika_get_csv "$API_PATH" "$TMPFILE" \
--data-urlencode "ids=$COUNTER" \
--data-urlencode "date1=$DATE1" \
--data-urlencode "date2=$DATE2" \
--data-urlencode "metrics=$METRICS" \
--data-urlencode "dimensions=$DIMENSIONS" \
--data-urlencode "accuracy=1" \
--data-urlencode "filters=$FILTERS" \
${GROUP:+--data-urlencode "group=$GROUP"} \
${LIMIT:+--data-urlencode "limit=$LIMIT"}
cp "$TMPFILE" "$CACHE_FILE"
echo "Ecommerce report for counter $COUNTER ($DATE1 — $DATE2):"
print_csv_head "$CACHE_FILE" 30
if [ -n "$CSV_OUT" ]; then
cp "$CACHE_FILE" "$CSV_OUT"
echo "Exported to: $CSV_OUT"
fi
#!/bin/sh
# List goals for a Yandex Metrika counter with cache + TSV index
# Usage: goals.sh --counter <ID> [--no-cache]
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/common.sh"
load_config
COUNTER=""
NO_CACHE=""
while [ $# -gt 0 ]; do
case "$1" in
--counter) COUNTER="$2"; shift 2 ;;
--no-cache) NO_CACHE="1"; shift ;;
*) shift ;;
esac
done
if [ -z "$COUNTER" ]; then
echo "Error: --counter <ID> is required." >&2
echo "Usage: goals.sh --counter <ID> [--no-cache]" >&2
exit 1
fi
COUNTER_DIR=$(cache_dir_for_counter "$COUNTER")
CACHE_JSON="$COUNTER_DIR/goals.json"
CACHE_TSV="$COUNTER_DIR/goals.tsv"
# Try cache first
if [ -z "$NO_CACHE" ] && [ -f "$CACHE_TSV" ] && [ -s "$CACHE_TSV" ]; then
echo "Goals for counter $COUNTER:"
echo "ID Name Type"
cat "$CACHE_TSV"
echo ""
echo "(cached: $CACHE_TSV)"
exit 0
fi
# Fetch from API
echo "Fetching goals for counter $COUNTER..." >&2
TMPFILE="${METRIKA_TMPDIR}/metrika_goals_$$.json"
trap 'rm -f "$TMPFILE"' EXIT
metrika_mgmt_get "/management/v1/counter/$COUNTER/goals" > "$TMPFILE"
# Save raw JSON
cp "$TMPFILE" "$CACHE_JSON"
# Generate TSV index: id<TAB>name<TAB>type
{
tr '{}' '\n' < "$TMPFILE" | while IFS= read -r _line; do
_id=$(echo "$_line" | grep -o '"id"[[:space:]]*:[[:space:]]*[0-9]*' | head -1 | sed 's/.*:[[:space:]]*//')
_name=$(echo "$_line" | grep -o '"name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*:[[:space:]]*"//;s/"$//' | tr ' \n' ' ')
_type=$(echo "$_line" | grep -o '"type"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*:[[:space:]]*"//;s/"$//')
if [ -n "$_id" ] && [ -n "$_name" ]; then
printf '%s\t%s\t%s\n' "$_id" "$_name" "$_type"
fi
done
} > "$CACHE_TSV"
# Output
echo "Goals for counter $COUNTER:"
echo "ID Name Type"
cat "$CACHE_TSV"
echo ""
_total=$(wc -l < "$CACHE_TSV" | tr -d ' ')
echo "Total goals: $_total"
echo "Cached: $CACHE_TSV"
#!/bin/sh
# Search engine traffic report
# Usage: search_engines.sh --counter <ID> --date1 YYYY-MM-DD [--date2 ...] [--group day|week|month]
# [--device ...] [--attribution lastsign|last|first]
# [--limit N] [--csv path] [--no-cache]
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/common.sh"
load_config
parse_common_params "$@"
require_counter
require_dates
ATTRIBUTION="${ATTRIBUTION:-lastsign}"
METRICS="ym:s:visits,ym:s:users,ym:s:bounceRate,ym:s:pageDepth,ym:s:avgVisitDurationSeconds"
DIMENSIONS="ym:s:${ATTRIBUTION}SourceEngine"
# Force filter: only organic traffic
_se_filters="$FILTERS AND ym:s:${ATTRIBUTION}TrafficSource=='organic'"
# Cache key
_params_str="search_${COUNTER}_${DATE1}_${DATE2}_${GROUP}_${DEVICE}_${ATTRIBUTION}_${LIMIT}"
_hash=$(cache_key "$_params_str")
COUNTER_DIR=$(cache_dir_for_counter "$COUNTER")
CACHE_FILE="$COUNTER_DIR/reports/search_${DATE1}_${DATE2}_${_hash}.csv"
# Skip cache if date2 is today (data still accumulating)
if date_is_today "$DATE2"; then
NO_CACHE="1"
fi
# Check cache
if [ -z "$NO_CACHE" ] && [ -f "$CACHE_FILE" ] && [ -s "$CACHE_FILE" ]; then
echo "Search engines for counter $COUNTER ($DATE1 — $DATE2):"
print_csv_head "$CACHE_FILE" 30
[ -n "$CSV_OUT" ] && cp "$CACHE_FILE" "$CSV_OUT" && echo "Copied to: $CSV_OUT"
exit 0
fi
# Build API path
if [ -n "$GROUP" ]; then
API_PATH="/stat/v1/data/bytime.csv"
else
API_PATH="/stat/v1/data.csv"
fi
echo "Fetching search engine report for counter $COUNTER ($DATE1 — $DATE2)..." >&2
TMPFILE="${METRIKA_TMPDIR}/metrika_search_$$.csv"
trap 'rm -f "$TMPFILE"' EXIT
metrika_get_csv "$API_PATH" "$TMPFILE" \
--data-urlencode "ids=$COUNTER" \
--data-urlencode "date1=$DATE1" \
--data-urlencode "date2=$DATE2" \
--data-urlencode "metrics=$METRICS" \
--data-urlencode "dimensions=$DIMENSIONS" \
--data-urlencode "accuracy=1" \
--data-urlencode "filters=$_se_filters" \
${GROUP:+--data-urlencode "group=$GROUP"} \
${LIMIT:+--data-urlencode "limit=$LIMIT"}
cp "$TMPFILE" "$CACHE_FILE"
echo "Search engines for counter $COUNTER ($DATE1 — $DATE2):"
print_csv_head "$CACHE_FILE" 30
if [ -n "$CSV_OUT" ]; then
cp "$CACHE_FILE" "$CSV_OUT"
echo "Exported to: $CSV_OUT"
fi
#!/bin/sh
# Traffic distribution by source
# Usage: traffic_summary.sh --counter <ID> --date1 YYYY-MM-DD [--date2 ...] [--group day|week|month]
# [--device desktop|mobile|tablet] [--source organic|ad|referral|direct]
# [--attribution lastsign|last|first] [--limit N] [--csv path] [--no-cache]
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/common.sh"
load_config
parse_common_params "$@"
require_counter
require_dates
ATTRIBUTION="${ATTRIBUTION:-lastsign}"
METRICS="ym:s:visits,ym:s:users,ym:s:bounceRate,ym:s:pageDepth,ym:s:avgVisitDurationSeconds"
DIMENSIONS="ym:s:${ATTRIBUTION}TrafficSource"
# Cache key
_params_str="traffic_${COUNTER}_${DATE1}_${DATE2}_${GROUP}_${DEVICE}_${SOURCE}_${ATTRIBUTION}_${LIMIT}"
_hash=$(cache_key "$_params_str")
COUNTER_DIR=$(cache_dir_for_counter "$COUNTER")
CACHE_FILE="$COUNTER_DIR/reports/traffic_${DATE1}_${DATE2}_${_hash}.csv"
# Skip cache if date2 is today (data still accumulating)
if date_is_today "$DATE2"; then
NO_CACHE="1"
fi
# Check cache
if [ -z "$NO_CACHE" ] && [ -f "$CACHE_FILE" ] && [ -s "$CACHE_FILE" ]; then
echo "Traffic summary for counter $COUNTER ($DATE1 — $DATE2):"
print_csv_head "$CACHE_FILE" 30
[ -n "$CSV_OUT" ] && cp "$CACHE_FILE" "$CSV_OUT" && echo "Copied to: $CSV_OUT"
exit 0
fi
# Build API path
if [ -n "$GROUP" ]; then
API_PATH="/stat/v1/data/bytime.csv"
else
API_PATH="/stat/v1/data.csv"
fi
# Fetch
echo "Fetching traffic summary for counter $COUNTER ($DATE1 — $DATE2)..." >&2
TMPFILE="${METRIKA_TMPDIR}/metrika_traffic_$$.csv"
trap 'rm -f "$TMPFILE"' EXIT
metrika_get_csv "$API_PATH" "$TMPFILE" \
--data-urlencode "ids=$COUNTER" \
--data-urlencode "date1=$DATE1" \
--data-urlencode "date2=$DATE2" \
--data-urlencode "metrics=$METRICS" \
--data-urlencode "dimensions=$DIMENSIONS" \
--data-urlencode "accuracy=1" \
--data-urlencode "filters=$FILTERS" \
${GROUP:+--data-urlencode "group=$GROUP"} \
${LIMIT:+--data-urlencode "limit=$LIMIT"}
# Cache result
cp "$TMPFILE" "$CACHE_FILE"
# Output
echo "Traffic summary for counter $COUNTER ($DATE1 — $DATE2):"
print_csv_head "$CACHE_FILE" 30
if [ -n "$CSV_OUT" ]; then
cp "$CACHE_FILE" "$CSV_OUT"
echo "Exported to: $CSV_OUT"
fi
#!/bin/sh
# UTM breakdown report
# Usage: utm_report.sh --counter <ID> --date1 YYYY-MM-DD [--date2 ...] [--group day|week|month]
# [--device ...] [--source ...] [--attribution lastsign|last|first]
# [--limit N] [--csv path] [--no-cache]
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/common.sh"
load_config
parse_common_params "$@"
require_counter
require_dates
ATTRIBUTION="${ATTRIBUTION:-lastsign}"
METRICS="ym:s:visits,ym:s:users,ym:s:bounceRate,ym:s:pageDepth,ym:s:avgVisitDurationSeconds"
DIMENSIONS="ym:s:${ATTRIBUTION}UTMSource,ym:s:${ATTRIBUTION}UTMMedium,ym:s:${ATTRIBUTION}UTMCampaign"
# Cache key
_params_str="utm_${COUNTER}_${DATE1}_${DATE2}_${GROUP}_${DEVICE}_${SOURCE}_${ATTRIBUTION}_${LIMIT}"
_hash=$(cache_key "$_params_str")
COUNTER_DIR=$(cache_dir_for_counter "$COUNTER")
CACHE_FILE="$COUNTER_DIR/reports/utm_${DATE1}_${DATE2}_${_hash}.csv"
# Skip cache if date2 is today (data still accumulating)
if date_is_today "$DATE2"; then
NO_CACHE="1"
fi
# Check cache
if [ -z "$NO_CACHE" ] && [ -f "$CACHE_FILE" ] && [ -s "$CACHE_FILE" ]; then
echo "UTM report for counter $COUNTER ($DATE1 — $DATE2):"
print_csv_head "$CACHE_FILE" 30
[ -n "$CSV_OUT" ] && cp "$CACHE_FILE" "$CSV_OUT" && echo "Copied to: $CSV_OUT"
exit 0
fi
# Build API path
if [ -n "$GROUP" ]; then
API_PATH="/stat/v1/data/bytime.csv"
else
API_PATH="/stat/v1/data.csv"
fi
echo "Fetching UTM report for counter $COUNTER ($DATE1 — $DATE2)..." >&2
TMPFILE="${METRIKA_TMPDIR}/metrika_utm_$$.csv"
trap 'rm -f "$TMPFILE"' EXIT
metrika_get_csv "$API_PATH" "$TMPFILE" \
--data-urlencode "ids=$COUNTER" \
--data-urlencode "date1=$DATE1" \
--data-urlencode "date2=$DATE2" \
--data-urlencode "metrics=$METRICS" \
--data-urlencode "dimensions=$DIMENSIONS" \
--data-urlencode "accuracy=1" \
--data-urlencode "filters=$FILTERS" \
${GROUP:+--data-urlencode "group=$GROUP"} \
${LIMIT:+--data-urlencode "limit=$LIMIT"}
cp "$TMPFILE" "$CACHE_FILE"
echo "UTM report for counter $COUNTER ($DATE1 — $DATE2):"
print_csv_head "$CACHE_FILE" 30
if [ -n "$CSV_OUT" ]; then
cp "$CACHE_FILE" "$CSV_OUT"
echo "Exported to: $CSV_OUT"
fi