crawler

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Jun 30, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package crawler implements gocrawl's concurrent crawl engine. It fetches pages within a configurable scope, captures redirect chains and robots.txt data, and produces a Result that analyzers consume. The engine has no knowledge of specific SEO/SEA checks.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Engine

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

Engine crawls a website concurrently within the configured scope.

func New

func New(opts Options, fetcher Fetcher) *Engine

New creates an Engine. fetcher is used to retrieve pages (raw or headless); a separate raw fetcher is always used for robots.txt regardless of render mode.

func (*Engine) Crawl

func (e *Engine) Crawl(ctx context.Context, seed string) (*Result, error)

Crawl walks the site starting at seed and returns the collected Result.

type Fetcher

type Fetcher interface {
	Fetch(ctx context.Context, rawURL string) (*Page, error)
}

Fetcher retrieves a single URL and returns a populated Page. Implementations include the raw HTTPFetcher and the (stubbed) headless renderer.

type HTTPFetcher

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

HTTPFetcher fetches pages over HTTP(S). It follows redirects manually so the full redirect chain is recorded on the resulting Page.

func NewHTTPFetcher

func NewHTTPFetcher(opts Options) *HTTPFetcher

NewHTTPFetcher builds a fetcher from the given options. When opts.Proxies is non-empty the client routes each request through a proxy chosen by opts.ProxyRotation; otherwise Go's default proxy behavior (environment variables) applies. The User-Agent header is chosen per request from opts.UserAgents / opts.UserAgent via opts.UserAgentRotation.

func (*HTTPFetcher) Fetch

func (f *HTTPFetcher) Fetch(ctx context.Context, rawURL string) (*Page, error)

Fetch retrieves rawURL, following redirects manually up to maxRedirects.

type Link struct {
	URL      string `json:"url"`    // absolute, normalized
	Anchor   string `json:"anchor"` // visible text
	Rel      string `json:"rel"`
	Nofollow bool   `json:"nofollow"`
	External bool   `json:"external"` // points outside the seed host
}

Link is an outbound link discovered on a page.

type Options

type Options struct {
	MaxDepth      int
	MaxPages      int
	Concurrency   int
	RatePerSecond float64
	UserAgent     string
	// UserAgents is an optional pool of User-Agent strings to rotate across. When non-empty it
	// supersedes UserAgent; UserAgentRotation picks one per request.
	UserAgents        []string
	UserAgentRotation RotationStrategy
	// Proxies is an optional pool of proxy URLs (http, https, or socks5) to route requests
	// through. When non-empty, ProxyRotation picks one per request. Empty leaves Go's default
	// proxy behavior (honoring HTTP_PROXY/HTTPS_PROXY/NO_PROXY) in place.
	Proxies         []*url.URL
	ProxyRotation   RotationStrategy
	Include         []*regexp.Regexp
	Exclude         []*regexp.Regexp
	RespectRobots   bool
	AllowSubdomains bool
	FollowExternal  bool
	FollowNofollow  bool
	// StripQuery drops the query string when normalizing URLs, so URLs that differ only by
	// their query collapse to one and are crawled once.
	StripQuery   bool
	Timeout      time.Duration
	MaxBodyBytes int64
	MaxRedirects int
	// Verbose logs each fetch and every rate-limit change to stderr while crawling.
	Verbose bool
	// AdaptiveDelay automatically slows the crawl (halving requests-per-second, honoring any
	// Retry-After header) when the server responds with HTTP 429 or 503.
	AdaptiveDelay bool
}

Options controls crawl scope and politeness.

func DefaultOptions

func DefaultOptions() Options

DefaultOptions returns conservative, polite defaults.

type Page

type Page struct {
	RequestedURL string            `json:"requested_url"`
	FinalURL     string            `json:"final_url"`
	StatusCode   int               `json:"status_code"`
	Header       http.Header       `json:"-"`
	ContentType  string            `json:"content_type"`
	Body         []byte            `json:"-"`
	RawBody      []byte            `json:"-"` // pre-JS HTML captured during headless render; nil in raw mode
	Doc          *goquery.Document `json:"-"` // nil if not HTML or parse failed
	Redirects    []Redirect        `json:"redirects,omitempty"`
	Links        []Link            `json:"-"`
	Depth        int               `json:"depth"`
	Referrer     string            `json:"referrer,omitempty"`
	Duration     time.Duration     `json:"duration_ms"`
	FetchedAt    time.Time         `json:"fetched_at"`
	Err          string            `json:"error,omitempty"`
	Render       *RenderResult     `json:"render,omitempty"`
}

Page is the unit passed from the engine to analyzers.

func (*Page) IsHTML

func (p *Page) IsHTML() bool

IsHTML reports whether the page body was parsed as an HTML document.

type Redirect

type Redirect struct {
	From   string `json:"from"`
	To     string `json:"to"`
	Status int    `json:"status"`
}

Redirect is one hop in a redirect chain.

type RenderResult

