Ingesting YouTube Transcripts, Reddit Discussions & Documentation into AI / LLM RAG Pipelines (Python & Node.js)
Retrieval-Augmented Generation (RAG) and AI Agent pipelines are only as smart as the context you feed them. While static company documentation is a great starting point, two of the highest-signal knowledge sources on the
Retrieval-Augmented Generation (RAG) and AI Agent pipelines are only as smart as the context you feed them. While static company documentation is a great starting point, two of the highest-signal knowledge sources on the internet live in YouTube technical talks/podcasts and Reddit community threads (r/LocalLLaMA, r/MachineLearning, r/webscraping, r/programming).
Yet if you try feeding raw YouTube watch page HTML or Reddit DOM trees directly into an LLM (OpenAI GPT-4o, Claude 3.5/3.7 Sonnet, or Gemini), you immediately hit three engineering bottlenecks:
-
Massive Token Bloat: A single raw YouTube or Reddit HTML page weighs
450 KB – 1.8 MB(120,000+tokens of inline scripts, SVGs, tracking JSON, and navigation menus), whereas the actual transcript or discussion text is only1,500 – 4,000tokens. -
Embedded JSON Obfuscation: YouTube stores subtitle track URLs deep inside
ytInitialPlayerResponse.captions.playerCaptionsTracklistRenderer.captionTracks, requiring balanced-brace JSON extraction and XML entity decoding. - Chunking Without Metadata: Splitting raw text without preserving the video timestamp, speaker/channel name, Reddit upvote score, or section hierarchy causes vector search to return unattributed fragments.
In this tutorial, we will build an end-to-end Multi-Source AI & RAG Ingestion Pipeline in Node.js and Python that extracts clean, token-counted JSON and Markdown from YouTube videos, Reddit subreddits/threads, and technical documentation websites.
You can also run all three extractors immediately via REST API on the Apify Store:
- 🎬 YouTube Video Transcript, Metadata, Comments & Channel Email Scraper
- 💬 Reddit Subreddit, Search & Thread to Clean JSON & AI Markdown Scraper
- 📄 Website to Clean Markdown & Text Scraper for AI, LLM & RAG
1. Extracting YouTube Video Transcripts & Metadata Without Headless Browsers
Launching Puppeteer or Playwright just to grab a YouTube video's subtitles wastes 500 MB+ of RAM and takes 8–15 seconds per video. Instead, when you fetch a YouTube watch page over standard HTTP, YouTube embeds a complete JSON payload assigned to var ytInitialPlayerResponse = { ... };.
Inside ytInitialPlayerResponse, we get two critical objects:
-
videoDetailsµformat.playerMicroformatRenderer: ContainsvideoId,title,author,channelId,viewCount,lengthSeconds,publishDate,keywords, and fullshortDescription. -
captions.playerCaptionsTracklistRenderer.captionTracks: An array of subtitle tracks (e.g.,languageCode: "en"), each with abaseUrlpointing to a lightweight XML transcript containing<text start="12.34" dur="3.21">...</text>nodes.
Balanced-Brace Parser for ytInitialPlayerResponse in Node.js
Because ytInitialPlayerResponse is embedded inside a <script> tag alongside other JavaScript statements, a naive regex often fails on nested braces or escaped quotes. Here is a deterministic, zero-dependency balanced-brace parser in Node.js:
export function extractPlayerResponse(html) {
const markers = ['var ytInitialPlayerResponse = ', 'ytInitialPlayerResponse = '];
let startIdx = -1;
for (const marker of markers) {
const pos = html.indexOf(marker);
if (pos !== -1) {
startIdx = html.indexOf('{', pos + marker.length - 1);
break;
}
}
if (startIdx === -1) return null;
let depth = 0;
let inString = false;
let escaped = false;
for (let i = startIdx; i < html.length; i++) {
const ch = html[i];
if (inString) {
if (escaped) escaped = false;
else if (ch === '\\') escaped = true;
else if (ch === '"') inString = false;
} else {
if (ch === '"') inString = true;
else if (ch === '{') depth++;
else if (ch === '}') {
depth--;
if (depth === 0) {
try {
return JSON.parse(html.slice(startIdx, i + 1));
} catch {
return null;
}
}
}
}
}
return null;
}
Decoding Timestamped XML Caption Tracks into RAG Markdown
Once we have playerResp, we select the user's preferred language track (en, es, de, vi, etc.), fetch the XML caption track, decode HTML entities (', ", &), and compute estimatedTokens:
function decodeHtmlEntities(str) {
if (!str) return '';
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, "'")
.replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
.replace(/&#(\d+);/g, (_, dec) => String.fromCharCode(parseInt(dec, 10)));
}
export async function fetchTranscriptSegments(playerResp, preferredLang = 'en') {
const tracks = playerResp?.captions?.playerCaptionsTracklistRenderer?.captionTracks || [];
const selected =
tracks.find((t) => t.languageCode === preferredLang) ||
tracks.find((t) => (t.languageCode || '').startsWith(preferredLang)) ||
tracks[0];
if (!selected?.baseUrl) return { language: null, segments: [] };
const resp = await fetch(selected.baseUrl, {
headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)' },
});
if (!resp.ok) return { language: selected.languageCode, segments: [] };
const xml = await resp.text();
const segments = [];
const regex = /<text\s+start="([\d.]+)"(?:\s+dur="([\d.]+)")?[^>]*>([\s\S]*?)<\/text>/g;
for (const match of xml.matchAll(regex)) {
const start = parseFloat(match[1]);
const duration = match[2] ? parseFloat(match[2]) : 0;
const text = decodeHtmlEntities(match[3].replace(/<[^>]+>/g, ''))
.replace(/\s+/g, ' ')
.trim();
if (text) segments.push({ start, duration, text });
}
return { language: selected.languageCode, segments };
}
2. Converting Reddit Subreddits & Threads into Token-Counted AI Markdown
When building an AI market research agent or developer sentiment tracker, Reddit threads provide unfiltered user feedback. However, scraping www.reddit.com's modern React/Lit web components (<shreddit-post>) differs significantly from old.reddit.com (<div class="thing">) or Reddit's .json API (data.children[].data).
Our Actor tungnguyen84/reddit-subreddit-json-ai-scraper handles all three formats automatically in a unified router:
-
JSON API Feed (
/r/LocalLLaMA.json): ParsesparsedJson.data.children, extractingtitle,subreddit,author,score,num_comments,selftext, andcreated_utc. -
old.reddit.com&www.reddit.comDOM: Extractsdiv.thingandshreddit-postattributes (post-title,score,comment-count,author,content-href) with Cheerio. -
LLM Markdown Formatter: Generates a clean
markdownForAiblock with upvote scores and comment counts so your LLM knows which posts have strong community consensus.
const words = (title + ' ' + selftext).split(/\s+/).filter(Boolean).length;
const markdownForAi = [
`# ${title}`,
`- **Subreddit**: ${subreddit} | **Author**: u/${author} | **Score**: ${score} | **Comments**: ${numComments}`,
`- **URL**: ${permalink}`,
'',
selftext || '_External Link Post_',
].join('\n');
const record = {
id,
subreddit,
title,
author,
score,
numComments,
url,
permalink,
selftext,
wordCount: words,
estimatedTokens: Math.ceil(words * 1.35),
markdownForAi,
scrapedAt: new Date().toISOString(),
};
3. Unified Python RAG Ingestion Pipeline (YouTube + Reddit + Docs -> Vector DB)
Here is a complete Python script that calls all three Apify Actors (youtube-video-transcript-metadata-scraper, reddit-subreddit-json-ai-scraper, and website-to-markdown-ai-rag-llm-scraper), normalizes their markdownForAi / markdown outputs into token-aware chunks, and prepares them for OpenAI embeddings or LangChain / LlamaIndex ingestion:
import os
import requests
from typing import List, Dict
APIFY_TOKEN = os.getenv("APIFY_TOKEN", "YOUR_APIFY_TOKEN")
def run_apify_actor(actor_slug: str, payload: dict) -> List[Dict]:
url = (
f"https://api.apify.com/v2/acts/{actor_slug}/"
f"run-sync-get-dataset-items?token={APIFY_TOKEN}"
)
resp = requests.post(url, json=payload, timeout=180)
resp.raise_for_status()
return resp.json()
def build_multi_source_rag_corpus() -> List[Dict]:
corpus = []
# 1. Ingest YouTube Video Transcripts & Metadata
yt_items = run_apify_actor(
"tungnguyen84~youtube-video-transcript-metadata-scraper",
{
"videoUrls": [{"url": "https://www.youtube.com/watch?v=jNQXAC9IVRw"}],
"preferredLanguage": "en",
"includeTimestamps": True,
"maxVideos": 5,
},
)
for item in yt_items:
corpus.append({
"source_type": "youtube",
"url": item.get("url"),
"title": item.get("title"),
"tokens": item.get("estimatedTokens", 0),
"markdown": item.get("markdownForAi", ""),
})
# 2. Ingest Reddit Subreddit Discussions
reddit_items = run_apify_actor(
"tungnguyen84~reddit-subreddit-json-ai-scraper",
{
"startUrls": [{"url": "https://old.reddit.com/r/programming/"}],
"maxPosts": 15,
},
)
for item in reddit_items:
corpus.append({
"source_type": "reddit",
"url": item.get("permalink"),
"title": item.get("title"),
"tokens": item.get("estimatedTokens", 0),
"markdown": item.get("markdownForAi", ""),
})
# 3. Ingest Technical Documentation Pages
doc_items = run_apify_actor(
"tungnguyen84~website-to-markdown-ai-rag-llm-scraper",
{
"startUrls": [{"url": "https://crawlee.dev"}],
"maxPagesPerCrawl": 5,
},
)
for item in doc_items:
corpus.append({
"source_type": "documentation",
"url": item.get("url"),
"title": item.get("title"),
"tokens": item.get("estimatedTokens", 0),
"markdown": item.get("markdown", ""),
})
return corpus
if __name__ == "__main__":
documents = build_multi_source_rag_corpus()
total_tokens = sum(d["tokens"] for d in documents)
print(f"Ingested {len(documents)} clean RAG documents ({total_tokens} total tokens)")
for doc in documents[:5]:
print(f"[{doc['source_type'].upper()}] {doc['title']} ({doc['tokens']} tokens)")
📊 Performance & Token Savings Benchmark
| Data Source | Raw HTML Size | Raw Token Count | Cleaned RAG Markdown | Token Reduction |
|---|---|---|---|---|
| YouTube Watch Page | 980 KB |
~210,000 tokens |
1.8 KB – 14 KB
|
97.8% reduction |
| Reddit Subreddit Feed | 640 KB |
~145,000 tokens |
12 KB (25 posts) |
96.5% reduction |
| Documentation Portal | 310 KB |
~68,000 tokens |
4.5 KB |
93.4% reduction |
By stripping layout boilerplate and extracting structured JSON/Markdown before hitting your embedding model (text-embedding-3-small or text-embedding-3-large), you cut vector database storage and LLM prompt costs by over 95% while dramatically improving retrieval precision.
🚀 Explore All 9 Live Apify Store Actors & REST APIs
Apify Store Actors
- 🎬 YouTube Video Transcript, Metadata, Comments & Channel Email Scraper
- 💬 Reddit Subreddit, Search & Thread to Clean JSON & AI Markdown Scraper
- 📄 Website to Clean Markdown & Text Scraper for AI, LLM & RAG
- 🌍 Google Maps & Local Business Lead, Email, Phone & Social Extractor
- 💻 GitHub Developer, Contributor & Stargazer B2B Lead Scraper
- 🏢 B2B Website Email, Phone & Social Media Contact Scraper
- 🛍️ Shopify Store Products, Prices & B2B Contact Email Scraper
- 🛒 WooCommerce & WordPress Store Product, Price & Contact Scraper
- 🔍 Website SEO Audit, Meta Tags, Headings & Contact Email Scraper
RapidAPI Hub REST APIs (Free Tier Available)
- Website to Clean Markdown AI, LLM & RAG Converter API
- B2B Website Email, Phone & Social Scraper API
- Shopify Store Product, Price & Contact Scraper API
- WooCommerce & WordPress Product, Price & Contact Spy API
- Website Sitemap, SEO Audit & Contact Scraper API
Offline Python CLI & Excel VBA Automation Packages (Payhip Store)
- Python B2B Lead Scraper & Excel VBA Suite Pro (
$14.99) - Shopify Competitor Price & Product Spy Pro (
$12.99) - Ultimate Excel VBA Automation Pack: 10 One-Click Macros (
$9.99)
Need custom AI data ingestion pipelines, RAG scrapers, or workflow automation? Reach out on Fiverr (@tungnguyen84)!
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.