scrape

package
v0.10.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 1, 2026 License: MIT Imports: 22 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// SourceHTTP — page is not a JS shell; plain HTTP fetch is authoritative.
	SourceHTTP = "http"
	// SourceHTTPShell — page is a JS shell but rendering wasn't possible
	// (no browser configured, or browser fetch failed). Always invalid as a
	// cache hit when a browser is now available, so we retry rendering.
	SourceHTTPShell = "http_shell"
	// SourceBrowser — page was rendered via the headless browser.
	SourceBrowser = "browser"
)

Source identifies how a Page was fetched. Stored in the cache so we can invalidate stale entries (notably JS-shell extractions cached before the user configured a browser) without churning entries for plain HTTP pages that don't need rendering.

View Source
const MaxBodyBytes = 20 << 20 // 20 MiB

MaxBodyBytes caps how much of an HTTP response body we will read. Prevents a malicious or misconfigured server from OOMing the process.

Variables

View Source
var (
	// ErrBadSelector wraps selector-extraction failures (typically an invalid
	// CSS selector) — a caller-input problem, not an upstream fault.
	ErrBadSelector = errors.New("selector extraction failed")
	// ErrSelectorNoMatch reports that the selector matched no elements.
	ErrSelectorNoMatch = errors.New("no elements matched selector")
)

Sentinel errors for selector scrapes so callers can classify failures (CLI exit codes, MCP error kinds) without string matching.

View Source
var ErrNoBrowser = errors.New("no browser configured (set with: ketch config set browser chrome)")

ErrNoBrowser is returned when browser rendering is needed but not configured.

Functions

func CacheStaleForBrowser

func CacheStaleForBrowser(source string, hasBrowser bool) bool

CacheStaleForBrowser reports whether a cache entry with the given source should be bypassed because rendering via the browser would do better. True when the cached entry is a known unrendered JS shell, or when it predates source tracking and a browser is now available (one-time migration for pre-source caches where the entry might be unrendered shell garbage).

func ContentHash

func ContentHash(s string) string

ContentHash returns the first 16 hex chars of the sha256 of s.

func FetchLLMSTxt

func FetchLLMSTxt(ctx context.Context, baseURL string) (string, bool)

FetchLLMSTxt attempts to fetch /llms.txt from the given base URL. It only probes bare domains (path empty or "/") and returns the content and true on success. All errors are silently swallowed — this is a best-effort check.

func InstallBrowser

func InstallBrowser() (string, error)

InstallBrowser downloads Chromium to the ketch cache directory.

func ResolveBrowserBin

func ResolveBrowserBin(configured string) (string, error)

ResolveBrowserBin resolves a browser configuration value to an absolute path. Accepts "chrome", "chromium" (searched in PATH), or an absolute path.

Types

type BrowserConn

type BrowserConn interface {
	// Fetch navigates to a URL and returns the rendered HTML. The context
	// bounds navigation and JS settling; cancellation unblocks the caller.
	Fetch(ctx context.Context, url string) (html string, err error)
	// Close releases browser resources.
	Close()
}

BrowserConn represents a connection to a headless browser for JS rendering.

func NewBrowserConn

func NewBrowserConn(binPath string) (BrowserConn, error)

NewBrowserConn launches a headless browser and returns a connection.

type FetchResult

type FetchResult struct {
	Page        *Page
	RawHTML     string
	NotModified bool
	JSDetection string // "static", "likely_shell", "ambiguous"

	// Doc is the parsed document that ScrapeConditional used for JS-shell
	// detection. Downstream callers (e.g. link extraction during a crawl)
	// can reuse it to avoid re-parsing the same HTML. Nil when the page
	// was re-fetched via the browser — in that case RawHTML is the
	// rendered HTML and needs a fresh parse.
	Doc *goquery.Document

	// Source is the fetch path that produced Page (SourceHTTP or SourceBrowser).
	Source string
}

FetchResult holds the output of a conditional scrape.

type Page

