Skip to content

About

Extract YouTube 'most replayed' (heatmap) data via ytInitialData or InnerTube /next

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

yt_heatmap

YouTube videolarının ilerleme çubuğundaki "En çok tekrarlanan" (most replayed / heatmap) verisini programatik olarak çıkarır. Resmi YouTube Data API v3'te bu veri yoktur; bu araç, YouTube'un dahili verisine iki yoldan erişir: HTML scrape veya InnerTube /next uç noktası.

⚠️ Deneysel bulgu: YouTube verinin yerini değiştirdi. Eski rehberlerin/gist'lerin/Apify actor'lerin kullandığı playerOverlays…markersMap → heatmap yolu artık çalışmıyor. Bu araç güncel konumu hedefler: frameworkUpdates.entityBatchUpdate.mutations → macroMarkersListEntity (MARKER_TYPE_HEATMAP).

Kurulum

Sıfır bağımlılık — yalnızca Python 3.10+ stdlib. Kurulum gerektirmez.

# Doğrudan çalıştır
python3 yt_heatmap.py VIDEO_ID

# Veya kütüphane olarak import et (path'e ekle)
import sys; sys.path.insert(0, "/path/to/youtube-trend")
from yt_heatmap import extract_heatmap

Testleri çalıştırma

Test suite'i (test_yt_heatmap.py, 64 test) sentetik verilerle çalışır — ağ çağrısı yapmaz. pytest gerektirir:

python3 -m pytest test_yt_heatmap.py -v

pytest yoksa standalone fallback mevcuttur (fixture gerektirmeyen testler koşar):

python3 test_yt_heatmap.py

İki backend: html vs innertube

