web-scraper / app /extractor.py
Claude
Claude Sonnet 5
Add rate limiting, tests, better extraction, and HF auto-deploy
ebab0e2 unverified
Raw History Blame Contribute Delete
1.68 kB
import re
from typing import Optional, Tuple
import trafilatura
from bs4 import BeautifulSoup
def extract_content(html: str, url: Optional[str] = None) -> Tuple[str, str]:
"""Extract a title and the main readable text from a page's HTML.
Uses trafilatura (a readability-style extractor) as the primary
strategy, since it identifies and drops boilerplate (nav, ads, social
widgets, footers) far more reliably than a class/id substring match.
Falls back to a plain BeautifulSoup text dump for pages trafilatura
can't parse at all (e.g. near-empty pages), so the API still returns
something rather than an error.
"""
document = trafilatura.bare_extraction(
html,
url=url,
include_comments=False,
include_tables=False,
favor_recall=True,
with_metadata=True,
)
text = (document.text or "").strip() if document else ""
title = (document.title or "").strip() if document else ""
if not text:
title, text = _fallback_extract(html)
if not title:
title = _title_from_html(html)
return title or "No title found", text
def _title_from_html(html: str) -> str:
soup = BeautifulSoup(html, "lxml")
if soup.title and soup.title.string:
return soup.title.string.strip()
return ""
def _fallback_extract(html: str) -> Tuple[str, str]:
soup = BeautifulSoup(html, "lxml")
title = _title_from_html(html)
for tag in soup(["script", "style", "iframe", "noscript", "meta", "link", "svg"]):
tag.decompose()
text = soup.get_text(separator="\n", strip=True)
text = re.sub(r"\n{3,}", "\n\n", text)
return title, text