Перейти к содержанию

GitHub Traffic API: скользящее окно и почему uniques нельзя суммировать

Статус: Confirmed (поведение API и формат ответов — реальными запросами к восьми публичным репозиториям 2026-10-10) + Observed (структура реального снимка той же даты; сами значения не публикуются)
Область применения: корректное накопление статистики просмотров/клонов собственных репозиториев за месяц, квартал и год; прежде всего — типовая ошибка сложения «уникальных посетителей» из перекрывающихся окон
Проверено на: GitHub REST GET /repos/{owner}/{repo}/traffic/views и .../traffic/clones; токен с правом чтения traffic-метрик; приватный снимок владельца по 8 репозиториям saymer-alt (значения метрик здесь не приводятся)
Последняя проверка: 2026-10-10
Source of truth: живое поведение GitHub Traffic API на дату проверки; официальная документация — как справочник форматов, не как гарантия сроков хранения
Provenance: оригинальная статья, написана по результатам проверки скрипта github_traffic_snapshot.py в профильном репозитории saymer-alt/saymer-alt (issue #2)

Что решаем

GitHub показывает просмотры и клоны только за «последние ~14 дней». Чтобы строить графики за месяц и дольше, окно приходится снимать периодически и копить. Статья фиксирует, что именно возвращает API, что можно суммировать, а что — нельзя, и как правильно копить данные.

Контекст

  • Ответ каждого эндпоинта содержит: count (события за всё окно), uniques (уникальные посетители за всё окно) и серию суточных записей timestamp/count/uniques.
  • Окно скользящее: каждый снимок покрывает примерно последние 14 дней, включая уже снятые ранее дни.
  • timestamp суточных записей — полночь UTC (2026-10-09T00:00:00Z).

Как это работает

Что аддитивно. Каждое «просмотр»/«клон» — это отдельное событие; события за разные сутки не пересекаются, поэтому суточные count из серии можно суммировать за любой период.

Что не аддитивно. uniques — результат дедупликации посетителей в пределах одного запроса (для серии — в пределах суток, для верхнего uniques — в пределах всего окна). Один и тот же посетитель дня X попадает в ~14 последовательных снимков, потому что день X остаётся в скользящем окне около двух недель. Сумма uniques по снимкам посчитает его до четырнадцати раз; сумма верхних uniques двух снимков с разницей в неделю пересчитает общие для обоих окон дни дважды.

Оценки уникальных за период — верхняя и нижняя границы. Синтетический пример: период — 10 дней, каждые сутки приходит 1 новый уникальный посетитель и «старый» посетитель A заходит каждый день. Тогда каждые сутки дают 2 uniques, каждое 14-дневное окно (если бы оно целиком покрывало период) — 11 uniques (10 новых + A).

  • Верхняя граница уникальных за период — сумма uniques по непересекающимся частям периода: она игнорирует посетителей, встречающихся в нескольких частях (в примере A посчитан десять раз), и поэтому никогда не меньше фактического числа.
  • Нижняя граница — максимум верхних uniques по одному окну, входящему в период: внутри одного окна дедупликация уже выполнена (посетитель A посчитан один раз), но посетители, приходившие только вне этого окна, не учтены.

Следствие. Точное число уникальных посетителей за месяц из перекрывающихся 14-дневных окон восстановить нельзя: посетитель, приходивший 1-го и 20-го числа, ни в одном отдельном окне не выглядит «одним посетителем месяца». Любое агрегированное число за период — это оценка между нижней и верхней границей, а не точное значение.

Правильная схема накопления

  1. Снимать снимок не реже одного раза в 14 дней (лучше — ежедневно): данные дня, попавшего в очередь на «выпадение» из окна, иначе теряются навсегда.
  2. Хранить весь JSON-снимок за день: файл с именем traffic-YYYY-MM-DD.json, без перезаписи старых дней.
  3. Для графиков: суммировать суточные count серии views/clones; uniques показывать только «как есть», с пометкой окна, либо давать границы периода — нижнюю (максимум uniques одного окна) и верхнюю (сумма uniques непересекающихся частей), явно помечая их как оценки.
  4. Суточная запись из более свежего снимка приоритетнее той же записи из более старого: GitHub может досчитать события дня до конца суток.

Требования

  • Токен GitHub с правом чтения traffic-метрик репозитория (для собственных репозиториев — обычный token owner'а); traffic-эндпоинты недоступны анонимно.
  • Репозитории, по которым снимается статистика; список — фиксированный, не по обходу каталога.

Проверка

Разовая проверка структуры ответа (поля и суточная серия):

gh api repos/<owner>/<repo>/traffic/views

Ожидаемо: count и uniques — целые; views — массив записей с timestamp в полночь UTC. Число записей у свежего репозитория может быть меньше 14 (окно растёт с момента первого дня с трафиком).

Проверка защиты от перезаписи (повторный запуск снимка в тот же день должен завершиться отказом, старый файл — остаться нетронутым):

python3 scripts/github_traffic_snapshot.py --output-dir ~/github-traffic-private
# повтор: FileExistsError на traffic-<сегодня>.json

Типичные проблемы

Сумма уникальных за месяц получилась больше суммы просмотров

Признак сложения uniques из перекрывающихся окон. Пересчитайте по суточным count для событий и оставьте uniques только в пределах одного окна/суток.

Часть дней в серии отсутствует

У репозитория не было трафика в эти дни, либо день уже выпал из скользящего окна к моменту первого снимка. Окно не восстанавливается задним числом.

HTTP 403 на traffic-запросе

Токену недоступна traffic-метрика (нужен доступ уровня push к репозиторию — на практике права владельца) или сработал rate limit. Скрипт снимка в профильном репозитории переводит любой такой отказ в явную ошибку и не пишет частичный файл.

Что подтверждено, а что нет

Confirmed (2026-10-10):

  • Формат ответов (поля, полночь UTC, целочисленные счётчики) на 8 репозиториях; суточные серии по 14 записей, дни без повторов.
  • Отказ API (403/404/429), пустой и некорректный JSON обрабатываются snapshot-скриптом как ошибки; при отказе в середине сбора файл не создаётся вовсе.
  • Файл дня создаётся с O_EXCL: повторный запуск не перезаписывает данные.

Observed:

  • На реальном снимке владельца от 2026-10-10 по восьми публичным репозиториям формат ответов и поведение скрипта соответствовали описанию (значения метрик не публикуются; для иллюстраций в этой статье используются только синтетические примеры).

Still research / unknown:

  • Точный момент дедупликации uniques внутри окна (GitHub не раскрывает алгоритм); для накопления это несущественно — складывать uniques всё равно нельзя.
  • Гарантии сохранения истории самим GitHub: документировано только «~14 дней», поэтому внешнее копирование обязательно.

Источники и provenance

  • REST API: GET /repos/{owner}/{repo}/traffic/views|clones (справочник);
  • скрипт снимка: scripts/github_traffic_snapshot.py в saymer-alt/saymer-alt;
  • проверка выполнена на реальном снимке владельца 2026-10-10; сырые метрики и пути к приватным данным здесь не приводятся.

Связанные материалы

  • Основы Keenetic и Entware — смежная методология разделения подтверждённых фактов и наблюдений.