type RenderResult struct {
	Implemented bool    `json:"implemented"`
	Note        string  `json:"note,omitempty"`
	LCP         float64 `json:"lcp_ms,omitempty"`
	FCP         float64 `json:"fcp_ms,omitempty"`
	CLS         float64 `json:"cls,omitempty"`
	TBT         float64 `json:"tbt_ms,omitempty"`
	TTFB        float64 `json:"ttfb_ms,omitempty"`

	// DataLayerPresent reports whether window.dataLayer was an array after the page rendered.
	DataLayerPresent bool `json:"data_layer_present,omitempty"`
	// DataLayer is the post-render snapshot of window.dataLayer, each element the raw JSON of
	// one entry (a GTM event push or a gtag() arguments object). It is deliberately not
	// serialized into reports: it can carry PII, so the datalayer analyzer emits sanitized
	// findings from it instead. Nil in raw mode or when the page has no dataLayer.
	DataLayer []json.RawMessage `json:"-"`
	// Requests holds outbound request URLs observed during render, bounded to avoid unbounded
	// growth. The datalayer analyzer uses it to confirm analytics/marketing tags actually
	// fired a network beacon. Not serialized.
	Requests []string `json:"-"`
}

RenderResult holds headless-rendering output, including lab-mode Core Web Vitals. Implemented is true when chromedp produced real measurements; false when rendering fell back to a raw fetch (Note carries the reason). Metric times are milliseconds; CLS is the unitless layout-shift score. Zero means not collected.

INP is a field-only metric and is not collected in lab mode. TBT (Total Blocking Time) is reported as a lab-mode proxy for responsiveness, matching Lighthouse.

type Result

type Result struct {
	Seed      string                 `json:"seed"`
	Pages     []*Page                `json:"pages"`
	Robots    map[string]*RobotsData `json:"robots,omitempty"`
	StartedAt time.Time              `json:"started_at"`
	Finished  time.Time              `json:"finished_at"`
	Opts      Options                `json:"-"`

	// ThrottleEvents counts how many times adaptive delay reduced the crawl rate after HTTP
	// 429/503 responses. FinalRate is the requests-per-second in effect when the crawl ended;
	// it is meaningful only when ThrottleEvents > 0.
	ThrottleEvents int     `json:"throttle_events,omitempty"`
	FinalRate      float64 `json:"final_rate,omitempty"`
	// contains filtered or unexported fields
}

Result is the complete output of a crawl, consumed by analyzers.

func (*Result) Page

func (r *Result) Page(rawURL string) (*Page, bool)

Page looks up a crawled page by URL (matched against requested and final URLs after normalization). The second return value reports whether it was found.

func (*Result) Reindex

func (r *Result) Reindex()

Reindex rebuilds the URL lookup index from r.Pages. The engine populates the index incrementally during a crawl, so production code never needs this; it lets callers that construct a Result by hand (notably tests) make Page lookups resolve.

type RobotsData

type RobotsData struct {
	Host     string   `json:"host"`
	Found    bool     `json:"found"`
	Status   int      `json:"status"`
	Sitemaps []string `json:"sitemaps,omitempty"`
	// contains filtered or unexported fields
}

RobotsData is the parsed robots.txt for a single host.

func (*RobotsData) TestAgent

func (r *RobotsData) TestAgent(path, userAgent string) bool

TestAgent reports whether the given path is allowed for userAgent.

type RotationStrategy

type RotationStrategy int

RotationStrategy selects how a pool (of proxies or User-Agent strings) picks an entry for a given request. It is shared by the proxy pool and the User-Agent pool so both rotate with identical semantics.

const (
	// RotateRoundRobin cycles through the pool in order, one entry per request. It is the
	// default once a pool has more than one entry.
	RotateRoundRobin RotationStrategy = iota
	// RotateRandom picks a uniformly random entry per request.
	RotateRandom
	// RotateStickyHost maps each target host to a fixed entry (by hash), so every request to a
	// given host uses the same proxy/User-Agent. For proxies this avoids a server seeing a
	// single session arrive from several IPs.
	RotateStickyHost
	// RotateOff disables rotation: the first entry is always used. A pool with a single entry
	// behaves this way regardless of strategy.
	RotateOff
)

func ParseRotation

func ParseRotation(s string) (RotationStrategy, error)

ParseRotation converts a config string into a RotationStrategy. An empty string defaults to round-robin, so simply supplying a multi-entry pool rotates without extra configuration.

type UAPool

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

UAPool selects a User-Agent string per request. It is built from Options and shared by the raw HTTP fetcher and the headless renderer so both rotate identically. A nil pool, or one with no agents, yields the empty string (callers then leave the header unset).

func NewUAPool

func NewUAPool(opts Options) *UAPool

NewUAPool builds a User-Agent pool from opts. When UserAgents is set it is the pool; otherwise the single UserAgent (if any) is the only entry.

func (*UAPool) Default

func (p *UAPool) Default() string

Default returns the pool's first agent without advancing rotation state. The headless renderer uses it as the browser-level User-Agent; per-navigation rotation is layered on top.

func (*UAPool) Next

func (p *UAPool) Next(host string) string

Next returns the User-Agent to use for a request to host, advancing rotation state as the strategy requires.

func (*UAPool) Rotates

func (p *UAPool) Rotates() bool

Rotates reports whether the pool has more than one agent (i.e. rotation can occur).

Jump to

Keyboard shortcuts

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