Documentation
¶
Overview ¶
Package forage is a scraping engine: a Client that fetches a URL the way a careful person with a browser would, and reports what it got.
Get picks an identity from the pool, checks robots.txt, waits its turn for the host, fetches over HTTP with a real browser's TLS fingerprint, classifies the page, and retries with backoff when the page is blocked, rate-limited or broken. When a browser is configured it is used for pages that need JavaScript, and optionally for pages the HTTP path could not get past.
The parts live in subpackages and can be used on their own: fetch, browser, session, pace, detect, extract, queue and metrics. Site-specific parsing belongs in the application, not here.
Example ¶
The README's minimal example. It compiles as part of the test suite and does not run, because it reaches a real site.
package main
import (
"bytes"
"context"
"errors"
"fmt"
"log"
"github.com/pigfox/forage"
"github.com/pigfox/forage/extract"
)
func main() {
client, err := forage.New(forage.Config{})
if err != nil {
log.Fatal(err)
}
res, err := client.Get(context.Background(), "https://example.com/")
if err != nil {
log.Fatal(err) // no response at all, or robots.txt said no
}
fmt.Println(res.Status, res.Outcome, res.Identity.ID, len(res.Body))
product := extract.Set{Fields: []extract.Field{
{Name: "title", Required: true, Selectors: []extract.Selector{extract.Text("h1")}},
{Name: "link", Selectors: []extract.Selector{extract.Attr("a", "href")}},
}}
rec, err := product.ExtractHTML(bytes.NewReader(res.Body))
if errors.Is(err, extract.ErrMissingField) {
log.Fatal(err) // the markup changed: fail loudly
}
fmt.Println(rec["title"], rec["link"])
}
Output:
Example (Configured) ¶
A fuller setup: several identities, a slower pace for one host, and headless Chrome for pages that need JavaScript.
package main
import (
"context"
"fmt"
"log"
"github.com/pigfox/forage"
"github.com/pigfox/forage/browser"
"github.com/pigfox/forage/pace"
"github.com/pigfox/forage/session"
)
func main() {
if err := configured(context.Background()); err != nil {
log.Fatal(err)
}
}
func configured(ctx context.Context) error {
pool, err := session.NewPool(session.Config{
Size: 3,
Languages: []string{"en-US", "en-GB"},
Timezones: []string{"America/New_York", "Europe/London"},
})
if err != nil {
return err
}
limiter := pace.NewLimiter(1, 1)
limiter.SetHost("slow.example", 0.2, 1)
chrome, err := browser.New(browser.Options{})
if err != nil {
return err
}
defer chrome.Close()
client, err := forage.New(forage.Config{
Pool: pool,
Limiter: limiter,
Browser: chrome,
BrowserOnBlock: true,
Retry: pace.Policy{MaxAttempts: 4},
})
if err != nil {
return err
}
res, err := client.Get(ctx, "https://example.com/app")
if err != nil {
return err
}
fmt.Println(res.Via, res.Outcome)
return nil
}
Output:
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrDisallowed = errors.New("forage: disallowed by robots.txt")
ErrDisallowed is returned, wrapped in a *DisallowedError, when robots.txt forbids the URL.
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client fetches pages. It is safe for concurrent use.
func (*Client) Extract ¶
Extract runs a selector set over a Result's body and counts a parse failure against the host and identity when a required field is missing.
func (*Client) ExtractAll ¶
ExtractAll is Extract for a list of items, such as search results.
type Config ¶
type Config struct {
// HTTP is the primary fetcher. Default fetch.New(fetch.Options{}).
HTTP fetch.Fetcher
// Browser, when set, renders pages that need JavaScript. A
// *browser.Browser is the usual choice. Nil means HTTP only.
Browser fetch.Fetcher
// BrowserOnBlock also sends a page to the Browser when the HTTP path
// was blocked, challenged or got an empty page.
BrowserOnBlock bool
// NeedsJS decides whether a page needs the browser. Default
// detect.NeedsJS.
NeedsJS func(detect.Page) bool
// Pool supplies identities. Default: one direct Chrome identity.
Pool *session.Pool
// Limiter spaces requests per host. Default one request per second
// per host.
Limiter *pace.Limiter
// Retry decides retries and their backoff. Default three attempts.
Retry pace.Policy
// Detector classifies pages. Default detect.Default().
Detector detect.Detector
// Metrics receives counts. Default metrics.Nop.
Metrics metrics.Hook
// IgnoreRobots turns the robots.txt check off. It is on by default.
IgnoreRobots bool
// RobotsAgent is the product token matched against robots.txt groups.
// Default "forage", which falls back to the "*" group.
RobotsAgent string
// RobotsTTL is how long a robots.txt is cached. Default 24h.
RobotsTTL time.Duration
}
Config wires a Client. Every field is optional.
type DisallowedError ¶
type DisallowedError struct {
URL string
}
DisallowedError names the URL robots.txt forbids.
func (*DisallowedError) Error ¶
func (e *DisallowedError) Error() string
func (*DisallowedError) Is ¶
func (e *DisallowedError) Is(target error) bool
Is makes errors.Is(err, ErrDisallowed) true.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package browser fetches pages with headless Chrome, for sites that build their content with JavaScript.
|
Package browser fetches pages with headless Chrome, for sites that build their content with JavaScript. |
|
Package detect classifies a fetched page as usable, blocked, a captcha or challenge, or suspiciously empty.
|
Package detect classifies a fetched page as usable, blocked, a captcha or challenge, or suspiciously empty. |
|
Package extract pulls named fields out of HTML with CSS selectors.
|
Package extract pulls named fields out of HTML with CSS selectors. |
|
Package fetch is an HTTP client that presents a real browser's TLS fingerprint and headers.
|
Package fetch is an HTTP client that presents a real browser's TLS fingerprint and headers. |
|
Package metrics counts what a scraper does, per host and per identity.
|
Package metrics counts what a scraper does, per host and per identity. |
|
Package pace decides when the next request may go: a token bucket per host, jittered exponential backoff between attempts, a retry policy, and a robots.txt check.
|
Package pace decides when the next request may go: a token bucket per host, jittered exponential backoff between attempts, a retry policy, and a robots.txt check. |
|
Package queue holds scrape jobs: a URL and an optional payload, leased to one worker at a time, retried on failure, and moved to a dead-letter state after too many attempts.
|
Package queue holds scrape jobs: a URL and an optional payload, leased to one worker at a time, retried on failure, and moved to a dead-letter state after too many attempts. |
|
Package session keeps a pool of identities — a proxy, a browser profile, a cookie jar, a language and a timezone each — and tracks how well each one is doing.
|
Package session keeps a pool of identities — a proxy, a browser profile, a cookie jar, a language and a timezone each — and tracks how well each one is doing. |