Documentation
¶
Index ¶
- Constants
- Variables
- func CacheStaleForBrowser(source string, hasBrowser bool) bool
- func ContentHash(s string) string
- func DefaultUserAgent() 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 FetchedContent
- 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) CacheKey(fetchURL string) string
- 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) FetchContent(ctx context.Context, rawURL string) (*FetchedContent, error)
- func (s *Scraper) FetchLLMSTxt(ctx context.Context, baseURL string) (string, bool)
- 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") // ErrPDFSelectorUnsupported reports that CSS selectors only apply to HTML. ErrPDFSelectorUnsupported = errors.New("CSS selector extraction is not supported for PDF documents") // ErrPDFRawUnsupported reports that returning PDF binary data is forbidden. ErrPDFRawUnsupported = errors.New("raw output is not supported for PDF documents") )
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.
var Version = "dev"
Version is the ketch version string embedded into DefaultUserAgent. cmd sets this from build-time version info at process start; library callers that leave it untouched get "dev".
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 DefaultUserAgent ¶ added in v0.13.0
func DefaultUserAgent() string
DefaultUserAgent is the honest HTTP User-Agent ketch sends when no operator override is configured. It identifies ketch and points at the project; it does not impersonate a browser.
func FetchLLMSTxt ¶
FetchLLMSTxt attempts to fetch /llms.txt anonymously. It is kept for package compatibility; callers with a Scraper should use its method so the configured cookie jar and redirect re-scoping apply.
func InstallBrowser ¶
InstallBrowser downloads Chromium to the ketch cache directory.
The revision directory is removed before downloading. Extraction is not atomic: an interrupted download leaves a partial tree behind, and the next attempt fails on it rather than replacing it — either because the unzip hits existing symlinks ("file exists") or because the leftover confuses the single-directory check during extraction. Both surface as errors that look unrelated to the real cause, so retries appear to fail differently each time. Clearing first makes every install start from a known state.
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 without cookie injection. This legacy signature is preserved for external package callers.
func NewBrowserConnWithCookies ¶ added in v0.12.0
func NewBrowserConnWithCookies(binPath string, jar *cookies.Jar) (BrowserConn, error)
NewBrowserConnWithCookies launches a headless browser and injects cookies from jar (which may be nil) before each navigation.
type FetchResult ¶
type FetchResult struct {
Page *Page
RawHTML string
ContentType 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 FetchedContent ¶ added in v0.12.0
FetchedContent preserves the response body bytes and declared content type.
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) CacheKey ¶ added in v0.12.0
CacheKey returns the page-cache key for an already-rewritten fetch URL. A configured jar with live cookies gets a jar-specific namespace even when no cookie matches the initial URL: a redirect may land on another host or path where a cookie does match. This prevents redirected authenticated content from colliding with an anonymous cache entry.
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 classifies a plain HTTP response before any browser render so PDFs bypass Chromium's PDF viewer and use the configured PDF extractor. For HTML, a cache entry is reused only when that entry is itself a browser render.
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. It classifies the HTTP response before consulting the browser cache or rendering, rejecting PDFs rather than returning Chromium's PDF-viewer HTML.
func (*Scraper) FetchContent ¶ added in v0.12.0
FetchContent fetches a URL without extraction or browser fallback while preserving response bytes and the server-declared content type.
func (*Scraper) FetchLLMSTxt ¶ added in v0.12.0
FetchLLMSTxt attempts to fetch /llms.txt from a bare-domain URL through the scraper's authenticated HTTP path. All errors are swallowed because this is only a best-effort shortcut before the requested page is scraped.
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 HTML renders; forced-browser PDFs still use text extraction after content classification.
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, rejecting PDFs before any forced browser render.
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 first classifies the response, rejects PDFs, then renders HTML 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.