稳定性修复: - 修复 cache.py SQLite 连接泄漏(contextlib.closing 包装) - 修复 fetch.py requests stream=True 连接泄漏(try/finally resp.close()) - RETRYABLE_STATUS 新增 403,激活 UA fallback 切换逻辑 - --cache-stats 移至实例解析前,无需实例即可查询 - classify_error 从错误消息提取 HTTP 状态码,正确分类 E_AUTH/E_RATE_LIMIT - --stream 与 --queries-file 互斥检查,违规报 E_INPUT - batch 退出码语义统一(0=有结果 / 1=全部错误 / 2=全部空结果) AI Agent 体验增强: - 错误码体系完善:E_CONFIG/E_AUTH/E_NETWORK/E_RATE_LIMIT/E_PARSE/E_EMPTY/E_INPUT/E_INTERNAL - recovery_hint 恢复提示字段,AI Agent 可程序化决策恢复策略 - stream 模式新增 error 事件类型(含 error_code + recovery_hint) - 进度事件扩展:instance_try/instance_ok/instance_fail - batch 模式统一 schema(status 字段区分 success/failed) - JSON 输出含 schema_version 字段确保版本兼容 测试与文档: - 测试覆盖:330 -> 352 - SKILL.md / README.md 同步更新
42 KiB
name, description, version, author, license, platforms, metadata
| name | description | version | author | license | platforms | metadata | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| searxng-use-cli | Use when you need to search the web via your OWN SearXNG instance (no public-instance discovery). 3 CLI scripts + a shared common.py module — execute privacy-respecting searches against a user-supplied instance (with multi-instance failover, 5xx/429 retry, auto-fetch) or via SEARXNG_INSTANCE env / config file, fetch/extract readable text or markdown from web pages. Zero-config replacement for proprietary search APIs. | 1.8.0 | Metona Team | MIT |
|
|
SearXNG CLI Toolkit
Overview
SearXNG is a privacy-respecting metasearch engine that aggregates results from 70+ search services without tracking users. This skill provides three standalone Python CLI scripts — works with any AI agent (Hermes, Claude Code, Codex, OpenCode, Cursor, Trae, etc.) or directly from your terminal.
Public-instance discovery has been removed. You must supply your own SearXNG instance URL (self-hosted or one you trust). This makes behavior deterministic and avoids depending on volatile public instances.
Key capabilities:
Search & results
- Multi-instance failover with parallel probing (faster failover, deterministic output order)
- Exponential-backoff retry on transient errors (429/5xx/connection) via shared
common.py --verifyhealth-check mode (reachability / JSON-API / latency / POST / engine list / auth status)- Cross-engine result deduplication (default on;
--no-dedupdisables) — collapses duplicate URLs ignoring tracking params (utm_*,gclid, etc.) and fragments - Result sorting (
--sort-by {score,date,engine,none}; default: score descending) — applied after dedup, before--max-results - Domain allowlist/blocklist (
--include-domain/--exclude-domain) — case-insensitive, ignores leadingwww., exclude wins on conflict - Batch mode (
--queries-file) — run multiple queries from a file in sequence, combined output
Output formats
- JSON (default, rich metadata), brief (title+URL+snippet), urls (plain list), CSV (spreadsheet-ready)
- Enhanced Markdown conversion — nested ordered lists (numbered), mixed
ul/olnesting,<dl>/<dt>/<dd>definition lists, GFM tables, fenced code blocks, blockquotes - Structured JSON error output (in
--format jsonmode) witherror_codefield for machine-readable failure reporting - JSON Lines streaming (
--stream) — each result emitted as a separate JSON line to stdout, enabling incremental processing by AI agents - Progress events (
--progress) — structured JSON Lines events to stderr for real-time execution tracking
Caching & config
- SQLite result caching (
--cache-ttl) — identical queries within a TTL skip the network entirely;--clear-cache/--cache-statsmanage it - Config file (
searxng.toml) pre-sets most flags;--config FILEloads a non-default config;instances.txtfor plain URL lists - Instance resolution priority:
-i→SEARXNG_INSTANCEenv → config file
Network & auth
- Proxy support (
--proxy) for both search and fetch (setsHTTP_PROXY/HTTPS_PROXY/NO_PROXY) - Auth via CLI flag, file, config file, or env var (
--auth-bearer/--auth-basic+*-filevariants) to avoid leaking secrets in shell history - Config-file auth —
auth_basicandauth_bearerfields insearxng.tomllet AI agents set credentials once (priority: CLI > file > config > env) - Credentials-file permission warning —
--auth-*-filewarns on stderr if the file is group/other-readable (POSIX only)
Engineering
- Shared
common.pymodule — unified retry/charset/auth/logging logic across both scripts search.py --fetchreusesfetch.py's higher-quality text extractor (no code duplication)- Structured logging (
--verbose/--quiet) — three levels: default INFO (progress + warnings),--verboseDEBUG (HTTP detail, cache keys),--quietWARNING (errors only). All log output to stderr; stdout reserved for data - Engine/category whitespace normalization (
"google, bing"→"google,bing") --time-range noneoption to disable time filtering
Scripts + shared module:
search.py— execute searches against a user-supplied instance, with multi-instance failover + exponential-backoff retry (429/5xx/connection) + auto-fetch + caching + batch + domain filteringfetch.py— download and extract readable text or markdown from web pagescommon.py— shared utilities (auth headers, charset detection, retry policy, fallback UAs, retry constants) used by both scriptscache.py— SQLite-backed result cache (SHA-256 key, TTL, WAL mode)_config.py— package constants (version, User-Agent)
Default settings
search.py ships with opinionated defaults tuned for AI research:
| Setting | Default | Flag to override |
|---|---|---|
| Instance | required — via -i, SEARXNG_INSTANCE env var, or config file |
-i / --instance |
| Safe search | 0 (off) | -s / --safesearch {0,1,2} |
| Time range | year | -t / --time-range {day,month,year,none} (none = disabled) |
| Output format | json | -f / --format {json,brief,urls,csv} |
| Engines | google,bing,brave,duckduckgo,startpage,wikipedia,wikidata | --engines <list> |
Quick Start
# Prerequisites: Python 3.8+
# Optional but recommended:
pip install requests beautifulsoup4
# 1. Search against YOUR instance (instance URL is required)
python scripts/search.py -q "python asyncio tutorial" -i https://my-searxng.example.com
# 2. Multiple instances for failover (comma-separated)
python scripts/search.py -q "rust memory safety" \
-i https://a.example.com,https://b.example.com --format brief
# 3. Search + auto-fetch top 3 result pages in one command
python scripts/search.py -q "climate policy" -i https://my-searxng.example.com --fetch 3
# 4. Fetch a result page
python scripts/fetch.py -u "https://example.com" --extract text
# 5. Skip -i by configuring the instance once (env var, current shell)
export SEARXNG_INSTANCE="https://my-searxng.example.com,https://backup.example.com"
python scripts/search.py -q "python asyncio tutorial" # -i not needed
# 6. Or use a config file (./searxng.toml or ~/.config/searxng-cli/searxng.toml)
# [searxng]
# instance = "https://my-searxng.example.com"
# # or: instances = ["https://a.example.com", "https://b.example.com"]
# # Auth (optional, for private instances):
# auth_basic = "user:password" # Basic auth
# auth_bearer = "sk-token-123" # Bearer token (auth_bearer wins if both set)
# # Any flag below can also be pre-set here (engines, categories, language,
# # safesearch, time_range, method, format, timeout, max_retries, proxy,
# # cache_ttl, fetch, fetch_timeout, fetch_retries, max_size).
# # Explicit CLI flags always override config values.
# # Auth priority: --auth-* > --auth-*-file > searxng.toml > env var
# Plain list also works in ./instances.txt (one URL per line, # for comments)
# 7. Cache results for 30 minutes (identical queries skip the network)
python scripts/search.py -q "python asyncio" -i https://s.example.com --cache-ttl 30
# 7b. Sort by date (newest first) or disable dedup for raw engine output
python scripts/search.py -q "ai news" -i https://s.example.com --sort-by date --no-dedup
# 8. Batch: run queries from a file (one per line; blank/# lines skipped)
python scripts/search.py --queries-file queries.txt -i https://s.example.com --format json > batch.json
# 9. Domain allowlist + blocklist (applied after search)
python scripts/search.py -q "rust async" -i https://s.example.com \
--include-domain doc.rust-lang.org,wikipedia.org --exclude-domain pinterest.com
# 10. Route through a corporate proxy (applies to search and fetch)
python scripts/search.py -q "ai news" -i https://s.example.com --proxy http://corp-proxy:8080
# 11. Auth from a file (avoids leaking tokens in shell history)
python scripts/search.py -q "test" -i https://private.example.com --auth-bearer-file ~/.searxng_token
# 12. Cache management (no search performed)
python scripts/search.py --cache-stats # entry count, age, size, path
python scripts/search.py --clear-cache # delete all entries
# 13. Export results as CSV (great for spreadsheets / data analysis)
python scripts/search.py -q "rust async" -i https://s.example.com --format csv > results.csv
# 14. Use a specific config file (overrides auto-discovered searxng.toml)
python scripts/search.py --config ./my-config.toml -q "test"
# 15. Control log verbosity on stderr
python scripts/search.py -q "test" -i https://s.example.com --verbose # debug detail
python scripts/search.py -q "test" -i https://s.example.com --quiet # errors only
Dependency levels:
| Level | Scripts | What you get |
|---|---|---|
| Zero deps (stdlib only) | search.py, _config.py |
Full search + auto-fetch |
pip install requests |
fetch.py |
Better HTTP (session reuse, redirect handling) |
pip install beautifulsoup4 |
fetch.py |
Higher-quality text extraction |
When to Use
- Web search without API keys — programmatic search results against your own SearXNG instance
- Privacy-conscious research — queries routed through your instance, not ad-tech infrastructure
- Scraping search results — batch query multiple terms and collect structured results as JSON
- Fetching search result pages — follow links from search results and extract clean text
Don't use for:
- High-frequency production search without a properly-scaled instance — respect your instance's rate limits
- Guaranteed uptime/accuracy — depends entirely on the instance you supply
AI Agent Integration Guide
This section documents the structured interfaces that AI agents can rely on for programmatic integration. All features are designed to be machine-readable and machine-actionable.
Output Channels
| Channel | Content | Description |
|---|---|---|
| stdout | Data | JSON/CSV/text — the only source AI should parse |
| stderr | Logs + Progress | Human-readable logs (default) or JSON Lines events (--progress) |
| exit 0 | Success | Results available on stdout |
| exit 1 | Fatal error | Error JSON on stdout (in --format json mode) or stderr |
| exit 2 | Empty results | Search succeeded but returned no results |
Error Code System
In --format json mode, errors are emitted as structured JSON on stdout:
{
"error": "All 2 instances failed. Last error: connection refused",
"error_code": "E_NETWORK",
"recovery_hint": "Retry with backoff, or try a different SearXNG instance. Check network connectivity, proxy settings, and instance uptime.",
"exit_code": 1,
"query": "search term"
}
AI agents can use error_code to programmatically decide recovery strategy, and recovery_hint for a ready-to-use actionable suggestion:
| Code | Meaning | recovery_hint (abridged) |
|---|---|---|
E_CONFIG |
Configuration error (no instance resolved) | Provide -i/SEARXNG_INSTANCE/config file |
E_AUTH |
Authentication failed (401/403) | Verify credentials, check token expiry & permissions |
E_NETWORK |
Network error (connection refused, timeout, 5xx, all instances failed) | Retry with backoff, switch instance, check proxy |
E_RATE_LIMIT |
Rate limited (429) | Wait and retry, reduce frequency, distribute load |
E_PARSE |
Parse error (JSON/HTML parsing failed) | Try different instance, switch --method |
E_EMPTY |
Empty results (exit code 2) | Refine query, broaden --time-range, add --categories |
E_INPUT |
Input error (bad parameters, file not found) | Check query syntax, flag combinations, file paths |
E_INTERNAL |
Internal error (unexpected exception) | Re-run with --verbose, report bug |
JSON Lines Streaming (--stream)
For large result sets, --stream outputs results as JSON Lines (one JSON
object per line) to stdout, allowing AI agents to process results
incrementally without waiting for the full response:
python scripts/search.py -q "large topic" -i https://your-instance --stream
Output format (each line is a separate JSON object):
{"type": "result", "result": {"title": "...", "url": "...", "content": "..."}}
{"type": "result", "result": {"title": "...", "url": "...", "content": "..."}}
{"type": "done", "schema_version": "1.0", "count": 2, "query": "large topic"}
type: "result"— one per search result, emitted as soon as availabletype: "done"— terminal event with total count andschema_version, always emitted lasttype: "error"— emitted when the search fails, includeserror_codeandrecovery_hint:{"type": "error", "error": "All 2 instances failed. Last error: HTTP Error 403", "error_code": "E_AUTH", "recovery_hint": "Verify credentials...", "query": "..."}- Exit code 0 on success, 1 on error, 2 on empty results (done event still emitted)
Only valid with --format json (single query mode). Using --stream with
--queries-file or non-json formats raises E_INPUT immediately.
Progress Events (--progress)
For long-running operations, --progress emits structured JSON Lines events
to stderr, enabling AI agents to track execution progress in real time:
python scripts/search.py -q "research topic" -i https://your-instance --progress --fetch 3
Event types (each on its own line, JSON Lines format on stderr):
{"event": "start", "query": "research topic", "instances": 2}
{"event": "instance_try", "url": "https://instance1.example.com", "attempt": 1}
{"event": "instance_ok", "url": "https://instance1.example.com", "latency": 0.342, "results": 10}
{"event": "instance_fail", "url": "https://instance2.example.com", "error": "HTTP 503", "error_code": "E_NETWORK"}
{"event": "cache_hit", "query": "research topic", "ttl": 30}
{"event": "cache_store", "query": "research topic", "ttl": 30}
{"event": "fetch_start", "count": 3}
{"event": "fetch_ok", "url": "https://example.com/page", "chars": 12345}
{"event": "fetch_fail", "url": "https://bad.example.com", "error": "HTTP 503"}
{"event": "done", "results": 10, "query": "research topic"}
{"event": "error", "error": "connection refused", "error_code": "E_NETWORK", "query": "..."}
AI agents can parse these events to:
- Show progress indicators to users
- Detect cache hits (skip waiting)
- Monitor fetch failures and retry strategies
- Correlate errors with specific queries in batch mode
--progress and --verbose can be used together (progress events on stderr,
debug logs also on stderr). --progress events are JSON Lines; --verbose
logs are human-readable text.
Output JSON Schema
The default --format json output includes a schema_version field so AI
agents can detect breaking changes. Run --dump-schema to get the full JSON
Schema document programmatically:
python scripts/search.py --dump-schema
Single query output shape:
{
"schema_version": "1.0",
"query": "search term",
"number_of_results": 10,
"results": [
{
"title": "Result title",
"url": "https://example.com/page",
"content": "Snippet text...",
"engine": "google",
"score": 1.0,
"category": "general",
"published_date": "2024-01-15T10:30:00"
}
],
"answers": ["Direct answer if available"],
"corrections": [],
"suggestions": ["related suggestion"],
"infoboxes": [],
"unresponsive_engines": [["engine_name", "error reason"]],
"fetched": [
{
"url": "https://example.com/page",
"status": "ok",
"text": "Extracted page content...",
"text_length": 12345,
"truncated": false,
"final_url": "https://example.com/final",
"user_agent_used": "searxng-cli/1.8.0"
}
],
"fetched_source": "json"
}
Batch mode (--queries-file) wraps results in a unified schema:
{
"schema_version": "1.0",
"queries": [
{"query": "term1", "status": "ok", "results": {...}},
{"query": "term2", "status": "error", "error": "...", "error_code": "E_NETWORK"}
]
}
Each batch entry has a status field ("ok" or "error"). Successful entries
contain results; failed entries contain error and error_code.
Fields marked as optional may be absent. The fetched and fetched_source
fields only appear when --fetch N is used.
Cross-Agent Compatibility
These scripts are agent-agnostic — they work with any AI agent that can invoke terminal commands:
| Agent | How to invoke |
|---|---|
| Hermes | python scripts/search.py -q "..." -i https://your-instance |
| Claude Code | Same — call via terminal tool |
| Codex (OpenAI) | Same — call via terminal tool |
| OpenCode | Same — call via terminal tool |
| Cursor | Same — call via terminal tool |
| Trae | Same — call via terminal tool |
| Standalone (human) | Run directly in any terminal |
Key design decisions for universal compatibility:
- Zero external dependencies (stdlib-only for
search.py) - Scripts inject their own directory into
sys.path, so they run from any working directory - Stdout carries data (JSON/text), stderr carries progress/warnings
- Exit codes: 0=success, 1=fatal error, 2=no results/empty
- NO agent-specific API calls or tool dependencies — purely CLI-based, portable across all agent platforms
Standalone usage (no agent):
# Clone or download the scripts/ directory, then run from anywhere:
python /path/to/scripts/search.py -q "your query" -i https://your-instance
python /path/to/scripts/fetch.py -u "https://example.com"
SearXNG Search API (Quick Reference)
Source: docs.searxng.org/dev/search_api.html
GET/POST /search?q=<query>&format=json GET /search
POST /search (form data) POST /
Parameters
| Parameter | Required | Values | Description |
|---|---|---|---|
q |
yes | string | Search query (supports engine syntax like site:) |
format |
no | json, csv, rss |
Output format (many instances only allow html) |
categories |
no | comma-separated | e.g. general,images,news,science |
language |
no | language code | e.g. en, zh-CN, de |
pageno |
no | integer (default 1) | Result page number — fully supported by search.py |
time_range |
no | day, month, year, none |
Time filter (search.py default: year; none = disabled) |
safesearch |
no | 0, 1, 2 |
Safe search (search.py default: 0 = off) |
engines |
no | comma-separated | search.py default: google,bing,brave,duckduckgo,startpage,wikipedia,wikidata |
Authentication
SearXNG supports Authorization headers for private instances (configured via settings.yml → server.secret_key). Both search.py and fetch.py accept:
# Bearer token (most common for API-style auth)
python search.py -q "test" --auth-bearer "sk-abc123..." -i https://private-searx.example.com
# Basic auth (base64-encoded user:pass)
python search.py -q "test" --auth-basic "admin:secret123" -i https://private-searx.example.com
# fetch.py also supports auth for authenticated endpoints
python fetch.py -u "https://protected.example.com/page" --auth-bearer "tok_xxx"
If both are provided, Bearer takes precedence. Auth headers are forwarded to both search API calls and auto-fetched page requests.
Avoid leaking secrets in shell history — credentials can be read from a file or env var instead of a CLI flag (priority: CLI flag > file > env var):
# From a file (first non-empty, non-# line is used)
python search.py -q "test" -i https://private.example.com --auth-bearer-file ~/.searxng_token
python search.py -q "test" -i https://private.example.com --auth-basic-file ~/.searxng_auth
# From an environment variable
export SEARXNG_BEARER_TOKEN="sk-abc123..."
export SEARXNG_BASIC_AUTH="admin:secret123"
python search.py -q "test" -i https://private.example.com # credentials picked up automatically
JSON Response Structure
{
"query": "searxng",
"number_of_results": 1234,
"results": [
{
"title": "...",
"url": "https://...",
"content": "Snippet text...",
"engine": "google",
"score": 0.0,
"category": "general"
}
],
"answers": [],
"corrections": [],
"suggestions": ["searxng docker", "searxng api"],
"infoboxes": [],
"unresponsive_engines": [["bing", "timeout"]]
}
Key gotcha: Many instances disable format=json in their settings. search.py auto-detects and falls back to HTML scraping. Enable format: [html, json] under search.formats in your instance's settings.yml to get rich JSON metadata.
Scripts
All scripts live in scripts/; run with python scripts/<name>.py from any directory. They import _config.py for shared constants and self-inject their own directory into sys.path.
1. search.py — Execute SearXNG Search
usage: search.py [-h] [--query QUERY] [--instance URL]
[--categories CATS] [--language LANG] [--pageno N]
[--time-range {day,month,year,none}] [--safesearch {0,1,2}]
[--engines E] [--method {GET,POST}] [--max-results N]
[--format {json,brief,urls,csv}] [--snippet-len N]
[--fetch N] [--fetch-timeout SEC] [--fetch-retries N]
[--max-size BYTES] [--output FILE] [--timeout SEC]
[--retry N] [--fail-fast] [--serial] [--verify]
[--auth-bearer TOKEN] [--auth-bearer-file FILE]
[--auth-basic USER:PASS] [--auth-basic-file FILE]
[--proxy URL] [--include-domain DOMAINS]
[--exclude-domain DOMAINS] [--queries-file FILE]
[--cache-ttl MINUTES] [--clear-cache] [--cache-stats]
[--sort-by {score,date,engine,none}] [--no-dedup]
[--config FILE] [--dump-schema] [--verbose] [--quiet] [--version]
What it does:
- Takes a search query and resolves one or more instance URLs (
-i,SEARXNG_INSTANCE, or config file — comma-separated for failover) - Multi-instance failover: if an instance fails (429/5xx/timeout/captcha), automatically tries the next one
- Exponential backoff: retries each instance up to 3 times with jitter on transient errors (429, 502, 503, 504, and connection errors)
- Parallel probing (default for 2+ instances): queries every instance concurrently and returns the first successful result in your original instance order — this keeps output deterministic while drastically speeding up failover when an early instance is down/slow. Use
--serialto disable. - Calls the SearXNG API (GET or POST) with
format=json - Falls back to HTML scraping if JSON is blocked
- HTML parser extracts results + suggestions + answers + infoboxes (full content, never truncated)
- Stable auto-fetch:
--fetch 3concurrently downloads top 3 result pages with retry, browser-UA fallback, CAPTCHA detection, andfetch.py's higher-quality text extractor (the same enginefetch.pyuses) - Health-check mode:
--verifyprobes each instance (reachability / JSON-API support / latency / POST support / engine list / auth status) and prints a report, then exits without searching — use it to validate your instance list - Result caching:
--cache-ttl 30stores results for 30 min; identical queries within the TTL skip the network entirely. Cache lives at$SEARXNG_CACHE_DIRor~/.cache/searxng-cli/cache.db(SQLite, WAL mode).--clear-cache/--cache-statsmanage it without searching - Batch mode:
--queries-file FILEreads one query per line (blank/#lines skipped) and runs them in sequence; JSON output is{"schema_version": "1.0", "queries": [{"query":..., "status": "ok"|"error", ...}]}(or one block per query in brief/urls). A failed query is recorded but does not abort the batch. Exit codes: 0 if any query returned results, 1 if all errored, 2 if all empty - Dedup + sort + domain filter: After search (and cache), duplicate URLs are collapsed (default;
--no-dedupdisables), results are sorted (--sort-by; default: score descending), and then--include-domain/--exclude-domainfilter by domain. Matching is case-insensitive and ignores a leadingwww.; when a domain is in both lists, exclude wins - Proxy & auth:
--proxy URLroutes both search and fetch through a proxy;--auth-bearer/--auth-basic(plus*-filevariants,searxng.tomlauth_basic/auth_bearerfields, andSEARXNG_BEARER_TOKEN/SEARXNG_BASIC_AUTHenv vars) supply credentials. Priority: CLI flag > file > config file > env var - Config defaults:
searxng.tomlmay pre-set most flags (engines, categories, language, safesearch, time_range, method, format, sort_by, timeout, max_retries, proxy, cache_ttl, fetch, fetch_timeout, fetch_retries, max_size, auth_basic, auth_bearer); explicit CLI flags always win - Structured errors: in
--format jsonmode, failures print a JSON object{"error": "...", "error_code": "E_*", "recovery_hint": "...", "exit_code": N, "query": "..."}to stdout so agents can parse them and decide recovery strategy
Key options:
--query "your search"— required unless--verify,--queries-file,--clear-cache,--cache-stats, or--dump-schemais used--instance https://searx.example.org— required unlessSEARXNG_INSTANCEenv var or a config file supplies it; comma-separated list enables failover--queries-file FILE— read queries from a file (one per line; blank/#skipped) and run them in sequence; overrides--query--engines google,duckduckgo— restrict to specific search engines (whitespace around commas is auto-stripped; see default list above)--method POST— use POST instead of GET (better for long queries)--categories general,news— comma-separated categories (whitespace auto-stripped)--language zh-CN— language filter--pageno 1— page number--time-range {day,month,year,none}— time filter (default:year;nonedisables time filtering)--safesearch {0,1,2}— safe search (default:0= off)--max-results N— limit number of results (applied AFTER dedup+sort, so the highest-scoring/newest items are kept)--sort-by {score,date,engine,none}— sort results (default:scoredescending;nonepreserves instance order). Applied after dedup, before--max-results. HTML-fallback results have no score and keep their order--no-dedup— disable cross-engine deduplication (by default, duplicate URLs — same page ignoring tracking params/fragment — are collapsed, keeping the first occurrence's engine/score)--config FILE— path to asearxng.tomlconfig file; overrides the default auto-discovery (./searxng.toml→~/.config/searxng-cli/searxng.toml). Must be the first flag so its values can set defaults for other flags--verbose/-v— show debug-level diagnostics on stderr (HTTP request URLs, response codes, cache keys, retry detail)--quiet— suppress progress messages and retry notices on stderr; only warnings and errors are shown (no short flag:-qis--query)--include-domain a.com,b.org— allowlist; only results from these domains are kept (applied after search)--exclude-domain pinterest.com— blocklist; results from these domains are dropped (applied after search; wins over include on conflict)--serial— disable parallel multi-instance probing; search instances strictly one at a time--verify— health-check mode: verify instances and exit (no search); combine with--format brieffor a table or--format jsonfor machine-readable output--format json— full JSON (default); includesfetchedarray when--fetchis used; errors are emitted as JSON to stdout--format brief— title + URL + full snippet (no truncation by default; use--snippet-len 200to cap)--format urls— only result URLs--format csv— CSV export (title,url,engine,score,published_date,content); in batch mode (--queries-file), all queries merge into one CSV with aquerycolumn--fetch N— after search, auto-fetch full text of top N result pages (concurrent, stdlib only)--fetch-timeout 10— timeout per page fetch (default: 10s)--fetch-retries 3— max retries per page fetch--max-size BYTES— cap page size (default: unlimited; e.g.5242880for 5MB)--retry 5— max retries per instance (default: 3)--timeout 15— request timeout in seconds--fail-fast— use only the first instance, don't fail over to the rest--proxy URL— HTTP/HTTPS proxy for both search and fetch (e.g.http://corp-proxy:8080)--cache-ttl MINUTES— cache results for N minutes (default:0= disabled); identical queries within the TTL skip the network--clear-cache— delete all cached entries and exit (no search)--cache-stats— print cache statistics (entries, age, size, path) and exit--dump-schema— print the JSON Schema for--format jsonoutput and exit; lets AI agents programmatically discover field names and types without parsing prose docs--auth-bearer TOKEN—Authorization: Bearerheader for private instances--auth-bearer-file FILE— read Bearer token from a file (first non-empty, non-#line); also honorsSEARXNG_BEARER_TOKENenv var--auth-basic USER:PASS—Authorization: Basicheader (auto base64-encoded)--auth-basic-file FILE— readuser:passfrom a file (first non-empty, non-#line); also honorsSEARXNG_BASIC_AUTHenv var--version— print version and exit
Completion criterion: Outputs valid JSON with results array. Non-zero exit on total failure (all instances exhausted).
2. fetch.py — Fetch & Extract Web Page Content
usage: fetch.py [-h] --url URL [--extract {text,html,markdown}]
[--timeout SEC] [--retries N] [--max-size BYTES]
[--user-agent STR] [--encoding CHARSET]
[--no-redirect] [--proxy URL] [--output FILE]
[--auth-bearer TOKEN] [--auth-bearer-file FILE]
[--auth-basic USER:PASS] [--auth-basic-file FILE]
[--verbose] [--quiet] [--version]
What it does:
- Downloads a web page via HTTP GET with retry + exponential backoff
- Stable fetching: retries on 429/5xx/connection errors (3x default), falls back to browser User-Agent if blocked
- No size limit by default — full page content returned; use
--max-sizefor a cap - Detects charset from HTTP headers, HTML meta tags, or UTF-8 fallback
- Extracts readable content using tree-based parsers (stdlib or BeautifulSoup)
- Outputs clean text, raw HTML, or properly-converted Markdown (with correct nested-link handling)
Key options:
--url https://...— required--extract text— clean readable text (default)--extract html— raw HTML--extract markdown— Markdown conversion (tree-based; handles nested tags, GFM tables, fenced code blocks, blockquotes, inline code, ordered/unordered/nested lists, definition lists, images, emphasis)--encoding gbk— force charset for non-UTF-8 pages (else auto-detected from HTTP header / HTML meta / UTF-8 fallback)--timeout 15— request timeout in seconds--retries 3— max retries on transient errors (429/5xx/connection); falls back to a browser User-Agent when blocked--max-size BYTES— cap page size (default: unlimited; e.g.5242880for 5MB)--user-agent STR— custom User-Agent header--no-redirect— do not follow HTTP 3xx redirects (implemented for both the requests and stdlib paths)--proxy URL— HTTP/HTTPS proxy (e.g.http://corp-proxy:8080); respects existingHTTP_PROXY/HTTPS_PROXYenv vars when omitted--output FILE— save to file instead of stdout--auth-bearer TOKEN/--auth-bearer-file FILE—Authorization: Bearerheader; file variant reads first non-empty, non-#line; also honorsSEARXNG_BEARER_TOKENenv var--auth-basic USER:PASS/--auth-basic-file FILE—Authorization: Basicheader (auto base64-encoded); file variant +SEARXNG_BASIC_AUTHenv var also supported--verbose/-v— show debug-level diagnostics on stderr--quiet— suppress progress messages on stderr; only warnings and errors are shown--version— print version and exit
Extraction strategy (text mode):
- Strip non-content elements (script, style, nav, footer, header)
- Extract
<article>,<main>, or<body>content - Collapse whitespace, output clean UTF-8
- Warn if extracted text < 500 chars (likely JS-heavy or bot-blocked)
Completion criterion: Outputs page content. Non-zero exit on HTTP failure. Stderr carries warnings for low-confidence extraction.
Common Workflow
INSTANCE=https://my-searxng.example.com
# 1. Search + auto-fetch top 3 result pages (uses defaults: safesearch off,
# time_range year, json output, the 7 default engines)
python scripts/search.py -q "python asyncio tutorial" -i "$INSTANCE" --fetch 3
# 2. Brief format with full snippets (no truncation by default)
python scripts/search.py -q "rust memory safety" -i "$INSTANCE" --format brief --fetch 2
# 3. Pipe-friendly: search -> extract first URL
TOP_URL=$(python scripts/search.py -q "rust book" -i "$INSTANCE" --format urls --fail-fast | head -1)
# 4. Override engines and use POST for a long query
python scripts/search.py -q "ai news" -i "$INSTANCE" --engines google,duckduckgo \
--method POST --fetch 5 --fetch-timeout 15 --format json
# 5. Failover across two instances
python scripts/search.py -q "quantum computing" -i "$INSTANCE,https://backup.example.com"
JSON vs HTML fallback difference: When an instance supports format=json, search results include rich metadata (engine name, score, category) and fetched_source is "json". With HTML fallback, results are less structured and fetched_source is "html". The auto-fetch feature works identically in both cases.
Troubleshooting
--instance is required (unless configured)
Public-instance discovery has been removed. You must supply an instance, but you have three options (in priority order):
-i https://your-instance(CLI flag, comma-separated for failover)SEARXNG_INSTANCEenvironment variable (e.g.export SEARXNG_INSTANCE="https://a,https://b")- A config file:
./searxng.tomlor~/.config/searxng-cli/searxng.toml(instance = "..."orinstances = [...]), or./instances.txt/~/.config/searxng-cli/instances.txt(one URL per line,#comments)
Self-host SearXNG (Docker: docker run -d -p 8080:8080 searxng/searxng) or use an instance you trust.
All instances return 429 / timeout
Your instance is rate-limited. Wait a few minutes, pass multiple instances via -i a,b, or tune your instance's limiter settings.
Validate your instance list with --verify
Before relying on a multi-instance setup, run python scripts/search.py --verify -i a,b,c (optionally with --format brief for a human-readable table). It reports each instance's reachability, JSON-API support, and latency, then exits without searching. Combine with SEARXNG_INSTANCE or a config file to check your persisted list in one go.
JSON format blocked (HTML fallback)
Some instances disable format=json. Enable search.formats: [html, json] in your instance's settings.yml. The HTML fallback parser handles the rest automatically.
Certificate errors on Windows
Some instances use Let's Encrypt certificates. Run pip install certifi or upgrade Python's certifi bundle.
fetch.py output is empty or gibberish
Try --encoding gbk for Chinese sites, --encoding shift_jis for Japanese. If the page requires JavaScript, use a headless browser instead. If a page 3xx-redirects and you passed --no-redirect, an empty body is expected (that's the redirect response itself).
Caching, proxy, domain filter, batch
- Cache:
--cache-ttl 30caches results for 30 min; repeat the same query and you'll see[cache hit]on stderr.--cache-statsshows entries/age/size/path;--clear-cachewipes them. The cache key covers query + engines + categories + language + time_range + safesearch + pageno + method — different params get separate entries. Cache lives at$SEARXNG_CACHE_DIRor~/.cache/searxng-cli/cache.db(SQLite, WAL mode). - Proxy:
--proxy http://corp-proxy:8080setsHTTP_PROXY/HTTPS_PROXYfor both search and fetch;NO_PROXYdefaults tolocalhost,127.0.0.1,::1so local traffic stays direct. Existing proxy env vars are honored when--proxyis omitted. - Domain filter:
--include-domain(allowlist) and--exclude-domain(blocklist) run after search. Matching is case-insensitive and ignores a leadingwww.. If a domain appears in both lists, exclude wins (the result is dropped). - Batch:
--queries-file FILEruns one query per line (blank/#skipped) in sequence. JSON output is an array[{"query":..., "results":...}, ...]; a failed query becomes{"query":..., "error":...}but does not abort the batch. Exit 0 if any query succeeded, 1 only if all failed. - Config defaults:
searxng.tomlcan pre-set most flags (see the Quick Start example). Explicit CLI flags always override config values; string-valued ints from TOML are normalized automatically.
"ModuleNotFoundError: No module named '_config'"
Each script self-injects its directory into sys.path, so this should no longer occur. If it does, ensure _config.py sits next to search.py / fetch.py in the same scripts/ directory.
Common Pitfalls
-
JSON format blocked. Many instances disable
format=json. Enable it in your instance settings for rich metadata; otherwise the HTML fallback parser is used. -
Rate limiting.
search.pyretries each instance up to 3 times with exponential backoff and jitter, then fails over to the next instance. Supply multiple instances via-i a,bfor resilience. -
fetch.py extraction quality varies. The heuristic text extractor works well on articles and documentation but poorly on SPAs, login walls, and JavaScript-heavy pages. For JS-heavy pages, use a headless browser.
-
Encoding issues on Windows. The scripts output UTF-8. On cmd.exe, run
chcp 65001first. PowerShell handles UTF-8 natively. For non-UTF-8 pages, usefetch.py --encoding gbk. -
POST vs GET. Some instances handle POST differently or block it entirely. If POST search fails, try the default GET method.
-
Overriding defaults. Remember the new defaults (safesearch off, time_range year, the 7-engine list). Pass the corresponding flag to change any of them per query.
-
Cache vs. freshness.
--cache-ttlreturns cached results without hitting the network — fast, but stale. For time-sensitive queries (news, prices), use--cache-ttl 0or--clear-cache; the cache key includestime_range/pagenobut not wall-clock time, so aday-range query cached at 09:00 is served as-is until the TTL expires. -
Domain filter semantics.
--include-domain/--exclude-domainrun after search and only prune the already-returned results — they do not make the instance fetch more. An overly strict allowlist can yield zero results. Exclude wins over include when a domain is in both lists. -
Dedup changes output. Dedup is on by default — duplicate URLs (ignoring
utm_*/gclid/fragment, normalizing scheme/host case and param order) are collapsed. Use--no-dedupif you need the raw per-engine result set (e.g., comparing engine coverage). -
Sort changes
--max-resultsbehavior. With the default--sort-by score,--max-results Nkeeps the highest-scoring N results, not the first N in instance order. Use--sort-by noneto preserve the original order before limiting.
Verification Checklist
python scripts/search.py -q "test" -i <URL> --format jsonreturns resultspython scripts/search.py -q "test" -i <URL> --method POSTworks (optional)python scripts/search.py -q "test" -i a,bfails over across instancespython scripts/fetch.py -u "https://example.com"returns readable textpython scripts/fetch.py -u "https://example.com" -e markdownproduces valid markdownpython scripts/fetch.py -u "http://<redirecting>" --no-redirectdoes not follow the redirect- All scripts have
--helpand--version - Scripts run from any working directory (sys.path self-injection)
- Scripts exit 0 on success, non-zero on failure
python scripts/search.py --verify -i <URL>reports instance health (reachable / JSON support / latency), exit 0python scripts/search.py -q "test" -i <URL>showsDedup:on stderr when duplicates exist;--no-dedupsuppresses itpython scripts/search.py -q "test" -i <URL> --sort-by datereturns newest-first;--sort-by nonepreserves instance orderpython scripts/search.py -q "test" -i <URL> --format csvoutputs a CSV with headertitle,url,engine,score,published_date,contentpython scripts/search.py --config ./my-config.toml -q "test"loads the specified config and applies its defaultspython scripts/search.py -q "test" -i <URL> --verboseshows debug-level diagnostics on stderrpython scripts/search.py -q "test" -i <URL> --quietsuppresses progress messages; only warnings/errors on stderrpython scripts/fetch.py -u <URL> --verboseshows debug-level diagnostics on stderrpython scripts/search.py -q "test" -i <URL> --cache-ttl 30then re-run →[cache hit]on stderrpython scripts/search.py --cache-statsprints entries/size/path;--clear-cachereports count deletedpython scripts/search.py --queries-file queries.txt -i <URL> --format jsonemits a JSON arraypython scripts/search.py -q "test" -i <URL> --include-domain example.com --exclude-domain spam.comfilters as expectedpython scripts/search.py -q "test" -i <URL> --proxy http://proxy:8080routes through the proxypython scripts/search.py -q "test" -i <URL> --auth-bearer-file <FILE>authenticates without CLI token leakage- A failing search with
--format jsonemits{"error":..., "exit_code":1}to stdout python scripts/fetch.py -u <URL> --proxy http://proxy:8080routes through the proxypython scripts/fetch.py -u <URL> --auth-bearer-file <FILE>authenticates- Stderr carries warnings; stdout carries data