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 → heatmapyolu artık çalışmıyor. Bu araç güncel konumu hedefler:frameworkUpdates.entityBatchUpdate.mutations → macroMarkersListEntity (MARKER_TYPE_HEATMAP).
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_heatmapTest 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 -vpytest yoksa standalone fallback mevcuttur (fixture gerektirmeyen testler koşar):
python3 test_yt_heatmap.pyHer 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.
# 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# Ç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.jsonfrom 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")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'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).
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.
| 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) |
STATUS_OK, STATUS_NO_HEATMAP, STATUS_FETCH_ERROR, STATUS_PARSE_ERROR, STATUS_INVALID_INPUT — batch sonuç durum kodları.
{
"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. ..."
}
]
}- 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.
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 | 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 | Heatmap |
|---|---|
| < ~48.900 | ❌ yok |
| ~48.900 – ~50.450 | |
| > ~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 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) veyastatus: "no_heatmap"(batch) döndürür.
https://www.youtube.com/watch?v=IDsayfasınıCONSENT=YES+cbçerezi + browser User-Agent ile çeker.- HTML'deki
ytInitialDataJSON blob'unu brace-aware çıkarır (regex değil —};truncation bug'ından kaçınır). - JSON ağacında
markerType == "MARKER_TYPE_HEATMAP"olanmacroMarkersListEntity'yi özyinelemeli arar (sabitmutations[]indeksi kullanmaz). - 100 segment + "Most replayed" dekorasyonlarını şekillendirilmiş çıktıya dönüştürür.
- Watch page'i bir kez çeker ve
INNERTUBE_API_KEY,INNERTUBE_CONTEXT_CLIENT_VERSION,VISITOR_DATAdeğerlerini ytcfg'den scrape eder (hardcode yok — anahtar rotasyonuna dayanıklı). POST /youtubei/v1/next?key=...uç noktasına{"context":{"client":{...}},"videoId":"..."}payload'u gönderir.- Yanıt, html backend ile aynı
frameworkUpdates.entityBatchUpdateyapısını içerir — aynıfind_heatmap_markers_list()fonksiyonu çalışır. - 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.
- Kırılgan: YouTube dahili yapıyı belgelenmemiş tutar; yer değiştirebilir (daha önce
markersMap→frameworkUpdatestaşı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.
--delayile 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:
intensityScoreNormalizedvideo-içi normalize'dir; mutlak tekrar sayısı verilmiyor. - InnerTube ek maliyeti:
innertubebackend'i her video için 2 istek yapar (context + POST); batch'te bu ~2x istek demektir.
Eğitim/research amaçlı. YouTube ile ilişkili değildir.
{ "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 } ] }