scrapers

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Jun 16, 2026 License: MIT Imports: 34 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AnalyzeFeedBody added in v0.2.0

func AnalyzeFeedBody(raw RawResponse, parseErr error, itemCount int) models.FeedDiagnosis

AnalyzeFeedBody turns a raw response and its parse outcome into a structured diagnosis. It is a pure function over the bytes (no IO) so it can be unit tested directly against crafted payloads.

func CheckLightpanda

func CheckLightpanda(autoStart bool) error

CheckLightpanda checks if Lightpanda is reachable on port 9222. If not, it either prompts the user interactively or auto-starts the container. Set autoStart to true to skip the interactive prompt.

func CheckSolimen

func CheckSolimen(autoStart bool) error

CheckSolimen checks if the Solimen service is reachable on port 5011. If not, it either prompts the user interactively or auto-starts the container.

func DiagnoseFeedURL added in v0.2.0

func DiagnoseFeedURL(feedURL string, headers map[string]string) models.FeedDiagnosis

DiagnoseFeedURL fetches a feed URL and returns a structured diagnosis. It is a thin wrapper over InspectFeedURL for callers that only need the diagnosis.

func HeadersFromParams

func HeadersFromParams(params map[string]any) map[string]string

HeadersFromParams extracts custom HTTP headers stored under params["headers"]. Values may arrive as map[string]string (in-process) or map[string]any (after a JSON/protobuf round-trip), so both shapes are handled. Returns nil when absent.

func Usable added in v0.2.0

func Usable(text string) (bool, string)

Usable reports whether extracted article text looks like real content, with a short reason when it does not. It is the shared yardstick behind probe-modes and selector scoring, so "good enough" means the same thing everywhere.

Types

type AnonymizedScraper

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

AnonymizedScraper owns the User-Agent pool and applies anonymization to each request. It holds no browser or collector state: ScrapeContent builds a fresh colly collector per call and ScrapeContentDynamic opens a fresh CDP connection to Lightpanda per call, so concurrent scrapes never share mutable state.

func GetSharedAnonymizedScraper

func GetSharedAnonymizedScraper() *AnonymizedScraper

GetSharedAnonymizedScraper returns the process-wide AnonymizedScraper, which owns the User-Agent pool. It holds no browser or collector state — ScrapeContent builds a per-call colly collector and ScrapeContentDynamic opens a per-call CDP connection to Lightpanda — so this needs no configuration and nothing to close.

func NewAnonymizedScraper

func NewAnonymizedScraper() *AnonymizedScraper

NewAnonymizedScraper creates a new AnonymizedScraper.

func (*AnonymizedScraper) HTTPClient

func (s *AnonymizedScraper) HTTPClient() *http.Client

HTTPClient returns an *http.Client that carries this scraper's anon profile (rotating User-Agent from s.userAgents + spoofed headers + Alt-Used) on every request via anonRoundTripper. Unlike the colly path it follows redirects across hosts and imposes no domain allowlist, so it suits feed/RSS fetching.

func (*AnonymizedScraper) ScrapeContent

func (s *AnonymizedScraper) ScrapeContent(url string, headers map[string]string) (dom *goquery.Selection, err error)

ScrapeContent visits the URL, processes the HTML content and returns the largest content block. Custom headers, when provided, are carried via colly.Context and overlaid after the anon profile so they take precedence (custom headers win).

func (*AnonymizedScraper) ScrapeContentDynamic added in v0.2.0

func (s *AnonymizedScraper) ScrapeContentDynamic(url string, customHeaders map[string]string) (*goquery.Selection, error)

ScrapeContentDynamic renders a page with JavaScript by driving Lightpanda over CDP and returns its body DOM. It uses chromedp — a pure-Go CDP client — so it needs no Playwright driver, no Node, and no bundled browser: it connects to the Lightpanda instance already running on the CDP port. Drop-in replacement for the old Playwright path (same signature and return shape). Custom headers are overlaid after the anon profile (custom wins).

type ArticleExtractor

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

ArticleExtractor implements the Scraper interface for article content extraction

func NewArticleExtractor

func NewArticleExtractor(configSelectors *models.Selectors) *ArticleExtractor

NewArticleExtractor creates a new ArticleExtractor instance

func (*ArticleExtractor) ExtractFromDOM

func (ae *ArticleExtractor) ExtractFromDOM(dom *goquery.Selection, url string, feedSelectors *models.Selectors) (string, error)

func (*ArticleExtractor) GetSelectors

func (ae *ArticleExtractor) GetSelectors() *models.Selectors

GetSelectors returns the configured selectors for this extractor

func (*ArticleExtractor) SetSelectors

func (ae *ArticleExtractor) SetSelectors(selectors *models.Selectors)

SetSelectors updates the config selectors

type ExtractResult added in v0.2.0

