--- name: web-research-fallback description: "Fallback web research when web_search/web_extract tools are unavailable — curl-based scraping, content extraction from JS-heavy SPAs, and structured report compilation." platforms: [linux, macos] --- # Web Research Fallback ## When to use When web_search and/or web_extract tools fail with "Web tools are not configured" (missing Firecrawl API key), and you need to perform web research. Use this approach to gather information directly via curl and parse HTML content without relying on a configured web scraping backend. Also use when you need to extract data from websites that block or rate-limit bot-friendly scraping services — curl with realistic User-Agent headers often succeeds where managed scrapers fail. ## Multi-API Stacking Strategy As of Jul 2026, the IT Pro Partner infrastructure uses a layered web scraping strategy. Different APIs for different use cases, all configured and accessible from this server: | Service | Cost | Monthly Credits | JS Rendering | Best For | |---------|------|----------------|-------------|----------| | **SearXNG** (self-hosted) | $0 | Unlimited | No | General web search — Google/Bing/DDG aggregation | | **Firecrawl** | $0 (Starter) | 1,000 | Limited | Quick content extraction, clean markdown, cached lookups | | **ScrapingAnt** | $19/mo ($0 free tier) | 100K (10K free) | Headless Chrome | JS-heavy sites (SPAs, login gates), gov sites, LinkedIn, Indeed, CBDriver | | **Apify** (future) | ~$30-50/mo | Pre-built actors | Yes | LinkedIn people search, social media scraping, structured data extraction | **Routing logic:** - **General search** -> SearXNG (127.0.0.1:8888, unlimited) - **Quick content fetch** -> Firecrawl free tier (1K/mo covers daily ops) - **JS-heavy sites** (government, job boards, LinkedIn public profiles) -> ScrapingAnt - **LinkedIn deep search** -> Apify actor (when needed, ~$30-50/mo) **Firecrawl is NOT the primary scrape tool.** SearXNG is for discovery/search. Firecrawl is for clean content extraction of pages you already have the URL for. ScrapingAnt is for the hard stuff. Structure searches accordingly — use SearXNG to find URLs, Firecrawl/ScrapingAnt to extract content. ### Credential storage All API keys live in `~/.hermes/.env`: - `FIRECRAWL_API_KEY`: set in .env and Hermes config - `SCRAPINGANT_API_KEY`: set in .env, used via terminal curl calls No keys in config.yaml, no standalone token files. The Hermes config.web.backend setting may still say `firecrawl` but this only controls the native `web_search`/`web_extract` tools (which use Firecrawl). The `curl`-based SearXNG and ScrapingAnt calls are done via terminal commands. ### Usage tracking A cron job at 9AM daily tracks Firecrawl credits via `/root/.hermes/scripts/track-firecrawl.py`. The portal Usage tab shows current month usage. When ScrapingAnt is added to tracking, extend this script. ## Tool priority order (for reference) 1. **web_search/web_extract** (native Hermes tools — use when API keys are configured) 2. **web-research-fallback** (this skill — SearXNG curl or ScrapingAnt for JS pages) 3. **terminal + curl** (last resort — individual page fetches) ## Core technique When web tools error out with "NOT_CONFIGURED" or API key missing: ### SearXNG JSON Search — lead generation When you need to *discover* URLs (not just fetch known pages), use the self-hosted SearXNG instance at `http://127.0.0.1:8888` with its JSON API: ```bash curl -s "http://127.0.0.1:8888/search?q=query+terms&format=json&language=en-US&pageno=1" -o /tmp/search_results.json ``` Then parse with a saved Python script (never pipe curl to python3 directly — see pitfall below): ```python #!/usr/bin/env python3 import json, sys data = json.load(sys.stdin) if sys.stdin.isatty() else json.load(open(sys.argv[1])) for r in data.get('results', []): title = r.get('title', 'N/A') url = r.get('url', 'N/A') content = r.get('content', '')[:250] print(f" Title: {title}\n URL: {url}\n Desc: {content}\n") ``` Usage: `python3 /tmp/parse_search.py /tmp/search_results.json` SearXNG returns up to 20 results per page by default. Combine with `pageno=2` etc. for more results. This is much faster than fetching individual pages — it gives you titles, URLs, and snippets in one call, letting you pick which pages to deep-dive on. ### Step 1: Fetch the page with curl ```bash curl -sL -o /tmp/output.html -w "%{http_code}" "https://target-url.com" \ -H "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" ``` Check exit code and HTTP status. 200 is good; 404 means the page doesn't exist; 3xx means redirect (use -L to follow). ### Step 2: Extract text content from HTML ```bash # Strip all HTML tags to get readable text cat /tmp/output.html | sed 's/<[^>]*>/\\n/g' | sed '/^$/d' | head -N # Or extract all text for full content cat /tmp/output.html | sed 's/<[^>]*>/\\n/g' | sed '/^$/d' ``` **For large/complex pages where `sed` produces empty output** (e.g. 100K+ line WordPress/Elementor pages), use a Python script instead: ```bash # Create the extraction script, then run it cat > /tmp/extract_text.py << 'PYEOF' import re, sys with open(sys.argv[1]) as f: html = f.read() html = re.sub(r'<(script|style)[^>]*>.*?', '', html, flags=re.DOTALL) html = re.sub(r'<[^>]+>', '\n', html) lines = [line.strip() for line in html.split('\n')] for line in lines: if line and len(line) > 10: print(line) PYEOF python3 /tmp/extract_text.py /tmp/output.html | head -200 ``` The Python approach properly removes `