Documentation
¶
Index ¶
- Constants
- Variables
- func CacheStaleForBrowser(source string, hasBrowser bool) bool
- func ContentHash(s string) string
- func FetchLLMSTxt(ctx context.Context, baseURL string) (string, bool)
- func InstallBrowser() (string, error)
- func ResolveBrowserBin(configured string) (string, error)
- type BrowserConn
- type FetchResult
- type Page
- type PageCache
- type Scraper
- func New() *Scraper
- func NewFromConfig(cfg *config.Config) (*Scraper, error)
- func NewWithBrowser(browserBin string) *Scraper
- func NewWithBrowserConn(conn BrowserConn, rw *urlrewrite.Rewriter) *Scraper
- func NewWithConfig(browserBin string, rw *urlrewrite.Rewriter, spaMarkers []string) *Scraper
- func NewWithRewriter(browserBin string, rw *urlrewrite.Rewriter) *Scraper
- func (s *Scraper) BrowserScrape(ctx context.Context, rawURL string) (*Page, string, error)
- func (s *Scraper) CachedScrape(ctx context.Context, pc PageCache, url string) (*Page, error)
- func (s *Scraper) CachedScrapeForce(ctx context.Context, pc PageCache, url string) (*Page, error)
- func (s *Scraper) CachedScrapeRaw(ctx context.Context, pc PageCache, url string) (*Page, string, string, error)
- func (s *Scraper) CachedScrapeRawForce(ctx context.Context, pc PageCache, url string) (*Page, string, string, error)
- func (s *Scraper) Close()
- func (s *Scraper) Fetch(ctx context.Context, rawURL string) (string, error)
- func (s *Scraper) HasBrowser() bool
- func (s *Scraper) MaybeBrowserFetch(ctx context.Context, rawURL, html string) (string, string)
- func (s *Scraper) Rewrite(url string) string
- func (s *Scraper) Scrape(ctx context.Context, rawURL string) (*Page, string, error)
- func (s *Scraper) ScrapeConditional(ctx context.Context, rawURL, etag, lastModified string) (*FetchResult, error)
- func (s *Scraper) ScrapeMarkdown(ctx context.Context, pc PageCache, url string, forceBrowser bool) (*Page, error)
- func (s *Scraper) ScrapeRaw(ctx context.Context, pc PageCache, url string, forceBrowser bool) (*Page, string, string, error)
- func (s *Scraper) ScrapeSelector(ctx context.Context, rawURL, selector string, forceBrowser bool) (*Page, error)
Constants ¶
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.
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 ¶
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.
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 ¶
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 ¶
ContentHash returns the first 16 hex chars of the sha256 of s.
func FetchLLMSTxt ¶
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 ¶
InstallBrowser downloads Chromium to the ketch cache directory.
func ResolveBrowserBin ¶
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 NewFromConfig ¶
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 ¶
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 ¶
BrowserScrape fetches a URL using the browser directly. Used when a host is known to require browser rendering.
func (*Scraper) CachedScrape ¶
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 ¶
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) Fetch ¶
Fetch fetches the raw HTML for a URL without extraction or browser fallback.
func (*Scraper) HasBrowser ¶
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 ¶
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 ¶
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 ¶
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.