type ExtractResult struct {
	URL     string
	Chars   int
	Matched bool // the article selector matched an element
}

ExtractResult is one selector test against one article URL.

type FeedInspection added in v0.2.0

type FeedInspection struct {
	Diagnosis   models.FeedDiagnosis
	SampleLinks []string
	// SampleContentChars holds the plain-text length of each sampled entry's feed
	// content (content:encoded, falling back to description), aligned 1:1 with
	// SampleLinks. It lets a caller tell whether the feed already ships full bodies.
	SampleContentChars []int
	Title              string
}

FeedInspection bundles a feed diagnosis with sample article links and the detected feed title, for scaffolding a feed configuration.

func InspectFeedURL added in v0.2.0

func InspectFeedURL(feedURL string, headers map[string]string, maxLinks int) FeedInspection

InspectFeedURL fetches a feed URL, attempts to parse it, and returns a full diagnosis plus up to maxLinks sample article links and the feed title (when the feed parses). It is read-only: nothing is stored. The raw body is always saved to disk (even when tracing is off) so the offending bytes stay inspectable.

type RSSFeedScraper

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

RSSFeedScraper implements the Scraper interface for RSS feeds

func NewRSSFeedScraper

func NewRSSFeedScraper(configSelectors *models.Selectors) *RSSFeedScraper

NewRSSFeedScraper creates a new RSSFeedScraper instance

func (*RSSFeedScraper) Fetch

func (s *RSSFeedScraper) Fetch(url string, params map[string]any) ([]models.FeedItem, error)

Fetch fetches and parses an RSS feed

func (*RSSFeedScraper) ScrapeContent

func (s *RSSFeedScraper) ScrapeContent(url string, params map[string]any) (string, error)

type RawResponse added in v0.2.0

type RawResponse struct {
	URL         string
	FinalURL    string // after redirects
	Status      int
	ContentType string
	Body        []byte
	Duration    time.Duration
}

RawResponse holds the raw bytes and metadata of a single feed HTTP fetch, captured before any parsing so failures stay inspectable.

func FetchRaw added in v0.2.0

func FetchRaw(feedURL string, headers map[string]string) (RawResponse, error)

FetchRaw performs a GET for a feed URL through the shared anonymized HTTP client — the same client gofeed uses for ParseURL — and returns the raw body plus response metadata. Custom headers are overlaid after the anon profile, so per-feed headers behave exactly as they do on the normal fetch path.

type Scraper

type Scraper interface {
	Fetch(url string, params map[string]any) ([]models.FeedItem, error)
	ScrapeContent(url string, params map[string]any) (string, error)
}

Scraper defines the interface for feed scrapers

type SelectorCandidate added in v0.2.0

type SelectorCandidate struct {
	Selector    string  `json:"selector"`
	Chars       int     `json:"chars"`        // trimmed text length
	LinkDensity float64 `json:"link_density"` // anchor text ÷ total text (0..1); high = nav/menu
	Snippet     string  `json:"snippet"`      // first printable chars of the element's text
}

SelectorCandidate is a ranked guess at the element that wraps an article body.

func SuggestSelectors added in v0.2.0

func SuggestSelectors(dom *goquery.Selection, max int) []SelectorCandidate

SuggestSelectors walks a page DOM and returns the most likely article-content selectors, ranked by text length penalized by link density (so nav/menus/footers, which are mostly links, sink below prose). It is a deterministic, token-cheap alternative to feeding raw HTML to an LLM: the agent picks from measured candidates and confirms with test_selector. Pure over the DOM, so it is unit-testable.

type SelectorScore added in v0.2.0

type SelectorScore struct {
	Samples   int
	Usable    int     // count of results that passed Usable
	MeanChars int     // mean extracted length over usable results
	Score     float64 // 0..1: usable ratio penalized by length variance
	Reliable  bool    // Score high enough to recommend without hesitation
}

SelectorScore summarizes how a candidate selector performed across several sample articles, so a selector that works on one page but flukes on others is not mistaken for a reliable one.

func ScoreSelector added in v0.2.0

func ScoreSelector(results []ExtractResult) SelectorScore

ScoreSelector aggregates per-article results into a stability score. The score is the fraction of usable samples, scaled down when usable lengths vary wildly (high relative variance means the selector grabs different things per page).

type TwitterScraper

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

TwitterScraper is a placeholder for a Twitter scraper This is just a skeleton - you would implement the actual Twitter scraping logic

func NewTwitterScraper

func NewTwitterScraper(apiKey, apiSecret, bearerToken string) *TwitterScraper

NewTwitterScraper creates a new TwitterScraper instance

func (*TwitterScraper) Fetch

func (s *TwitterScraper) Fetch(url string, params map[string]string) ([]models.FeedItem, error)

Fetch fetches tweets from Twitter

Jump to

Keyboard shortcuts

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