type Page struct {
	URL          string `json:"url"`
	FetchedURL   string `json:"fetched_url,omitempty"`
	Title        string `json:"title"`
	Markdown     string `json:"markdown"`
	ETag         string `json:"etag,omitempty"`
	LastModified string `json:"last_modified,omitempty"`
	ContentHash  string `json:"content_hash,omitempty"`
}

Page represents a scraped web page.

type PageCache

type PageCache interface {
	Get(url string) (*Page, string)
	Put(url string, page *Page, source string)
	GetRaw(url string) (rawHTML, source string, page *Page)
	PutRaw(url string, page *Page, source, rawHTML string)
}

PageCache is the subset of the cache API the scrape pipeline needs. *cache.Cache implements it (the cache package imports scrape, so the dependency points this way via an interface). Callers may pass nil to bypass caching entirely.

type Scraper

type Scraper struct {
	// contains filtered or unexported fields
}

Scraper fetches web pages and extracts content as markdown.

func New

func New() *Scraper

New creates a Scraper with defaults.

func NewFromConfig

func NewFromConfig(cfg *config.Config) (*Scraper, error)

NewFromConfig builds a Scraper from cfg: compiled URL rewriter, optional browser binary, and operator-configured SPA markers. The rewriter's regexes are compiled once here — callers should construct one Scraper and reuse it. The returned Scraper is safe for concurrent use and must be Closed by the caller.

func NewWithBrowser

func NewWithBrowser(browserBin string) *Scraper

NewWithBrowser creates a Scraper with browser fallback for JS-rendered pages.

func NewWithBrowserConn

func NewWithBrowserConn(conn BrowserConn, rw *urlrewrite.Rewriter) *Scraper

NewWithBrowserConn creates a Scraper backed by a pre-supplied BrowserConn, bypassing binary resolution. HasBrowser reports true and getBrowser returns conn directly. Used to drive browser code paths (e.g. --force-browser) without a real Chrome install.

func NewWithConfig

func NewWithConfig(browserBin string, rw *urlrewrite.Rewriter, spaMarkers []string) *Scraper

NewWithConfig creates a Scraper like NewWithRewriter but folds operator- configured SPA markers (config spa_markers) into the JS-shell detector in addition to the built-in markers. Pass nil/empty spaMarkers for built-ins only.

func NewWithRewriter

func NewWithRewriter(browserBin string, rw *urlrewrite.Rewriter) *Scraper

NewWithRewriter creates a Scraper with an optional browser binary and optional URL rewriter. Pass "" for browserBin to disable browser fallback; pass nil rewriter to disable URL rewriting. The detector uses only the built-in SPA markers; use NewWithConfig to add operator-configured markers.

func (*Scraper) BrowserScrape

func (s *Scraper) BrowserScrape(ctx context.Context, rawURL string) (*Page, string, error)

BrowserScrape fetches a URL using the browser directly. Used when a host is known to require browser rendering.

func (*Scraper) CachedScrape

func (s *Scraper) CachedScrape(ctx context.Context, pc PageCache, url string) (*Page, error)

CachedScrape checks the cache first, falls back to fetch+extract. Hits are bypassed when the entry was extracted from an unrendered JS shell and a browser is now available to do better, or when the entry predates source tracking (a one-time migration once a browser is configured). The cache is keyed by the rewritten URL so original and rewritten URLs share one cache entry.

func (*Scraper) CachedScrapeForce

func (s *Scraper) CachedScrapeForce(ctx context.Context, pc PageCache, url string) (*Page, error)

CachedScrapeForce is the forced-browser markdown path. It always renders via the browser, reusing a cache entry only when that entry is itself a browser render (force-browser selects the rendering pipeline, not cache freshness — bypass the cache for that). HTTP/shell/markdown-only entries never satisfy a forced request, which is precisely the anti-poisoning guard.

func (*Scraper) CachedScrapeRaw

