@ezmar/yandex-metric-parser-lib 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 VRomazanov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,213 @@
1
+ # yandex-metric-parser-lib
2
+
3
+ Типизированная обёртка над API отчётов Яндекс Метрики для сбора ключевых слов:
4
+ по каким словам сайт находят в поиске и сколько кликов из Яндекс Директа пришло по каждому слову.
5
+
6
+ Библиотека делает две вещи: аккуратно достаёт данные (квоты, пагинация, ретраи, валидация запроса
7
+ до его отправки) и приводит органику и Директ к единому реестру ключевых слов. Кластеризация,
8
+ поиск корреляций и генерация текстов — задача следующего слоя, поверх этого реестра.
9
+
10
+ ## Установка
11
+
12
+ ```bash
13
+ npm install @ezmar/yandex-metric-parser-lib
14
+ ```
15
+
16
+ Нужен OAuth-токен Яндекса — см. раздел [Получение токена](#получение-токена) ниже.
17
+
18
+ ## Быстрый старт
19
+
20
+ ```ts
21
+ import { YandexMetrika } from '@ezmar/yandex-metric-parser-lib';
22
+
23
+ const metrika = new YandexMetrika({ token: process.env.YANDEX_METRIKA_TOKEN! });
24
+
25
+ const registry = await metrika.buildKeywordRegistry({ counterId: 44147844 });
26
+
27
+ for (const keyword of registry.keywords.slice(0, 20)) {
28
+ console.log(keyword.displayPhrase, keyword.totalVisits, keyword.direct?.clicks ?? 0);
29
+ }
30
+ ```
31
+
32
+ `buildKeywordRegistry` выгружает органику, посадочные страницы и Директ и склеивает их
33
+ в единый реестр. Логины клиентов Директа подставляются автоматически; если доступа к кампаниям нет,
34
+ Директ выгружается по визитам (без кликов и расходов) с предупреждением.
35
+
36
+ ## Получение токена
37
+
38
+ Токен выдаётся OAuth-сервером Яндекса. Два пути:
39
+
40
+ **Быстрый — только ClientId.** Создайте приложение на
41
+ [oauth.yandex.ru](https://oauth.yandex.ru/?dialog=create-client-entry) с доступом
42
+ **`metrika:read`**, затем откройте в браузере:
43
+
44
+ ```
45
+ https://oauth.yandex.ru/authorize?response_type=token&client_id=<ваш ClientId>
46
+ ```
47
+
48
+ Токен придёт во фрагменте адреса: `#access_token=…`. Refresh-токена в этом потоке нет.
49
+
50
+ **Для автоматизации — ClientId + ClientSecret.** Добавьте в настройках приложения
51
+ Redirect URI `https://oauth.yandex.ru/verification_code` (Платформы → Веб-сервисы),
52
+ затем:
53
+
54
+ ```bash
55
+ YANDEX_CLIENT_ID=… YANDEX_CLIENT_SECRET=… npm run get-token -- --code --save
56
+ ```
57
+
58
+ Скрипт напечатает ссылку, спросит код с экрана (он живёт 10 минут), обменяет его на токен
59
+ и запишет `access_token` с `refresh_token` в `.env.local`. Продление без браузера:
60
+ `npm run get-token -- --refresh --save`.
61
+
62
+ > **Владельцем токена становится аккаунт, под которым вы авторизовались в браузере**,
63
+ > а не владелец приложения. У этого аккаунта должен быть доступ к счётчику,
64
+ > иначе API ответит `403`.
65
+
66
+ Проверить токен, не запуская библиотеку:
67
+
68
+ ```bash
69
+ curl -s -o /dev/null -w "%{http_code}\n" \
70
+ -H "Authorization: OAuth <token>" \
71
+ https://api-metrika.yandex.net/management/v1/counters
72
+ ```
73
+
74
+ Ожидается `200`.
75
+
76
+ ## Что можно достать
77
+
78
+ | Метод | Что отдаёт | Требует доступ к Директу |
79
+ |---|---|---|
80
+ | `getOrganicSearchPhrases` | Поисковые фразы органики: визиты, посетители, отказы, глубина, время на сайте | нет |
81
+ | `getPhraseLandingMap` | Связку «фраза → страница входа» | нет |
82
+ | `getDirectSearchPhraseCost` | Реальные запросы Директа, купленные ключевые слова, **клики и расходы** | да |
83
+ | `getDirectKeywordsByVisits` | Ключевые слова и запросы Директа по визитам, с конверсией | нет |
84
+ | `getPhraseTimeline` | Динамику фраз по дням/неделям с оценкой тренда | нет |
85
+
86
+ Под капотом — группировки `ym:s:<attribution>SearchPhrase`, `ym:s:<attribution>DirectSearchPhrase`,
87
+ `ym:ad:<attribution>DirectSearchPhrase`, `ym:ad:<attribution>DirectPhraseOrCond` и метрики
88
+ `ym:ad:clicks`, `ym:ad:<currency>ConvertedAdCost`.
89
+
90
+ ```ts
91
+ import { aggregateDirectKeywords, settledRange } from '@ezmar/yandex-metric-parser-lib';
92
+
93
+ // Только органика, за 90 дней, без роботов, топ по визитам
94
+ const organic = await metrika.getOrganicSearchPhrases({
95
+ counterId: 44147844,
96
+ ...settledRange(90),
97
+ searchEngines: ['yandex'],
98
+ });
99
+
100
+ // Клики и расходы Директа
101
+ const direct = await metrika.getDirectSearchPhraseCost({ counterId: 44147844 });
102
+
103
+ // Сводка по купленным ключевым словам, а не по запросам
104
+ const byKeyword = aggregateDirectKeywords(direct);
105
+
106
+ // Что растёт
107
+ const timeline = await metrika.getPhraseTimeline({ counterId: 44147844, topPhrases: 20 });
108
+ const growing = timeline.filter((series) => (series.trend ?? 0) > 1.2);
109
+ ```
110
+
111
+ Идентификатор счётчика можно не искать руками — он находится по адресу сайта:
112
+
113
+ ```ts
114
+ const counter = await metrika.management.findCounterBySite('example.ru');
115
+ const registry = await metrika.buildKeywordRegistry({ counterId: counter.id });
116
+ ```
117
+
118
+ `findCounterBySite` бросает `CounterLookupError`, если совпадений нет или их несколько:
119
+ выгружать наугад выбранный счётчик хуже, чем остановиться.
120
+
121
+ ## Ограничения API, о которых нужно знать
122
+
123
+ Это не мелочи реализации — они меняют то, как читать выгрузку.
124
+
125
+ - **Поисковых фраз нет в Logs API.** Из «фразовых» полей там доступно только
126
+ `ym:s:<attribution>DirectPhraseOrCond`. Сырой выгрузки фраз не существует: только агрегаты
127
+ из API отчётов.
128
+ - **Порог раскрытия.** Фразы — конфиденциальные данные: если в выборке меньше 10 посетителей,
129
+ строка не отдаётся. Это режет ровно длинный хвост запросов. Библиотека прокидывает
130
+ `contains_sensitive_data` в `meta.containsSensitiveData` и помечает пограничные записи
131
+ флагом `quality.belowPrivacyThreshold`.
132
+ - **Семплирование.** По умолчанию API считает по неполной выборке. Библиотека всегда шлёт
133
+ `accuracy=full` и предупреждает, если ответ всё равно пришёл семплированным.
134
+ - **Лаг дозаполнения.** 99 % визитов завершаются в течение 3 дней. Период по умолчанию —
135
+ 30 дней, заканчивающихся на `T-3` (`settledRange`).
136
+ - **Квоты.** 30 запросов/с на IP, 3 параллельных, 5000 в сутки, **200 за 5 минут** к `/stat/v1/data`.
137
+ Встроенный лимитер ждёт локально вместо того, чтобы ловить 429.
138
+ - **Нельзя смешивать** `ym:s:` и `ym:ad:` в `dimensions`/`metrics` одного запроса —
139
+ библиотека ловит это до отправки.
140
+ - **Атрибуция.** По умолчанию `last`. Модели `first`, `last_significant`
141
+ и `last_yandex_direct_click` с 25 июня 2026 схлопываются в аналоги — на них выдаётся предупреждение.
142
+
143
+ ## Предупреждения
144
+
145
+ Ничего не проглатывается молча. Все оговорки приходят в `report.meta.warnings`
146
+ и в колбэк `onWarning`:
147
+
148
+ ```ts
149
+ const metrika = new YandexMetrika({
150
+ token,
151
+ onWarning: (warning) => logger.warn({ code: warning.code, ...warning.details }, warning.message),
152
+ });
153
+ ```
154
+
155
+ Коды: `sampling`, `sensitive_data_hidden`, `data_lag`, `deprecated_attribution`,
156
+ `unknown_field`, `truncated`, `direct_access_missing`.
157
+
158
+ ## Проверка актуальности
159
+
160
+ API Метрики меняется, и молча разошедшаяся с ним библиотека хуже упавшей. Источник истины —
161
+ машиночитаемая документация [`llms-full.txt`](https://yandex.ru/dev/metrika/ru/llms-full.txt).
162
+
163
+ ```bash
164
+ npm run fetch-docs # скачать свежую документацию в docs/full-docs.txt
165
+ npm run parse-contract # разобрать её в снапшот + рантайм-реестр
166
+ npm run check-contract # сравнить снапшот с документацией
167
+ ```
168
+
169
+ `check-contract` падает (exit 1), только если сломалось то, на чём держатся рецепты — список
170
+ в [`src/contract/used.ts`](src/contract/used.ts). Появление новых группировок и метрик печатается
171
+ отчётом, но сборку не роняет: API растёт постоянно.
172
+
173
+ В CI ([`.github/workflows/contract.yml`](.github/workflows/contract.yml)) проверка идёт на каждом PR
174
+ против закоммиченной документации и раз в неделю — против свежескачанной (`--fetch`).
175
+
176
+ Дополнительно:
177
+
178
+ - каждый запрос уходит с `User-Agent: yandex-metric-parser-lib (contract <version>)`,
179
+ а `report.meta.contractVersion` попадает в результат — по логам видно, на какой версии
180
+ контракта получены данные;
181
+ - запрос с неизвестной группировкой или метрикой не отправляется вовсе
182
+ (`RequestValidationError`; опция `allowUnknownFields: true` понижает это до предупреждения);
183
+ - если API вернул поле, которого нет в снапшоте, приходит предупреждение `unknown_field`.
184
+
185
+ ## Структура
186
+
187
+ ```
188
+ src/
189
+ client/ HTTP, лимитер квот, ошибки, предупреждения
190
+ reports/ /stat/v1/data и /stat/v1/data/bytime, валидация, пагинация, даты
191
+ management/ логины клиентов Директа
192
+ contract/ снапшот контракта, сгенерированный реестр, список используемых полей
193
+ keywords/ рецепты выгрузки и слой нормализации
194
+ scripts/ fetch-docs, parse-contract, check-contract
195
+ examples/ сквозной сценарий выгрузки реестра в NDJSON
196
+ ```
197
+
198
+ ## Разработка
199
+
200
+ ```bash
201
+ npm install
202
+ npm run typecheck
203
+ npm test
204
+ npm run build
205
+
206
+ # Сквозная выгрузка: счётчик по адресу сайта или по идентификатору
207
+ node --env-file=.env.local --import tsx examples/build-registry.ts --site=example.ru
208
+ YANDEX_METRIKA_TOKEN=… npx tsx examples/build-registry.ts --counter=44147844 --days=30
209
+ ```
210
+
211
+ ## Лицензия
212
+
213
+ MIT