getw turns a web page into a source-grounded semantic document and derives
LLM-ready text from that document.
getw https://example.com/articleThe default output is compact Markdown on stdout. It keeps headings, lists, quotes, code, tables, and essential provenance without carrying the token cost of raw HTML or a full JSON AST.
getw separates four concerns:
- Load the source with verified static HTTP first.
- When static extraction looks like a JavaScript shell, optionally render the page with Playwright and wait for meaningful DOM stability.
- Use Trafilatura to select the main content and normalize its structure into getw's versioned, provider-neutral document tree.
- Deterministically lower that tree to compact Markdown, regular Markdown, plain grounding text, or lossless normalized JSON.
No LLM is called during normal extraction. Optional semantic enrichment is a separate, explicit operation and never replaces the structural document.
See the architecture notes for the complete rationale.
Install the core static extractor:
uv tool install "getw @ git+https://github.com/dollce/getw.git"Install browser rendering support for JavaScript-heavy pages:
uv tool install "getw[browser] @ git+https://github.com/dollce/getw.git"
python -m playwright install chromiumgetw also tries an installed Chrome or Edge channel when Playwright's pinned
Chromium is unavailable.
For local development:
git clone https://github.com/dollce/getw.git
cd getw
uv sync --extra browser --extra testOptional source-grounded LLM enrichment uses a separate extra:
uv sync --extra semanticThe default path writes compact LLM input to stdout:
getw https://example.com/articleChoose a deterministic output projection without fetching the page again:
getw https://example.com/article --format markdown
getw https://example.com/article --format plain
getw https://example.com/article --format json --prettyControl acquisition when reproducibility or a specific page requires it:
# Never launch a browser
getw https://example.com/article --render static
# Require a browser-rendered DOM
getw https://example.com/app --render browser
# Require a meaningful element before DOM stability is evaluated
getw https://example.com/app --wait-for "main article"
# Use an installed system browser channel
getw https://example.com/app --browser-channel chromeRead supplied HTML explicitly. A plain Python/CLI string is never guessed to be HTML:
getw .\page.html --html --base-url https://example.com/ --format markdown
Get-Content .\page.html -Raw | getw - --html --base-url https://example.com/Save any output projection:
getw https://example.com/article --format json -o article.getw.jsonDiagnostics and warnings go to stderr; extracted content goes to stdout. Add
--verbose to see every static/browser attempt, selection reason, duration,
character count, and quality score.
The common path has one function and one stable return type:
import getw
document = getw.extract("https://example.com/article")
llm_input = document.textHTML is explicit, so it cannot be mistaken for a URL:
document = getw.extract(
getw.Html(
html_source,
base_url="https://example.com/article",
)
)One extraction can be lowered repeatedly:
compact = document.render("compact") # essential provenance + compact Markdown
markdown = document.render("markdown")
plain = document.render("plain") # stable LangExtract grounding string
full_ir = document.to_json(indent=2) # normalized structure + diagnostics
restored = getw.Document.from_json(full_ir)
assert restored == documentThe async facade runs the same contract without blocking an asyncio caller:
document = await getw.aextract(url, render="auto")Advanced callers can retain a configured Extractor and inject alternative
loaders without changing the public Document model:
config = getw.ExtractionConfig(
load_mode="auto",
wait_for="main article",
timeout=20,
)
extractor = getw.Extractor(config)
document = extractor.extract(url)Structural web extraction and domain-specific fact extraction are different
problems. getw therefore keeps LLM enrichment opt-in:
import getw
document = getw.extract("https://example.com/news")
task = getw.SemanticTask(
instruction="Extract organizations exactly as written in the source.",
model_id="gemini-3.5-flash",
examples=(
getw.SemanticExample(
text="Acme announced a new product.",
extractions=(
getw.SemanticExtraction("organization", "Acme"),
),
),
),
)
enriched = getw.enrich(document, task)
annotation = enriched.annotations[0]
print(annotation.text, annotation.targets)The adapter passes one stable plain-text projection to LangExtract. Returned character intervals are mapped back to getw node-local ranges. Ungrounded model output is dropped by default. The original structural IR remains unchanged.
The default LLM representation. It includes the final source URL and useful metadata, preserves block semantics with minimal Markdown, and omits repeated inline link targets. It never summarizes, rewrites, or silently truncates.
The extracted body as interoperable Markdown. Link targets are retained. Headerless source tables use fenced TSV rather than inventing a header row.
A stable text sequence used for source grounding. It intentionally has fewer structural cues than Markdown.
The versioned normalized document, metadata, semantic annotations, notices, and load attempts. It is lossless with respect to getw's selected/normalized content, not a byte-for-byte archive of the original HTTP response.
auto starts with static HTTP. It escalates only when extraction is empty or a
versioned shell signal is present (for example an empty React/Next/Nuxt root,
bundled-script shell, or explicit --wait-for selector).
Browser loading waits for DOMContentLoaded, then either the requested visible
selector or a stable signature of visible text and DOM node count. It does not
use a fixed sleep and does not depend on networkidle. Every attempted path and
the selected path are recorded in the result.
If an obvious empty shell needs rendering but no browser capability exists,
getw fails with an installation hint instead of emitting Loading... as page
content. Interactive login and CAPTCHA challenges are not bypassed.
Version 2 is intentionally a web text extractor. The earlier PDF, DOCX, PPTX, XLSX, OCR, and general file-conversion code was removed rather than carried into an unrelated core. Supplied HTML remains supported because it is the replay and integration boundary for authenticated or externally automated pages.
MIT