func (s *Scraper) CachedScrapeRaw(ctx context.Context, pc PageCache, url string) (*Page, string, string, error)

CachedScrapeRaw is the raw-HTML path. It routes through ScrapeConditional so one fetch yields Page + RawHTML + Source (the markdown path's Scrape discards the body). Raw lookup is a hit only when RawHTML is non-empty — a markdown-only entry does not poison a raw request. On a raw miss against an existing markdown entry, the refetch back-fills RawHTML while preserving the cached Page (one fetch, both representations cached). A nil pc skips cache read/write and returns the fresh fetch result directly.

func (*Scraper) CachedScrapeRawForce

func (s *Scraper) CachedScrapeRawForce(ctx context.Context, pc PageCache, url string) (*Page, string, string, error)

CachedScrapeRawForce is the forced-browser raw path: render unconditionally and emit the rendered HTML. A cache hit is honored only for a prior browser render (GetRaw already requires non-empty RawHTML). BrowserScrape runs extractor.Extract internally; the resulting markdown Page is unused here, but that work is dwarfed by render cost — don't split the API to avoid it.

func (*Scraper) Close

func (s *Scraper) Close()

Close releases browser resources if any.

func (*Scraper) Fetch

func (s *Scraper) Fetch(ctx context.Context, rawURL string) (string, error)

Fetch fetches the raw HTML for a URL without extraction or browser fallback.

func (*Scraper) HasBrowser

func (s *Scraper) HasBrowser() bool

HasBrowser reports whether this scraper has browser fallback configured. Guarded by browserMu because getBrowser clears browserBin on failed resolution — concurrent callers (MCP tool calls, multi-URL scrapes) must not race that write.

func (*Scraper) MaybeBrowserFetch

func (s *Scraper) MaybeBrowserFetch(ctx context.Context, rawURL, html string) (string, string)

MaybeBrowserFetch re-fetches rawURL via the browser if html looks JS-rendered. Returns the (possibly rendered) html and the fetch source actually used.

func (*Scraper) Rewrite

func (s *Scraper) Rewrite(url string) string

Rewrite returns the URL after applying configured rewrite rules, or the original URL if no rule matches or no rewriter is configured. Safe to call on a Scraper with no rewriter (nil-safe via urlrewrite.Rewriter).

func (*Scraper) Scrape

func (s *Scraper) Scrape(ctx context.Context, rawURL string) (*Page, string, error)

Scrape fetches a URL and returns extracted markdown content along with the fetch source (SourceHTTP or SourceBrowser). If the page appears JS-rendered and a browser is configured, automatically retries with the browser for full content extraction.

func (*Scraper) ScrapeConditional

func (s *Scraper) ScrapeConditional(ctx context.Context, rawURL, etag, lastModified string) (*FetchResult, error)

ScrapeConditional fetches a URL with conditional headers and JS detection.

func (*Scraper) ScrapeMarkdown

func (s *Scraper) ScrapeMarkdown(ctx context.Context, pc PageCache, url string, forceBrowser bool) (*Page, error)

ScrapeMarkdown picks the markdown fetch path: forced browser render or the auto-detecting CachedScrape.

func (*Scraper) ScrapeRaw

func (s *Scraper) ScrapeRaw(ctx context.Context, pc PageCache, url string, forceBrowser bool) (*Page, string, string, error)

ScrapeRaw picks the raw-HTML fetch path: forced browser render or the auto-detecting CachedScrapeRaw.

func (*Scraper) ScrapeSelector

func (s *Scraper) ScrapeSelector(ctx context.Context, rawURL, selector string, forceBrowser bool) (*Page, error)

ScrapeSelector fetches rawURL and returns only elements matching the CSS selector, converted to markdown — bypassing readability extraction and the page cache. Under forceBrowser it renders via the browser and selects against the rendered DOM; otherwise it fetches plain HTTP with JS-shell auto-detection. The URL is rewritten before fetch so selector scrapes share the canonical URL-rewrite path with Scrape/ScrapeConditional.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL