Read any page (markdown, text or HTML)
Read any web page as markdown, text or HTML — one page or many at once, taking a browser only when the page actually needs one.
Also known as: reader, fetch, scrape, page, webpage, url, markdown, extract, crawl, article
Call it
bowmark.read.page(url: string, options?: ReadOptions): Promise<ReadResult>
bowmark.read.pages(urls: string[], options?: ReadOptions): Promise<ReadResult[]>Functions
| Function | What it does |
|---|---|
page | Loads one page and returns its content. |
pages | The same read over many urls, six in flight at a time, results in the order the urls were given. |
Types
type ReadFormat = "markdown" | "text" | "cleanHtml" | "html"
type ReadStrategy = "auto" | "fetch" | "browser"
type ReadOptions = {
format?: ReadFormat // default "markdown"
strategy?: ReadStrategy // default "auto" — plain GET, browser only if needed
maxChars?: number // default 200000; over it, content is cut + truncated:true
timeoutMs?: number // default 20000, per leg
}
type ReadResult = {
url: string // FINAL url after redirects
requestedUrl: string // the url you passed
status: number // 0 = never completed; see error
ok: boolean
title: string | null // <title>, else first <h1>, else null
content: string
format: ReadFormat
servedBy: "fetch" | "browser" // which rung actually paid for this
escalated: boolean // the GET was tried and rejected
escalationReason: string | null // WHY a browser was needed. Set even under
// strategy:"fetch", where one was warranted but
// not taken — so under-reading is visible
chars: number
truncated: boolean
error: string | null // set INSTEAD of throwing; a dead url in pages() never
// costs you the other results
wall: { vendor: string; cleared: boolean } | null // the bot wall this page is
// behind, if a rendered look found one. Reported even
// when we could NOT clear it, so a block is a named
// fact rather than an empty page
warnings: string[]
}Examples
// Read one page as markdown. The default strategy takes the cheap rung and
// only opens a browser if this page turns out to need one.
const page = await bowmark.read.page("https://example.com/pricing");
log(`${page.title} — ${page.chars} chars via ${page.servedBy}`);
return { title: page.title, content: page.content };// Many pages in one script — the shape this exists for. Six run at a time,
// and a dead url comes back as a VALUE, so it never costs you the rest.
const urls = [
"https://a.example.com/docs/intro",
"https://b.example.com/pricing",
"https://c.example.com/changelog",
];
const pages = await bowmark.read.pages(urls, { maxChars: 20000 });
for (const p of pages.filter(p => !p.ok)) log(`failed ${p.requestedUrl}: ${p.error}`);
const ok = pages.filter(p => p.ok);
// Which ones cost a browser — worth knowing before you scale the list up.
log(`${ok.filter(p => p.escalated).length}/${ok.length} needed a browser`);
return ok.map(p => ({ url: p.url, title: p.title, content: p.content }));Providers behind it
None — this capability needs no site. It computes the answer, or reads a public API that publishes it.