This document explains the provider-based architecture for extracting platform-specific content (such as YouTube transcripts, GitHub READMEs, or Reddit comments) within the MindCache system.
Traditional HTML parsers fail on modern, JavaScript-rendered platforms like YouTube and X (Twitter). Instead of scattering conditional domain checks throughout the document processor, MindCache uses a provider-based extraction pipeline that delegates processing to platform-specific classes.
graph TD
URL[Target URL] --> Factory[ExtractorFactory]
Factory -->|youtube.com / youtu.be| YT[YouTubeExtractor]
Factory -->|x.com / twitter.com| X[XExtractor]
Factory -->|github.com| GH[GitHubExtractor]
Factory -->|reddit.com / redd.it| RD[RedditExtractor]
Factory -->|*.pdf / arxiv.org/pdf| PDF[PDFExtractor]
Factory -->|google.com/search| GS[GoogleSearchExtractor]
Factory -->|Other Domains| Gen[GenericExtractor]
YT --> Result[ExtractionResult]
X --> Result
GH --> Result
RD --> Result
PDF --> Result
GS --> Result
Gen --> Result
Result --> DB[Document Database]
The extraction system lives in the app/services/extractors/ package and consists of these core components:
Abstract base class defined in base.py with one abstract method:
async def extract(self, url: str) -> ExtractionResultStrongly-typed container carrying extracted content and metadata:
| Field | Type | Description |
|---|---|---|
content |
str | Clean extracted text (Markdown) |
title |
str | null |
author |
str | null |
published_date |
datetime | null |
source_type |
str | Source identifier (Generic, YouTube, GitHub, Reddit, X, PDF) |
platform_metadata |
dict | Structured JSON metadata specific to the platform |
final_url |
str | null |
Centralized registry in factory.py that maps URL domains to extractors. Resolution logic:
- PDF check: URL ends with
.pdf, or path contains/pdf/(arxiv, generic) - Google Search check: hostname is
google.comand path starts with/search - Direct domain lookup: exact match in
_registry - Subdomain fallback: strip
www.orm.prefix from hostname - Generic fallback:
GenericExtractorfor all unmatched domains
class ExtractorFactory:
_registry = {
"youtube.com": YouTubeExtractor(),
"youtu.be": YouTubeExtractor(),
"x.com": XExtractor(),
"twitter.com": XExtractor(),
"github.com": GitHubExtractor(),
"reddit.com": RedditExtractor(),
"old.reddit.com": RedditExtractor(),
"redd.it": RedditExtractor(),
}
@classmethod
def get_extractor(cls, url: str) -> ContentExtractorSeven platform-specific extractors plus one generic fallback.
Purpose: Fallback extractor for standard websites. Uses Trafilatura as primary with BeautifulSoup4 structural extraction as fallback.
Source file: generic.py
Extraction process:
- Downloads page HTML via HTTPX (with urllib fallback for TLS fingerprinting bypass)
- Runs Trafilatura for main content + metadata extraction
- If Trafilatura returns empty, runs
_fallback_extract()— a custom BS4 extractor that:- Extracts title (from
<title>,og:title,twitter:title) - Extracts meta description, keywords
- Extracts all headings (h1-h6) as structural outline
- Extracts JSON-LD schema data
- Converts body content to Markdown (preserving headings, lists, code blocks, tables)
- Extracts title (from
- Constructs rich searchable content: Title + Meta Description + Keywords + Headings + Schema Data + Main Content
Purpose: Extracts YouTube video metadata and transcripts.
Source file: youtube.py
ID Extraction: Handles all YouTube URL formats:
youtube.com/watch?v=VIDEO_IDyoutu.be/VIDEO_IDyoutube.com/embed/VIDEO_IDyoutube.com/shorts/VIDEO_IDm.youtube.com/watch?v=VIDEO_IDyoutube.com/live/VIDEO_ID
Extraction process:
- Metadata: Uses
yt-dlpin skip-download mode (yt_dlp.YoutubeDL({"format": "best", ...})) to retrieve:- Title, channel, description, tags, categories
- Duration, upload date, view count
- Transcript: Uses
youtube-transcript-api(YouTubeTranscriptApi.get_transcript()) to fetch captions - Timestamp formatting: Transcript segments are formatted with
[MM:SS]timestamps every 30 seconds:[00:00] Welcome to this video [00:30] Today we'll discuss vector databases [01:00] FAISS is Meta's library for... - Content assembly: Title + Description + Transcript text (with timestamps)
Fallback: If transcript is disabled or missing (Shorts, long videos without captions):
extracted_content= Title + Description + Tagsplatform_metadata.transcript_available = false- Pipeline proceeds gracefully without errors
Platform metadata:
{
"video_id": "dQw4w9WgXcQ",
"duration": 212,
"channel": "Rick Astley",
"tags": ["rickroll", "80s"],
"transcript_available": true
}Source type: "YouTube"
Purpose: Extracts X/Twitter tweet content.
Source file: x.py
Extraction process:
- Converts
x.comortwitter.comURLs tofixupx.com(a public CDN syndication proxy) - Fetches HTML from the syndication URL
- Extracts OpenGraph title and description from HTML meta tags
Fallback: If the syndication API fails (private tweet, deleted record, rate limits):
- Sets
partial_extraction = True extracted_contentuses basic URL parameters (Username, Tweet ID)- Pipeline continues without crashing
Platform metadata:
{
"author": "Jack",
"tweet_id": "20",
"timestamp": "2006-03-21T20:50:14",
"partial_extraction": false
}Source type: "X"
Purpose: Extracts GitHub repository metadata and README content.
Source file: github.py
URL Parsing: Extracts owner and repo name from URL patterns:
github.com/owner/repogithub.com/owner/repo/tree/branch(uses owner/repo)github.com/owner/repo/blob/branch/path(uses owner/repo)
Extraction process:
- Downloads repository page HTML via HTTPX
- Extracts from HTML:
- Description: Meta tags (
og:description,twitter:description) and sidebar description - Topics:
.topic-taglinks (e.g., "python", "pdf", "rust") - Stars: Star count from the repo page
- README: Content from
article.markdown-body(rendered README HTML)
- Description: Meta tags (
- If README not found in page HTML, attempts to fetch raw README from
raw.githubusercontent.com:- Tries
mainbranch first, thenmasterbranch - Supports both
README.mdandREADME.rstextensions
- Tries
- Falls back gracefully if README is unavailable (partial extraction)
Platform metadata:
{
"owner": "karpathy",
"repo_name": "micrograd",
"description": "A tiny scalar-valued autograd engine",
"topics": ["deep-learning", "autograd", "python"],
"stars": 12345,
"readme_available": true,
"partial_extraction": false
}Source type: "GitHub"
Purpose: Extracts Reddit posts and comments.
Source file: reddit.py
URL Handling:
- Normalizes URLs to
old.reddit.com(bypasses Cloudflare) - Strips
.jsonsuffix if present - Resolves
redd.itshort URLs via HTTP redirect
Extraction process:
- Downloads page HTML from
old.reddit.comvia HTTPX - For post pages: Extracts title, subreddit, author, score, post content, and up to 10 top comments with scores
- For listing feeds: Extracts post titles, URLs, and subreddits
Fallback: If HTTP request fails (network error, deleted content):
- Falls back to
GenericExtractorfor the URL
Source type: "Reddit"
Purpose: Extracts PDF document content.
Source file: pdf.py
Extraction process:
- Downloads PDF bytes via HTTPX (with urllib fallback)
- Writes to a temporary file
- Reads metadata + first page text via
pypdf(in a thread to avoid blocking) - Converts full PDF to Markdown using
@pspdfkit/pdf-to-markdownvianpx(Node.js tool) - Cleans up temporary file
Title extraction (in order of priority):
- PDF metadata title field (if not generic/filename-based)
- First page text analysis (first 15 lines checked for title-like patterns)
- Fallback to "PDF Document"
Source type: "PDF"
Purpose: Extracts Google Search result pages.
Source file: google_search.py
Extraction process:
- Parses the
qquery parameter from the URL - Attempts to download Google SERP HTML and extract
<h3>result headings with URLs - If Google scraping fails or returns empty results, falls back to DuckDuckGo Search via
duckduckgo_search.DDGS.text()
Content: Returns top 10 search results with titles, URLs, and snippets.
Source type: "Generic" (treated as generic content, knowledge score check applies)
ExtractorFactory centralizes URL routing. This maintains a clean document processor that doesn't need to know which platform it is interacting with.
# app/services/extractors/factory.py
class ExtractorFactory:
_registry = {
"youtube.com": YouTubeExtractor(),
"youtu.be": YouTubeExtractor(),
"x.com": XExtractor(),
"twitter.com": XExtractor(),
"github.com": GitHubExtractor(),
"reddit.com": RedditExtractor(),
"old.reddit.com": RedditExtractor(),
"redd.it": RedditExtractor(),
}
@classmethod
def get_extractor(cls, url: str) -> ContentExtractor:
# 1. Check if PDF
# 2. Check if Google Search
# 3. Direct domain match
# 4. Subdomain fallback
# 5. GenericExtractor fallbackTo integrate a new platform (e.g., Stack Overflow):
Create a new file in app/services/extractors/ (e.g. stackoverflow.py):
# app/services/extractors/stackoverflow.py
from app.services.extractors.base import ContentExtractor, ExtractionResult
class StackOverflowExtractor(ContentExtractor):
async def extract(self, url: str) -> ExtractionResult:
# 1. Fetch Stack Overflow page HTML
# 2. Extract title, question body, answers, tags
# 3. Handle errors gracefully
return ExtractionResult(
content="Question and answer content...",
title="How to use FAISS in Python?",
author="user123",
source_type="StackOverflow",
platform_metadata={
"tags": ["python", "faiss", "vector-search"],
"score": 42,
"answer_count": 3
}
)Add your class to the package interface file app/services/extractors/__init__.py:
from app.services.extractors.stackoverflow import StackOverflowExtractor
__all__ = [
# ...
"StackOverflowExtractor"
]Add the target hostnames to ExtractorFactory._registry inside app/services/extractors/factory.py:
# app/services/extractors/factory.py
from app.services.extractors.stackoverflow import StackOverflowExtractor
class ExtractorFactory:
_so_extractor = StackOverflowExtractor()
_registry = {
# Existing registrations...
"stackoverflow.com": _so_extractor,
}Add domain matching and parsing tests to tests/test_extractors.py:
async def test_stackoverflow_extractor_success():
extractor = StackOverflowExtractor()
result = await extractor.extract("https://stackoverflow.com/questions/12345")
assert result.source_type == "StackOverflow"
assert result.title is not Noneuv run pytest tests/test_extractors.py