Her iki backend de birebir aynı heatmap verisini döndürür (100 segment, intensity değerleri, decoration'lar — doğrulanmış). Aralarındaki fark çekme yöntemidir.

html (varsayılan) innertube
Yöntem GET watch page → ytInitialData brace-aware parse GET (context) + POST /youtubei/v1/next
Çıktı HTML içinden JSON çıkarımı Doğrudan structured JSON
Büyüklük ~1.4MB HTML ~1.2MB JSON
İstek sayısı 1 2 (context + POST)
Kırılganlık noktası ytInitialData anchor + HTML yapısı client version + visitor_data
Stabilite orta (HTML değişebilir) yüksek (API kontratı daha sabit)

Ne zaman innertube kullan? Üretimde, uzun vadeli kullanımda veya HTML parse kırılganlığından kaçınmak istediğinizde. Ek GET maliyeti (~200ms) stability karşılığı kabul edilebilir.

Hızlı başlangıç

CLI — tek video

# Raw 11-karakter ID (varsayılan html backend)
python3 yt_heatmap.py dQw4w9WgXcQ --top 3 --pretty

# Tam URL (watch / youtu.be / shorts / embed desteklenir)
python3 yt_heatmap.py "https://youtu.be/dQw4w9WgXcQ"

# InnerTube backend (structured JSON, daha robust)
python3 yt_heatmap.py dQw4w9WgXcQ --backend innertube

# Ham ytInitialData yapısı (şekillendirme olmadan; her zaman html backend)
python3 yt_heatmap.py dQw4w9WgXcQ --raw

CLI — toplu işleme (batch)

# Çoklu konumsal argüman
python3 yt_heatmap.py ID1 ID2 ID3 --output batch.json --delay 1.5

# Dosyadan (yorum satırları # ile atlanır) + InnerTube backend
python3 yt_heatmap.py -f ids.txt --backend innertube --output batch.json

# stdin pipe
cat ids.txt | python3 yt_heatmap.py --output batch.json

Kütüphane — tek video

from yt_heatmap import extract_heatmap

# Varsayılan html backend
result = extract_heatmap("dQw4w9WgXcQ", top_n=3)

# InnerTube backend
result = extract_heatmap("dQw4w9WgXcQ", top_n=3, backend="innertube")

for seg in result["mostReplayedSegments"]:
    start_s = seg["startMillis"] / 1000
    print(f"Most replayed at {start_s:.1f}s")

Kütüphane — toplu (partial-success)

from yt_heatmap import extract_heatmap_batch

batch = extract_heatmap_batch(
    ["dQw4w9WgXcQ", "ciSzqTlacRM", "BAD_ID"],
    top_n=1,
    delay=1.0,
    backend="innertube",  # opsiyonel; varsayılan 'html'
    on_progress=lambda i, total, vid, status: print(f"[{i}/{total}] {vid} -> {status}"),
)
print(batch["summary"])  # {'total': 3, 'ok': 1, 'failed': 2}
for r in batch["results"]:
    print(r["videoId"], r["status"])  # 'ok' | 'no_heatmap' | 'fetch_error' | 'parse_error' | 'invalid_input'

CLI referansı

yt_heatmap [-h] [--top N] [--pretty] [--raw] [--timeout S] [--batch-file F]
           [--output FILE] [--delay S] [--backend {html,innertube}] videos [videos ...]
Argüman Varsayılan Açıklama
videos (konumsal, nargs=*) — Video ID/URL'leri. Birden fazla → batch modu.
--top N kapalı Yoğunluğa göre ilk N tepe noktasını da döndür (topPeaks).
--pretty kapalı JSON çıktısını girintili yaz.
--raw kapalı Ham markersList JSON'u (şekillendirme yok). Yalnızca tek video, her zaman html backend.
--timeout S 20 HTTP istek başına zaman aşımı (saniye).
--batch-file F, -f F — ID/URL listesi dosyası. Batch modunu etkinleştirir.
--output FILE, -o FILE — JSON'u stdout yerine dosyaya yaz (batch modu).
--delay S 1.0 Batch'te istekler arası bekleme (rate-limit). Son istekten sonra uyumaz.
--backend {html,innertube} html Çekme stratejisi: html (ytInitialData scrape) veya innertube (POST /youtubei/v1/next, structured JSON, daha robust).

Mod seçimi mantığı: 1 girdi → tek modu; >1 girdi → batch modu. Stdin yalnızca konumsal argüman ve --batch-file olmadığında okunur (cron/subshell uyumlu).

Exit kodları: 0 başarılı; 2 hata (tek modda herhangi bir hata; batch modda yalnızca tüm girdiler başarısızsa).

Kütüphane API'si

extract_heatmap(url_or_id, *, top_n=None, timeout=20, user_agent=..., backend="html") -> dict

Tek video → şekillendirilmiş heatmap dict. backend: "html" veya "innertube". Dönen dict'e backend alanı eklenir (hangi yöntemin kullanıldığını belirtir). Hata durumunda FetchError / ParseError / NoHeatmapError fırlatır.

extract_heatmap_batch(inputs, *, top_n=None, timeout=20, delay=1.0, user_agent=..., backend="html", on_progress=None) -> dict

Çoklu video → partial-success batch. Bir kötü video batch'i bozmaz; her girdi bir status koduyla sonuç üretir. on_progress(i, total, video_id, status) callback'i ilerleme takibi için.

İstisnalar

Sınıf Anlamı
HeatmapError Temel sınıf
FetchError HTTP/ağ hatası
ParseError ytInitialData bulunamadı/parse edilemedi (html) veya InnerTube JSON geçersiz (innertube)
NoHeatmapError Sayfa geldi ama heatmap yok (bkz. eligibility eşiği)

Sabitler

STATUS_OK, STATUS_NO_HEATMAP, STATUS_FETCH_ERROR, STATUS_PARSE_ERROR, STATUS_INVALID_INPUT — batch sonuç durum kodları.

Çıktı şeması

Tek video (extract_heatmap, CLI tek modu)

{
  "videoId": "dQw4w9WgXcQ",
  "fetchedAt": "2026-07-28T09:08:16.978200+00:00",
  "hasHeatmap": true,
  "backend": "html",                 // "html" | "innertube" — hangi yöntem kullanıldı
  "durationMillis": 214000,        // heatmap'in kapsadığı toplam süre
  "durationSeconds": 214.0,
  "segmentCount": 100,              // her zaman 100 eşit süreli kutu
  "segmentDurationMillis": 2140,
  "heatmapMetadata": { "maxHeightDp": 40, "minHeightDp": 4, "showHideAnimationDurationMillis": 200 },
  "segments": [                     // 100 adet; video-içi normalize yoğunluk dizisi
    { "startMillis": 0, "endMillis": 2140, "durationMillis": 2140, "intensityScoreNormalized": 1 },
    { "startMillis": 2140, "endMillis": 4280, "durationMillis": 2140, "intensityScoreNormalized": 0.42 }
    // ...
  ],
  "mostReplayedSegments": [         // oynatma çubuğunda "Most replayed" etiketli ayrık zirveler
    {
      "startMillis": 0, "endMillis": 6420,
      "decorationTimeMillis": 2140,
      "label": "Most replayed",
      "icon": "UNKNOWN"
    }
  ],
  "topPeaks": [                     // yalnızca --top N ile; yoğunluğa göre sıralı segmentler
    { "startMillis": 0, "endMillis": 2140, "durationMillis": 2140, "intensityScoreNormalized": 1 }
  ]
}

Batch (extract_heatmap_batch, CLI batch modu)

{
  "fetchedAt": "2026-07-28T09:27:07+00:00",
  "summary": { "total": 3, "ok": 1, "failed": 2 },
  "results": [
    {
      "input": "dQw4w9WgXcQ",            // orijinal girdi
      "videoId": "dQw4w9WgXcQ",          // normalize edilmiş ID (veya null)
      "ok": true,
      "status": "ok",                     // 'ok'|'no_heatmap'|'fetch_error'|'parse_error'|'invalid_input'
      "result": { /* tek video şeması, backend alanı dahil */ },
      "error": null
    },
    {
      "input": "ciSzqTlacRM", "videoId": "ciSzqTlacRM",
      "ok": false, "status": "no_heatmap", "result": null,
      "error": "No entity with markerType='MARKER_TYPE_HEATMAP' found. ..."
    }
  ]
}

intensityScoreNormalized nasıl okunur

  • 0.0–1.0 float, o video içinde normalize edilir.
  • Tepe noktası = 1.0 (video'nun en çok tekrarlanan anı).
  • Videolar arası karşılaştırılamaz — bir videodaki 0.5 ile diğerindeki 0.5 aynı mutlak tekrar sıklığını ifade etmez.

Eligibility: hangi videolar heatmap içerir?

Heatmap eligibility'si süre ve izlenmenin birleşik eşiğidir — ikisi birden belirler. 80+ video üzerinde doğrulandı (2026-07-28).

Üç bölge

Bölge Süre İzlenme Heatmap Açıklama
Kesin NO ≤31s irrelevant ❌ 75M izlenmeye kadar test edildi, hiçbiri heatmap almadı. Segment başına <0.32s — muhtemelen yetersiz veri.
Gri bölge 32-44s algoritmik ⚠️ YouTube'un pipeline'ı video bazında karar veriyor. 32s/7M→✅ ama 34s/5.5M→❌; 36s/575K→✅ ama 36s/112K→❌; 39s/1.4M→❌. Net kural yok.
Kesin YES ≥41s ≥50K ✅ Her zaman 100 segment. 41s/52K→✅ ile doğrulandı; ≥45s tüm testlerde YES.
İzlenme yetersiz ≥41s <50K ❌ Süre yeterli ama izlenme eşiği altında.

İzlenme eşiği (≥41s videolar için)

İzlenme Heatmap
< ~48.900 ❌ yok
~48.900 – ~50.450 ⚠️ geçiş bandı (belirsiz)
> ~50.450 ✅ var (100 segment)

Doğrulanan sınır noktaları: 46.920→YOK, 48.932→YOK, 50.451→VAR, 51.082→VAR, 60.958→VAR. Ayrıca 41s bandında: 41s/52.681→VAR, 41s/9.227→YOK.

Shorts ile ilgili önemli not

Shorts feed'e özel videolar heatmap almaz hipotezi çürütüldü. isShortsEligible=False olan videolar bile heatmap alıyor (ör. 32s/7M KATSEYE teaser). Watch page URL (/watch?v=) hiçbir videoyu /shorts/'a redirect etmez — tüm testler watch page üzerinden yapıldı. Belirleyici faktör süre + izlenme, Shorts işareti değil.

Diğer kurallar:

  • Tarih kısıtı yok — 2009'dan (Rickroll) günümüze yüksek izlenmeli tüm videolar veri içerir.
  • Geçiş keskin (≥41s bandında) — kademeli değil. Eşiğin hemen altında sıfır veri, hemen üstünde tam 100 segment.
  • Eşik altındaki videolar NoHeatmapError (tek mod) veya status: "no_heatmap" (batch) döndürür.

Nasıl çalışır

html backend (varsayılan)

  1. https://www.youtube.com/watch?v=ID sayfasını CONSENT=YES+cb çerezi + browser User-Agent ile çeker.
  2. HTML'deki ytInitialData JSON blob'unu brace-aware çıkarır (regex değil — }; truncation bug'ından kaçınır).
  3. JSON ağacında markerType == "MARKER_TYPE_HEATMAP" olan macroMarkersListEntity'yi özyinelemeli arar (sabit mutations[] indeksi kullanmaz).
  4. 100 segment + "Most replayed" dekorasyonlarını şekillendirilmiş çıktıya dönüştürür.

innertube backend

  1. Watch page'i bir kez çeker ve INNERTUBE_API_KEY, INNERTUBE_CONTEXT_CLIENT_VERSION, VISITOR_DATA değerlerini ytcfg'den scrape eder (hardcode yok — anahtar rotasyonuna dayanıklı).
  2. POST /youtubei/v1/next?key=... uç noktasına {"context":{"client":{...}},"videoId":"..."} payload'u gönderir.
  3. Yanıt, html backend ile aynı frameworkUpdates.entityBatchUpdate yapısını içerir — aynı find_heatmap_markers_list() fonksiyonu çalışır.
  4. Aynı şekillendirme uygulanır, çıktıya backend: "innertube" eklenir.

Her iki backend de ortak find_heatmap_markers_list() + shape_result() paylaşır; sadece veri çekme aşaması farklıdır.

Sınırlamalar ve uyarılar

  • Kırılgan: YouTube dahili yapıyı belgelenmemiş tutar; yer değiştirebilir (daha önce markersMap → frameworkUpdates taşınmıştı). YouTube tekrar taşırsa, arama yüksek sesle başarısız olur — sessizce yanlış veri dönmez.
  • ToS: YouTube'un Hizmet Şartları büyük ölçekli otomatik kazımayı kısıtlayabilir. --delay ile rate-limit uygulayın, sorumlu kullanın.
  • Bot koruması: curl/urllib ile şu an HTTP 200 alınabiliyor, ancak yüksek hacim IP bloğu riski taşır.
  • Tutarlılık: intensityScoreNormalized video-içi normalize'dir; mutlak tekrar sayısı verilmiyor.
  • InnerTube ek maliyeti: innertube backend'i her video için 2 istek yapar (context + POST); batch'te bu ~2x istek demektir.

Lisans

Eğitim/research amaçlı. YouTube ile ilişkili değildir.

About

Extract YouTube 'most replayed' (heatmap) data via ytInitialData or InnerTube /next

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages