
Multi Search
- 773 installs
- 11 repo stars
- Updated February 20, 2026
- nex-zmh/agent-websearch-skill
multi-search is an agent skill that performs web searches with automatic engine failover across DuckDuckGo, Tavily, Bing API, and Bing scraper based on network conditions and API quotas.
About
multi-search is a Python agent skill that consolidates multiple search engines with automatic network detection and intelligent failover. In quality-priority mode the engine order is Tavily API at 1000 queries per month, then DuckDuckGo unlimited free search, then Bing API, then a Bing scraper fallback. The skill manages API quotas and network caching so agents get reliable results without manual engine selection. Dependencies include requests, tavily, and duckduckgo_search, with optional TAVILY_API_KEY and BING_API_KEY environment variables. Developers reach for multi-search when building agents that need resilient web research across varying network environments and quota constraints.
- Automatically selects from Tavily, DuckDuckGo, Bing API, and Bing crawler based on availability and priority
- Two operating modes: quality-first (Tavily priority) and balanced (free engines first)
- Built-in quota management and 5-minute network detection cache
- Supports both general search and specialized search_skills function for agent skill discovery
- Zero-config fallback to unlimited free engines when APIs are unavailable
Multi Search by the numbers
- 773 all-time installs (skills.sh)
- +20 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #1,352 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/nex-zmh/agent-websearch-skill --skill multi-searchAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 773 |
|---|---|
| repo stars | ★ 11 |
| Security audit | 2 / 3 scanners passed |
| Last updated | February 20, 2026 |
| Repository | nex-zmh/agent-websearch-skill ↗ |
How do agents search the web with automatic failover?
Get reliable web search results without worrying about API quotas, network blocks, or choosing the right engine.
Who is it for?
Agent developers building research workflows who need automatic search engine switching across blocked networks and varying API quota limits.
Skip if: Developers needing deep site crawling, JavaScript-rendered page extraction, or production search index hosting should use dedicated crawl infrastructure.
When should I use this skill?
Agent needs web search, API quota management, automatic engine switching, or reliable results across changing network environments.
What you get
Ranked web search results with engine selection logs, quota usage tracking, and network-adapted cache hits.
- search results
- quota usage logs
By the numbers
- Tavily API supports 1000 queries per month in quality-priority mode
- Integrates 4 search engines with automatic failover
Files
Multi-Search Skill - 智能多引擎搜索
本技能整合多个搜索引擎,自动检测网络环境,智能选择最佳可用引擎。
引擎优先级
质量优先模式 (prefer_quality=True)
1. Tavily API (1000次/月) - 质量最高,需 API Key 2. DuckDuckGo (无限免费) - 无需 API Key 3. Bing Web Search API (1000次/月) - 需 API Key 4. Bing 爬虫 (无限免费) - 最终回退
平衡模式 (prefer_quality=False, 默认)
1. DuckDuckGo (无限免费) - 优先免费引擎 2. Tavily API (1000次/月) - 如果配置了 API Key 3. Bing Web Search API (1000次/月) 4. Bing 爬虫 (无限免费)
核心能力
- 智能网络检测与引擎切换
- 自动配额管理(Tavily/Bing API)
- 支持网页内容抓取
- 5分钟网络检测缓存
使用方式
基本搜索
from multi_search import search
# 平衡模式 - 优先免费引擎
results = search("Python tutorial", max_results=5)
# 质量优先模式 - 优先使用 Tavily
results = search("AI research", max_results=5, prefer_quality=True)
# 强制重新检测网络(切换 VPN 后使用)
results = search("OpenClaw skills", max_results=5, force_network_check=True)搜索技能(自动质量优先)
from multi_search import search_skills
results = search_skills("OpenClaw AI agent automation", max_results=10)查看系统状态
from multi_search import get_status
status = get_status() # 使用缓存
status = get_status(force_network_check=True) # 强制重新检测抓取网页详细内容
from multi_search import search, fetch_web_content, fetch_search_results_content
# 搜索并抓取第一个结果的详细内容
results = search("OpenClaw new features", max_results=3)
if results:
content = fetch_web_content(results[0]['href'], max_length=3000)
# content['title'], content['content'], content['success']
# 批量抓取所有搜索结果的详细内容
enriched_results = fetch_search_results_content(results, max_length=2000)
for r in enriched_results:
if r.get('full_content'):
# 使用 summarize 技能总结内容
pass与 Summarize 技能结合使用
OpenClaw 工作流:
1. 使用 multi-search 搜索关键词
2. 选择感兴趣的搜索结果
3. 使用 fetch_web_content() 抓取网页内容
4. 使用 summarize 技能总结网页内容
5. 将摘要呈现给用户返回结果格式
[
{
'title': '结果标题',
'href': 'https://example.com',
'body': '结果摘要...',
'source': 'duckduckgo' # 或 'tavily', 'bing_api', 'bing_scraper'
}
]参数说明
query: 搜索关键词max_results: 最大结果数(默认5)prefer_quality: 是否优先质量(默认False)force_network_check: 是否强制重新检测网络(默认False)
注意事项
- DuckDuckGo: 免费无限,但某些网络环境无法访问
- Tavily: 质量高,需要 API key,1000次/月
- Bing API: 官方稳定,需要 Azure 账号,1000次/月
- Bing 爬虫: 免费无限,但可能受反爬影响
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Virtual Environment
.venv/
venv/
ENV/
env/
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
# API Keys and Secrets - IMPORTANT: Never commit real API keys!
# Use api_keys.example.json as template, then rename to api_keys.json
api_keys.json
!api_keys.example.json
# Cache and Data files (auto-generated on first run)
# Use .example files as templates
quota.json
network_cache.json
!quota.example.json
!network_cache.example.json
*.cache
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Logs
*.log
logs/
# Testing
.pytest_cache/
.coverage
htmlcov/
{
"tavily": "YOUR_TAVILY_API_KEY_HERE",
"bing_api": "YOUR_BING_API_KEY_HERE"
}
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2026 Nex-ZMH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
================================================================================
The full text of the GNU General Public License v3.0 can be found at:
https://www.gnu.org/licenses/gpl-3.0.txt
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Multi-Search Skill - 智能多引擎搜索
自动按优先级切换:DuckDuckGo -> Tavily -> Bing API -> Bing爬虫
自动检测网络环境,智能选择可用引擎
"""
import os
import sys
import json
import re
import html
import time
from datetime import datetime, timedelta
from typing import List, Dict, Optional
from urllib.parse import quote
if sys.platform == 'win32':
import codecs
sys.stdout = codecs.getwriter('utf-8')(sys.stdout.detach())
QUOTA_FILE = os.path.join(os.path.dirname(__file__), "quota.json")
NETWORK_CACHE_FILE = os.path.join(os.path.dirname(__file__), "network_cache.json")
API_KEYS_FILE = os.path.join(os.path.dirname(__file__), "api_keys.json")
MAX_TAVILY_QUOTA = 1000
MAX_BING_API_QUOTA = 1000
NETWORK_CHECK_INTERVAL = 300
def get_api_key(service: str) -> Optional[str]:
"""从环境变量或配置文件获取 API key"""
env_key = os.environ.get(f'{service}_API_KEY')
if env_key:
return env_key
if os.path.exists(API_KEYS_FILE):
try:
with open(API_KEYS_FILE, 'r', encoding='utf-8') as f:
keys = json.load(f)
return keys.get(service.lower())
except:
pass
return None
class NetworkChecker:
"""网络环境检测器 - 检测各引擎可用性"""
def __init__(self):
self.cache = self._load_cache()
def _load_cache(self) -> Dict:
"""加载网络检测缓存"""
if os.path.exists(NETWORK_CACHE_FILE):
try:
with open(NETWORK_CACHE_FILE, 'r', encoding='utf-8') as f:
cache = json.load(f)
last_check = datetime.fromisoformat(cache.get('last_check', '2000-01-01'))
if (datetime.now() - last_check).seconds < NETWORK_CHECK_INTERVAL:
return cache
except:
pass
return {'availability': {}, 'last_check': datetime.now().isoformat()}
def _save_cache(self):
"""保存网络检测缓存"""
try:
self.cache['last_check'] = datetime.now().isoformat()
with open(NETWORK_CACHE_FILE, 'w', encoding='utf-8') as f:
json.dump(self.cache, f, indent=2)
except Exception as e:
print(f"[WARN] Failed to save network cache: {e}")
def check_duckduckgo(self, force: bool = False) -> bool:
"""检测 DuckDuckGo 是否可用"""
if not force and 'duckduckgo' in self.cache['availability']:
return self.cache['availability']['duckduckgo']
try:
import requests
response = requests.get(
'https://duckduckgo.com/html/?q=test',
timeout=5,
headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'}
)
available = response.status_code == 200
self.cache['availability']['duckduckgo'] = available
self._save_cache()
return available
except:
self.cache['availability']['duckduckgo'] = False
self._save_cache()
return False
def check_bing(self, force: bool = False) -> bool:
"""检测 Bing 是否可用"""
if not force and 'bing' in self.cache['availability']:
return self.cache['availability']['bing']
try:
import requests
response = requests.get(
'https://www.bing.com',
timeout=5,
headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'}
)
available = response.status_code == 200
self.cache['availability']['bing'] = available
self._save_cache()
return available
except:
self.cache['availability']['bing'] = False
self._save_cache()
return False
def check_tavily(self, force: bool = False) -> bool:
"""检测 Tavily API 是否可用(需要配置 API key)"""
api_key = get_api_key('TAVILY')
if not api_key:
return False
if not force and 'tavily' in self.cache['availability']:
return self.cache['availability']['tavily']
try:
from tavily import TavilyClient
client = TavilyClient(api_key=api_key)
response = client.search(query='test', max_results=1)
available = len(response.get('results', [])) >= 0
self.cache['availability']['tavily'] = available
self._save_cache()
return available
except:
self.cache['availability']['tavily'] = False
self._save_cache()
return False
def get_availability(self, force_check: bool = False) -> Dict[str, bool]:
"""获取所有引擎的可用性状态"""
return {
'duckduckgo': self.check_duckduckgo(force_check),
'bing': self.check_bing(force_check),
'tavily': self.check_tavily(force_check)
}
class QuotaManager:
"""管理 API 使用配额"""
def __init__(self):
self.quota_data = self._load_quota()
def _load_quota(self) -> Dict:
"""加载配额数据"""
if os.path.exists(QUOTA_FILE):
try:
with open(QUOTA_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except:
pass
return {
"tavily": {"used": 0, "month": datetime.now().month},
"bing_api": {"used": 0, "month": datetime.now().month},
"last_reset": datetime.now().isoformat()
}
def _save_quota(self):
"""保存配额数据"""
try:
with open(QUOTA_FILE, 'w', encoding='utf-8') as f:
json.dump(self.quota_data, f, indent=2)
except Exception as e:
print(f"[WARN] Failed to save quota: {e}")
def _check_month_reset(self):
"""检查是否需要月度重置"""
current_month = datetime.now().month
for service in ["tavily", "bing_api"]:
if self.quota_data[service]["month"] != current_month:
self.quota_data[service]["used"] = 0
self.quota_data[service]["month"] = current_month
print(f"[INFO] Reset {service} quota for new month")
self._save_quota()
def use_quota(self, service: str) -> bool:
"""使用一次配额,返回是否成功"""
self._check_month_reset()
if service == "tavily":
if self.quota_data["tavily"]["used"] < MAX_TAVILY_QUOTA:
self.quota_data["tavily"]["used"] += 1
self._save_quota()
return True
elif service == "bing_api":
if self.quota_data["bing_api"]["used"] < MAX_BING_API_QUOTA:
self.quota_data["bing_api"]["used"] += 1
self._save_quota()
return True
return False
def get_quota_status(self) -> Dict:
"""获取配额状态"""
self._check_month_reset()
return {
"tavily": {
"used": self.quota_data["tavily"]["used"],
"limit": MAX_TAVILY_QUOTA,
"remaining": MAX_TAVILY_QUOTA - self.quota_data["tavily"]["used"]
},
"bing_api": {
"used": self.quota_data["bing_api"]["used"],
"limit": MAX_BING_API_QUOTA,
"remaining": MAX_BING_API_QUOTA - self.quota_data["bing_api"]["used"]
}
}
class SearchEngine:
"""搜索引擎基类"""
def search(self, query: str, max_results: int = 5) -> List[Dict]:
raise NotImplementedError
class DuckDuckGoSearch(SearchEngine):
"""DuckDuckGo 搜索 - 无需 API Key"""
def search(self, query: str, max_results: int = 5) -> List[Dict]:
"""使用 DuckDuckGo 搜索"""
try:
try:
from ddgs import DDGS
except ImportError:
from duckduckgo_search import DDGS
with DDGS() as ddgs:
results = []
for r in ddgs.text(query, max_results=max_results):
results.append({
'title': r.get('title', ''),
'href': r.get('href', ''),
'body': r.get('body', '')[:200] + "..." if len(r.get('body', '')) > 200 else r.get('body', ''),
'source': 'duckduckgo'
})
return results
except Exception as e:
print(f"[ERROR] DuckDuckGo search failed: {e}")
return []
class BingScraper(SearchEngine):
"""Bing 爬虫搜索 - 无需 API Key"""
def search(self, query: str, max_results: int = 5) -> List[Dict]:
"""使用 Bing 网页搜索"""
try:
import requests
from bs4 import BeautifulSoup
search_url = f"https://www.bing.com/search?q={quote(query)}"
response = requests.get(search_url, timeout=15, headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip, deflate',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1',
})
if response.status_code != 200:
print(f"[ERROR] Bing scraper failed: {response.status_code}")
return []
soup = BeautifulSoup(response.text, 'html.parser')
links = soup.find_all('a', href=True)
results = []
for link in links:
href = link.get('href', '')
text = link.get_text().strip()
if not text or len(text) < 5:
continue
if 'bing.com' in href or 'msn.com' in href:
continue
if not href.startswith('http'):
continue
results.append({
'title': text[:100],
'href': href,
'body': '',
'source': 'bing_scraper'
})
if len(results) >= max_results:
break
return results
except ImportError:
print("[ERROR] BeautifulSoup not installed. Run: pip install beautifulsoup4")
return []
except Exception as e:
print(f"[ERROR] Bing scraper failed: {e}")
return []
class TavilySearch(SearchEngine):
"""Tavily API 搜索 - 需要 API Key"""
def __init__(self):
self.api_key = get_api_key('TAVILY')
def is_available(self) -> bool:
return self.api_key is not None
def search(self, query: str, max_results: int = 5) -> List[Dict]:
"""使用 Tavily API 搜索"""
try:
from tavily import TavilyClient
client = TavilyClient(api_key=self.api_key)
response = client.search(
query=query,
max_results=max_results,
search_depth="basic"
)
results = []
for item in response.get("results", []):
results.append({
'title': item.get('title', ''),
'href': item.get('url', ''),
'body': item.get('content', '')[:200] + "..." if len(item.get('content', '')) > 200 else item.get('content', ''),
'source': 'tavily'
})
return results
except Exception as e:
print(f"[ERROR] Tavily search failed: {e}")
return []
class BingAPISearch(SearchEngine):
"""Bing Web Search API - 需要 API Key"""
def __init__(self):
self.api_key = os.environ.get("BING_API_KEY")
self.endpoint = "https://api.bing.microsoft.com/v7.0/search"
def is_available(self) -> bool:
return self.api_key is not None
def search(self, query: str, max_results: int = 5) -> List[Dict]:
"""使用 Bing Web Search API"""
try:
import requests
headers = {"Ocp-Apim-Subscription-Key": self.api_key}
params = {
"q": query,
"count": max_results,
"textDecorations": False,
"textFormat": "HTML"
}
response = requests.get(self.endpoint, headers=headers, params=params)
response.raise_for_status()
search_results = response.json()
results = []
for item in search_results.get("webPages", {}).get("value", []):
results.append({
'title': item.get('name', ''),
'href': item.get('url', ''),
'body': item.get('snippet', ''),
'source': 'bing_api'
})
return results
except Exception as e:
print(f"[ERROR] Bing API search failed: {e}")
return []
class MultiSearch:
"""多引擎搜索管理器"""
def __init__(self):
self.quota_manager = QuotaManager()
self.network_checker = NetworkChecker()
self.duckduckgo = DuckDuckGoSearch()
self.bing_scraper = BingScraper()
self.tavily = TavilySearch()
self.bing_api = BingAPISearch()
def search(self, query: str, max_results: int = 5, prefer_quality: bool = False, force_network_check: bool = False) -> List[Dict]:
"""
智能搜索 - 自动选择最佳引擎
Args:
query: 搜索关键词
max_results: 最大结果数
prefer_quality: 是否优先质量(优先使用 Tavily)
force_network_check: 是否强制重新检测网络
Returns:
搜索结果列表
"""
print("=" * 60)
print("🔍 Multi-Search - 智能多引擎搜索")
print("=" * 60)
print(f"[Query] {query}")
print(f"[Mode] {'Quality Priority' if prefer_quality else 'Balanced'}")
print("-" * 60)
print("[Network] Checking availability...")
availability = self.network_checker.get_availability(force_check=force_network_check)
print(f" DuckDuckGo: {'✅' if availability['duckduckgo'] else '❌'}")
print(f" Bing: {'✅' if availability['bing'] else '❌'}")
print(f" Tavily: {'✅' if availability['tavily'] else '❌'}")
print("-" * 60)
quota_status = self.quota_manager.get_quota_status()
print(f"[Quota] Tavily: {quota_status['tavily']['remaining']}/{MAX_TAVILY_QUOTA} remaining")
print(f"[Quota] Bing API: {quota_status['bing_api']['remaining']}/{MAX_BING_API_QUOTA} remaining")
print("-" * 60)
results = []
used_engine = ""
if prefer_quality and availability['tavily'] and quota_status['tavily']['remaining'] > 0:
print("[Strategy] Quality first: Trying Tavily...")
if self.quota_manager.use_quota("tavily"):
results = self.tavily.search(query, max_results)
used_engine = "Tavily"
if not results and availability['duckduckgo']:
print("[Strategy] Trying DuckDuckGo...")
results = self.duckduckgo.search(query, max_results)
used_engine = "DuckDuckGo"
if not results and self.bing_api.is_available() and quota_status['bing_api']['remaining'] > 0:
print("[Strategy] Trying Bing Web Search API...")
if self.quota_manager.use_quota("bing_api"):
results = self.bing_api.search(query, max_results)
used_engine = "Bing API"
if not results and availability['bing']:
print("[Strategy] Using Bing Scraper (fallback)...")
results = self.bing_scraper.search(query, max_results)
used_engine = "Bing Scraper"
if results:
print(f"\n[OK] {used_engine} returned {len(results)} results:\n")
for idx, r in enumerate(results, 1):
print(f"{idx}. {r['title']}")
print(f" Source: {r['source']}")
print(f" URL: {r['href']}")
if r['body']:
print(f" Summary: {r['body'][:150]}...")
print()
else:
print("[ERROR] All search engines failed")
print("[TIP] Try enabling VPN or check your network connection")
print("=" * 60)
return results
def get_status(self, force_network_check: bool = False) -> Dict:
"""获取搜索系统状态"""
quota = self.quota_manager.get_quota_status()
availability = self.network_checker.get_availability(force_check=force_network_check)
return {
"quota": quota,
"network": availability,
"engines": {
"duckduckgo": {"available": availability['duckduckgo'], "type": "unlimited"},
"bing_scraper": {"available": availability['bing'], "type": "unlimited"},
"tavily": {"available": availability['tavily'], "type": "api"},
"bing_api": {"available": self.bing_api.is_available(), "type": "api"}
}
}
def search(query: str, max_results: int = 5, prefer_quality: bool = False, force_network_check: bool = False) -> List[Dict]:
"""
执行多引擎搜索
Args:
query: 搜索关键词
max_results: 最大结果数
prefer_quality: 是否优先质量(会优先使用 Tavily)
force_network_check: 是否强制重新检测网络
Returns:
搜索结果列表
"""
multi_search = MultiSearch()
return multi_search.search(query, max_results, prefer_quality, force_network_check)
def search_skills(query: str = "OpenClaw AI agent skills", max_results: int = 10, force_network_check: bool = False) -> List[Dict]:
"""搜索 OpenClaw/AI Agent 相关技能"""
return search(query, max_results, prefer_quality=True, force_network_check=force_network_check)
def get_status(force_network_check: bool = False):
"""获取搜索系统状态"""
multi_search = MultiSearch()
status = multi_search.get_status(force_network_check=force_network_check)
print("=" * 60)
print("📊 Multi-Search System Status")
print("=" * 60)
print("\n[Network Status]")
for engine, available in status["network"].items():
status_icon = "✅" if available else "❌"
print(f" {status_icon} {engine}")
print("\n[Quota Status]")
for service, info in status["quota"].items():
print(f" {service}: {info['used']}/{info['limit']} used, {info['remaining']} remaining")
print("\n[Engine Status]")
for engine, info in status["engines"].items():
status_icon = "✅" if info["available"] else "❌"
print(f" {status_icon} {engine} ({info['type']})")
print("=" * 60)
return status
class WebContentFetcher:
"""网页内容抓取器"""
def __init__(self, timeout: int = 15):
self.timeout = timeout
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
}
def fetch(self, url: str, max_length: int = 5000) -> Dict:
"""抓取网页内容"""
try:
import requests
from bs4 import BeautifulSoup
response = requests.get(url, timeout=self.timeout, headers=self.headers)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
for script in soup(["script", "style", "nav", "footer", "header"]):
script.decompose()
title = soup.title.string if soup.title else ""
text = soup.get_text(separator='\n', strip=True)
lines = [line.strip() for line in text.splitlines() if line.strip()]
content = '\n'.join(lines)
if len(content) > max_length:
content = content[:max_length] + "..."
return {
'title': title,
'content': content,
'url': url,
'success': True
}
except Exception as e:
return {
'title': '',
'content': '',
'url': url,
'success': False,
'error': str(e)
}
def fetch_web_content(url: str, max_length: int = 5000) -> Dict:
"""抓取网页内容的便捷函数"""
fetcher = WebContentFetcher()
return fetcher.fetch(url, max_length)
def fetch_search_results_content(results: List[Dict], max_length: int = 2000) -> List[Dict]:
"""批量抓取搜索结果的详细内容"""
fetcher = WebContentFetcher()
enriched = []
for result in results:
enriched_result = result.copy()
if result.get('href'):
content = fetcher.fetch(result['href'], max_length)
if content['success']:
enriched_result['full_content'] = content
enriched.append(enriched_result)
return enriched
if __name__ == "__main__":
print("Multi-Search Skill - 智能多引擎搜索")
print("Usage: from multi_search import search, get_status")
demo = False
if demo:
search("Python tutorial", max_results=3)
search("OpenClaw AI agent skills 2025", max_results=3, force_network_check=True)
{
"availability": {
"duckduckgo": true,
"bing": true,
"tavily": false
},
"last_check": "2026-01-01T00:00:00.000000"
}
{
"tavily": {
"used": 0,
"month": 1
},
"bing_api": {
"used": 0,
"month": 1
},
"last_reset": "2026-01-01T00:00:00.000000"
}
<p align="center"> <img src="https://raw.githubusercontent.com/Nex-ZMH/Agent-websearch-skill/main/logo.jpg" width="660" alt="Agent WebSearch Skill Logo"> </p>
<h1 align="center">Agent WebSearch Skill 🔍</h1>
<p align="center"> <b>Intelligent Multi-Engine Search — Works With or Without VPN</b> </p>
<p align="center"> <i>Zero config. Zero API keys. Auto-fallback from DuckDuckGo → Tavily → Bing API → Bing Scraper.</i> </p>
<p align="center"> <a href="https://opensource.org/licenses/GPL-3.0"> <img src="https://img.shields.io/badge/License-GPL%203.0-blue.svg?style=flat-square" alt="License: GPL-3.0"> </a> <a href="https://www.python.org/"> <img src="https://img.shields.io/badge/Python-3.8%2B-green.svg?style=flat-square" alt="Python: 3.8+"> </a> <a href="https://github.com/Nex-ZMH/Agent-websearch-skill"> <img src="https://img.shields.io/badge/Platform-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey.svg?style=flat-square" alt="Platform"> </a> <img src="https://img.shields.io/badge/No%20VPN%20Required-✓-success.svg?style=flat-square" alt="No VPN Required"> </p>
<p align="center"> Built by <a href="https://github.com/Nex-ZMH">Nex-ZMH</a>, an energy industry AI explorer from a remote mountain village of China. </p>
<p align="center"> 🌐 Languages: <a href="#english">English</a> · <a href="#中文">简体中文</a> · </p>
<p align="center"> ⚡️Quick Routes: <a href="#getting-started">Getting Started</a> · <a href="#features">Features</a> · <a href="#installation">Installation</a> · </p>
---
The Problem We Solve
🚫 Common Pain Points
| Issue | Description |
|---|---|
| 🔒 Cannot Get Foreign API Keys | Brave Search require foreign credit cards or Visa cards, difficult for users in China |
| 🌐 Unstable Network Environment | VPN connections are intermittent, search engine availability changes constantly |
| 💰 Limited API Quota | Search functionality stops working after free quota is exhausted |
| 🔄 Tedious Manual Switching | Need to manually change search engines every time network changes |
💡 Why Not Use Brave Search?
>
OpenClaw's built-in Brave Search requires:
- ✅ VPN access to reach the service
- ✅ Visa/MasterCard credit card for account registration
- ✅ Payment method binding to get API Key
>
For most users in China, these barriers are hard to overcome. This project has zero barriers — just clone and use!
✅ Our Solution
Agent WebSearch Skill solves these problems through intelligent engine selection strategy:
- ✨ Zero Config Ready — Works with Bing Scraper even without any API Key
- 🔄 Auto Failover — Automatically switches to next available engine when one fails
- 📊 Smart Quota Management — Prioritizes free engines to save API quota for critical moments
- 🌐 Network Adaptive — Auto-detects network environment and selects optimal engine
---
English
Getting Started
Agent WebSearch Skill — An intelligent multi-engine search solution that works in any network environment. Whether you have VPN access or not, whether you have API keys or not, this tool ensures you can always perform web searches seamlessly.
Features
- 🔍 Multi-Engine Architecture — DuckDuckGo, Tavily, Bing API, Bing Scraper with auto-fallback
- 🔄 Auto Failover — Automatically switches to next available engine when one fails
- 🌐 Network Adaptive — Detects network environment and selects optimal engine
- 📊 Smart Quota Management — Prioritizes free engines to save API quota
- ⚡ Zero Config — Works out of the box without any API keys
- 🎯 Quality Mode — Optional quality-first mode for important searches
Installation
# Clone repository
git clone https://github.com/Nex-ZMH/Agent-websearch-skill.git
cd Agent-websearch-skill
# Install dependencies
pip install requests tavily-python duckduckgo-search beautifulsoup4Usage
from multi_search import search, get_status, fetch_web_content
# Basic search — auto-select best engine
results = search("Python async tutorial", max_results=5)
# Quality-first mode — for important searches
results = search("AI research papers 2024", max_results=5, prefer_quality=True)
# Force network recheck after VPN switch
results = search("latest tech news", force_network_check=True)
# Check system status
status = get_status()
# Fetch detailed content from URL
content = fetch_web_content(results[0]['href'], max_length=3000)Smart Search Strategy
┌─────────────────────────────────────────────────────────────┐
│ Search Engine Selection Strategy │
├─────────────────────────────────────────────────────────────┤
│ │
│ Balanced Mode (Default) — Free engines first, save quota │
│ ┌──────────┐ ┌─────────┐ ┌──────────┐ ┌─────────┐ │
│ │DuckDuckGo│ → │ Tavily │ → │ Bing API │ → │ Bing │ │
│ │ (Free) │ │(API) │ │ (API) │ │ Scraper │ │
│ └──────────┘ └─────────┘ └──────────┘ └─────────┘ │
│ ↓ ↓ ↓ ↓ │
│ Needs VPN VPN+API VPN+API Works in China │
│ │
│ Quality First Mode — Premium APIs first for best results │
│ ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │
│ │ Tavily │ → │DuckDuckGo│ → │ Bing API │ → │ Bing │ │
│ │(Premium)│ │ (Free) │ │ (API) │ │ Scraper │ │
│ └─────────┘ └──────────┘ └──────────┘ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘Engine Comparison
| Engine | VPN Required | API Key | Monthly Quota | Quality | Best For |
|---|---|---|---|---|---|
| DuckDuckGo | ✅ Yes | ❌ No | ♾️ Unlimited | ⭐⭐⭐ | Daily searches |
| Tavily API | ✅ Yes | ✅ Yes | 1000 | ⭐⭐⭐⭐⭐ | AI Agents, important searches |
| Bing API | ✅ Yes | ✅ Yes | 1000 | ⭐⭐⭐⭐ | Official stable search |
| Bing Scraper | ❌ No | ❌ No | ♾️ Unlimited | ⭐⭐⭐ | Fallback without VPN |
Why Choose Us?
Scenario 1: No VPN, No API Key (China mainland)
Search → DuckDuckGo fails → Skip Tavily → Skip Bing API → Bing Scraper succeeds ✅
Result: Works perfectly without any configuration!Scenario 2: Has VPN, Has Tavily API Key
Search → DuckDuckGo succeeds ✅
Result: Uses free engine, saves API quotaScenario 3: Unstable Network
Search → DuckDuckGo fails → Tavily succeeds ✅
Result: Auto-switch, seamless experienceAPI Configuration (Optional)
Note: This project works out of the box without any configuration!
Method 1: Environment Variables (Recommended)
export TAVILY_API_KEY="your-tavily-api-key"
export BING_API_KEY="your-bing-api-key"Method 2: Configuration File
cp api_keys.example.json api_keys.json
# Edit api_keys.json with your keysRequirements
- Python 3.8+
requeststavily-pythonduckduckgo-searchbeautifulsoup4
---
中文
简介
Agent WebSearch Skill — 一款智能多引擎搜索解决方案,在任何网络环境下都能正常工作。无论你是否有科学上网,无论你是否有 API Key,这个工具都能确保你顺畅地进行网络搜索。
功能特性
- 🔍 多引擎架构 — DuckDuckGo、Tavily、Bing API、Bing 爬虫,自动故障转移
- 🔄 自动切换 — 一个引擎失败,自动切换到下一个可用引擎
- 🌐 网络自适应 — 自动检测网络环境,选择最优引擎
- 📊 智能配额管理 — 优先使用免费引擎,节省 API 配额
- ⚡ 零配置 — 无需任何 API Key,开箱即用
- 🎯 质量模式 — 可选的质量优先模式,适合重要搜索
安装方法
# 克隆仓库
git clone https://github.com/Nex-ZMH/Agent-websearch-skill.git
cd Agent-websearch-skill
# 安装依赖
pip install requests tavily-python duckduckgo-search beautifulsoup4使用方法
from multi_search import search, get_status, fetch_web_content
# 基本搜索 — 自动选择最优引擎
results = search("Python 异步编程教程", max_results=5)
# 质量优先模式 — 适合重要搜索
results = search("AI 论文 2024", max_results=5, prefer_quality=True)
# 切换网络后强制重新检测
results = search("最新科技新闻", force_network_check=True)
# 查看当前系统状态
status = get_status()
# 抓取网页详细内容
content = fetch_web_content(results[0]['href'], max_length=3000)智能搜索策略
┌─────────────────────────────────────────────────────────────┐
│ 搜索引擎选择策略 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 平衡模式(默认)— 优先免费引擎,节省 API 配额 │
│ ┌─────────┐ ┌─────────┐ ┌──────────┐ ┌─────────┐ │
│ │DuckDuckGo│ → │ Tavily │ → │ Bing API │ → │ Bing │ │
│ │ (免费) │ │(需API) │ │ (需API) │ │ 爬虫 │ │
│ └─────────┘ └─────────┘ └──────────┘ └─────────┘ │
│ ↓ ↓ ↓ ↓ │
│ 需科学上网 需科学上网+API 需科学上网+API 国内直连 │
│ │
│ 质量优先模式 — 优先高质量 API,适合重要搜索 │
│ ┌─────────┐ ┌─────────┐ ┌──────────┐ ┌─────────┐ │
│ │ Tavily │ → │DuckDuckGo│ → │ Bing API │ → │ Bing │ │
│ │(高质量) │ │ (免费) │ │ (需API) │ │ 爬虫 │ │
│ └─────────┘ └─────────┘ └──────────┘ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘各引擎特点对比
| 引擎 | 需要科学上网 | 需要 API Key | 月配额 | 搜索质量 | 适用场景 |
|---|---|---|---|---|---|
| DuckDuckGo | ✅ 需要 | ❌ 不需要 | ♾️ 无限 | ⭐⭐⭐ | 日常搜索首选 |
| Tavily API | ✅ 需要 | ✅ 需要 | 1000次 | ⭐⭐⭐⭐⭐ | AI Agent、重要搜索 |
| Bing API | ✅ 需要 | ✅ 需要 | 1000次 | ⭐⭐⭐⭐ | 官方稳定搜索 |
| Bing 爬虫 | ❌ 不需要 | ❌ 不需要 | ♾️ 无限 | ⭐⭐⭐ | 国内无科学上网时的保底方案 |
为什么选择我们?
场景 1:国内用户,没有科学上网,没有 API Key
用户搜索 → DuckDuckGo 失败 → Tavily 跳过 → Bing API 跳过 → Bing 爬虫成功 ✅
结果:正常返回搜索结果,完全可用!场景 2:有科学上网,有 Tavily API Key
用户搜索 → DuckDuckGo 成功 ✅
结果:使用免费引擎,节省 API 配额场景 3:网络不稳定,时断时续
用户搜索 → DuckDuckGo 失败 → Tavily 成功 ✅
结果:自动切换,用户无感知API 配置(可选)
重要:本项目无需任何配置即可使用!以下配置仅用于解锁高级功能。
方法 1:环境变量(推荐)
export TAVILY_API_KEY="your-tavily-api-key"
export BING_API_KEY="your-bing-api-key"方法 2:配置文件
cp api_keys.example.json api_keys.json
# 编辑 api_keys.json 填入你的密钥系统要求
- Python 3.8+
requeststavily-pythonduckduckgo-searchbeautifulsoup4
---
Roadmap
- [ ] Add Google Search API support
- [ ] Implement async/await for parallel searches
- [ ] Add rate limiting configuration
- [ ] Support custom search engine priority
- [ ] Add Searxng integration for privacy-focused users
---
Author
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.
Related skills
FAQ
What search engines does multi-search support?
multi-search supports Tavily API, DuckDuckGo, Bing API, and a Bing scraper fallback. Quality-priority mode tries Tavily first at 1000 queries per month, then unlimited DuckDuckGo, then Bing options.
What Python packages does multi-search require?
multi-search requires requests, tavily, and duckduckgo_search installed via pip. Optional TAVILY_API_KEY and BING_API_KEY environment variables unlock higher-quality API engines.
Is Multi Search safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.