Documentation
¶
Overview ¶
Package gosearch performs web search and page-content extraction without an API key. It works by fetching each search engine's public HTML result page (DuckDuckGo, Google, Yandex, Bing) and parsing it directly, plus a Fetch function that extracts the readable content of any URL.
It is built for local-first / zero-dependency Go programs (for example an LLM agent's web-search tool) that cannot or will not depend on a hosted search API. The only third-party dependency is golang.org/x/net/html.
Reliability ¶
Search engines run anti-bot systems (image captchas, JavaScript challenges, IP-reputation blocks). gosearch behaves like an ordinary browser — realistic headers, a persistent cookie jar, self-imposed rate limiting, and the lowest-friction endpoint each engine offers — but it never attempts to solve a CAPTCHA, execute a JavaScript challenge, or spoof its identity to defeat a security control. When an engine blocks a request, Search returns a typed error (ErrBlocked or ErrChallenge) rather than an empty result, so callers can react (for example, fall back to another engine via WithFallback).
Anti-bot strictness varies by engine and by network. DuckDuckGo is the most reliable (it is the only engine with an official no-JavaScript HTML endpoint); Google is moderate; Yandex is the most likely to block; Bing is the most tolerant of automated clients after DuckDuckGo. Requests from datacenter/cloud IPs are far more likely to be blocked than requests from a residential network.
Index ¶
- Variables
- type Engine
- type Option
- func WithAllowedDomains(domains ...string) Option
- func WithBlockedDomains(domains ...string) Option
- func WithCookies(cookies ...*http.Cookie) Option
- func WithDates() Option
- func WithFallback(engines ...Engine) Option
- func WithHTTPClient(client *http.Client) Option
- func WithHeader(key, value string) Option
- func WithMarkdown() Option
- func WithMaxResults(n int) Option
- func WithProxy(rawURL string) Option
- func WithRetries(n int) Option
- func WithTimeout(d time.Duration) Option
- func WithUserAgent(ua string) Option
- type Page
- type Result
Constants ¶
This section is empty.
Variables ¶
var ( // ErrBlocked means the engine refused the request because its anti-bot // system flagged it (for example an HTTP 429, an IP-reputation block, or a // redirect to a "you look like a bot" page). This is not a bug in the // query; it typically means the current IP/network is distrusted. Callers // using WithFallback will advance to the next engine on this error. ErrBlocked = serrors.ErrBlocked // ErrChallenge means the engine served an interactive challenge (an image // CAPTCHA or a JavaScript anti-bot challenge) instead of results. It is a // specific, common form of being blocked. gosearch never attempts to solve // such challenges by design. Callers using WithFallback will advance to the // next engine on this error. ErrChallenge = serrors.ErrChallenge // ErrNoResults means the request succeeded and was parsed successfully, but // the engine genuinely returned no results for the query. This is a valid // answer, not a failure: WithFallback does NOT advance to another engine on // this error, because a different engine is no more likely to have results // for a query that legitimately has none. ErrNoResults = serrors.ErrNoResults // ErrUnsupportedEngine means Search was called with an Engine value that is // not one of the defined constants (DuckDuckGo, Google, Yandex, Bing). ErrUnsupportedEngine = serrors.ErrUnsupportedEngine )
Sentinel errors returned by Search and Fetch. Callers should test for these with errors.Is, never by string-matching an error message. Providers wrap these with additional context using fmt.Errorf("%w: ..."), so errors.Is continues to work through wrapping and through the errors.Join that the fallback chain uses.
These are re-exported from an internal package so that internal packages can return the same values without an import cycle; the identity is unchanged, so errors.Is works across the whole library.
Functions ¶
This section is empty.
Types ¶
type Engine ¶
type Engine int
Engine identifies which search engine Search should query. Pass one of the exported constants (DuckDuckGo, Google, Yandex, Bing) as the primary engine, and optionally more via WithFallback.
const ( // DuckDuckGo queries html.duckduckgo.com, DuckDuckGo's no-JavaScript HTML // endpoint. It is the most reliable engine for this library because it is // explicitly designed to work without JavaScript. DuckDuckGo Engine = iota // Google queries Google Search. Google has no official no-JavaScript // endpoint and its result markup is regionally A/B tested, so parsing is // best-effort and more likely to break or be blocked than DuckDuckGo. Google // Yandex queries Yandex Search. Yandex applies aggressive, geo/IP-based // anti-bot gating, so it is the most likely of these engines to return // ErrBlocked or ErrChallenge. Yandex // Bing queries Microsoft Bing Search. Bing's plain-HTML endpoint is the // least aggressive of the four against automated clients — it served // clean organic results to a flagged datacenter IP during testing where // Google and Yandex challenged or blocked. Titles arrive wrapped in // Bing's click-tracker; the real destination is recovered from the // result's visible citation URL (best-effort, see the provider docs). Bing )
type Option ¶
type Option func(*config)
Option configures a Search or Fetch call. Options are applied in order.
Most options (timeout, proxy, headers, cookies, custom client) apply to both Search and Fetch. A few are meaningful only for Search — WithFallback and WithMaxResults — and are ignored by Fetch; each such option documents this.
func WithAllowedDomains ¶ added in v0.2.0
WithAllowedDomains keeps only Search results whose host matches one of the given domains or a subdomain of one; everything else is dropped. Results whose URL carries no parsable host cannot be proven allowed and drop too. An empty list means no allowlisting. Search-only; ignored by Fetch.
func WithBlockedDomains ¶ added in v0.2.0
WithBlockedDomains drops Search results whose host is one of the given domains or a subdomain of one ("spam.example.net" also kills "www.spam.example.net" but not "notspam.example.net"). This is the caller's SEO-spam / quality policy — the library itself makes no judgments about which sites deserve to exist. Combined with WithAllowedDomains, deny is applied first. Search-only; ignored by Fetch.
func WithCookies ¶
WithCookies seeds cookies into the client's cookie jar before the first request. This lets you reuse a session (for example cookies exported from your own logged-in browser) so requests look like a returning visitor.
func WithDates ¶ added in v0.2.0
func WithDates() Option
WithDates makes Search populate Result.Date with each engine's own freshness stamp (best-effort, often absent). Off by default so callers who need timeless results — historical queries, stable snapshots — never see date metadata by accident; enabling it does not change what the engine is asked or returns, it only surfaces metadata already on the page. This option is Search-only and is ignored by Fetch.
func WithFallback ¶
WithFallback sets the ordered list of engines to try if the primary engine returns ErrBlocked or ErrChallenge. Engines are tried in exactly the order given; the first that succeeds wins. Fallback is not triggered by ErrNoResults (an empty result set is a valid answer). This option is Search-only and is ignored by Fetch.
func WithHTTPClient ¶
WithHTTPClient uses the supplied *http.Client as-is instead of the client the library would otherwise build. This is an advanced escape hatch: it bypasses the library's default headers, cookie jar, and rate limiting, so you take responsibility for configuring those yourself. Timeout, proxy, and header options are not applied on top of a client supplied this way.
func WithHeader ¶
WithHeader adds or overrides a single request header. Call it multiple times to set multiple headers.
func WithMarkdown ¶ added in v0.2.0
func WithMarkdown() Option
WithMarkdown makes Fetch return its extracted main content as GitHub-flavored Markdown in Page.Content — headings as # levels, lists as bullets, code blocks fenced, links as [text](href), emphasis preserved — instead of plain text. Structure is what makes page content useful to LLM consumers; callers who want bare text simply omit this option (the default). This option is Fetch-only and is ignored by Search.
func WithMaxResults ¶
WithMaxResults caps the number of results Search returns. Zero (the default) means no cap. This option is Search-only and is ignored by Fetch.
func WithProxy ¶
WithProxy routes all requests through the given proxy URL (for example "http://user:pass@host:port" or "socks5://host:port"). This is a legitimate way to use your own network egress; it is not used to rotate identities to defeat anti-bot systems.
func WithRetries ¶
WithRetries sets how many times a transiently failing request — transport error or HTTP 408/5xx — is retried with exponential backoff before the failure is final. The default is 2; passing 0 or a negative number disables retrying entirely. Blocks and challenges (ErrBlocked/ErrChallenge) are never retried — they are deterministic for the caller's IP reputation, and WithFallback exists for them.
func WithTimeout ¶
WithTimeout sets the timeout for the operation. A non-positive duration resets to the default. The default is 15 seconds.
func WithUserAgent ¶
WithUserAgent overrides the default browser User-Agent header. Supplying a realistic browser User-Agent is part of how this library avoids looking like a bot; override only if you have a specific reason.
type Page ¶
type Page struct {
// URL is the page that was fetched. If the request followed redirects,
// this is the final URL.
URL string
// Title is the page's title (from <title>, an <h1>, or article metadata,
// whichever the extractor judged best). It may be empty.
Title string
// Content is the extracted main text of the page. It may be empty if the
// extractor could not identify a main content region (for example, a page
// whose content is rendered entirely by JavaScript).
Content string
}
Page is the extracted, readable content of a URL returned by Fetch. It holds the main article/body text with navigation, ads, scripts, and boilerplate removed — not the raw HTML of the page.
func Fetch ¶
Fetch retrieves url and extracts its main readable content into a Page. It does not run JavaScript, so pages whose content is rendered client-side may yield an empty Page.Content. If the server responds with an anti-bot page (for example HTTP 403/429), Fetch returns ErrBlocked/ErrChallenge.
With Markdown opted in (WithMarkdown), Page.Content carries GitHub-flavored Markdown with headings, lists, code fences, links, and emphasis preserved; the default remains plain text.
Search-only options (WithFallback, WithMaxResults) are ignored by Fetch.
type Result ¶
type Result struct {
// Title is the result's display title (the clickable heading).
Title string
// URL is the destination link the result points to.
URL string
// Snippet is the short description/excerpt shown under the title. It may
// be empty for some engines or result types.
Snippet string
// Date is the result's freshness stamp exactly as the engine rendered it
// (for example "2026-08-20" or "3 days ago") when the engine exposed one.
// It stays "" unless Search was called with WithDates, and even then many
// engines simply do not provide dates on their no-JavaScript pages — an
// empty Date is normal and must not be treated as an error.
Date string
}
Result is a single web-search result returned by Search.
func Search ¶
Search queries the given engine for query and returns the parsed results.
If WithFallback supplied additional engines, they are tried, in order, only when the current engine returns ErrBlocked or ErrChallenge; the first engine that succeeds wins. Fallback is NOT triggered by ErrNoResults (an empty result set is a valid answer) nor by other errors (network failures, etc.), which are returned immediately. If every engine in the chain is blocked/challenged, Search returns the errors.Join of each engine's error, so errors.Is(err, ErrBlocked) / errors.Is(err, ErrChallenge) still report true.
The same underlying HTTP client (with its cookie jar and rate limiter) is reused across the fallback chain.
All four engines are implemented. The DuckDuckGo parser is validated against a real captured success page; the Google and Yandex parsers are best-effort heuristics written against documented markup until a real capture lands — see plan.md's exit criteria and AGENTS.md Known Pitfalls. Bing served clean organic results even to a flagged datacenter IP during the 2026-08-24 probe, but its parser is likewise best-effort until a real capture lands.
Directories
¶
| Path | Synopsis |
|---|---|
|
browser
module
|
|
|
examples
|
|
|
basic
command
Command basic demonstrates gosearch's public API: a DuckDuckGo search and a Fetch of one of the results.
|
Command basic demonstrates gosearch's public API: a DuckDuckGo search and a Fetch of one of the results. |
|
internal
|
|
|
e2e
Package e2e exercises the public gosearch API against the REAL provider packages — the only place where the full dispatch → provider → Detect chain can be observed without hitting the live engines.
|
Package e2e exercises the public gosearch API against the REAL provider packages — the only place where the full dispatch → provider → Detect chain can be observed without hitting the live engines. |
|
htmlx
Package htmlx holds small DOM helpers over golang.org/x/net/html that the providers and the readability extractor share: attribute lookup, class testing, text extraction, and tree traversal.
|
Package htmlx holds small DOM helpers over golang.org/x/net/html that the providers and the readability extractor share: attribute lookup, class testing, text extraction, and tree traversal. |
|
httpclient
Package httpclient provides the shared HTTP client every gosearch provider uses.
|
Package httpclient provides the shared HTTP client every gosearch provider uses. |
|
provider
Package provider holds the shared type that search-engine providers return.
|
Package provider holds the shared type that search-engine providers return. |
|
providers/bing
Package bing implements web search against Microsoft Bing's plain-HTML result page.
|
Package bing implements web search against Microsoft Bing's plain-HTML result page. |
|
providers/duckduckgo
Package duckduckgo implements web search against html.duckduckgo.com, the no-JavaScript HTML endpoint.
|
Package duckduckgo implements web search against html.duckduckgo.com, the no-JavaScript HTML endpoint. |
|
providers/google
Package google implements web search against Google's classic non-JavaScript result page (the server-rendered markup served when the client does not run JavaScript).
|
Package google implements web search against Google's classic non-JavaScript result page (the server-rendered markup served when the client does not run JavaScript). |
|
providers/yandex
Package yandex implements web search against Yandex's server-rendered search result page (yandex.com/search/?text=...).
|
Package yandex implements web search against Yandex's server-rendered search result page (yandex.com/search/?text=...). |
|
readability
Package readability extracts the main readable content of an HTML page — title and body text — with navigation, ads, scripts, and boilerplate removed.
|
Package readability extracts the main readable content of an HTML page — title and body text — with navigation, ads, scripts, and boilerplate removed. |
|
serrors
Package serrors holds gosearch's sentinel errors in an internal package.
|
Package serrors holds gosearch's sentinel errors in an internal package. |