-
Notifications
You must be signed in to change notification settings - Fork 0
Custom Search Engines
Learn how to add, configure, and manage custom search engines in NaviDuck. Extend your browsing capabilities with specialized search tools.
- Quick Start
- Understanding Search Engine Structure
- Adding Custom Search Engines
- Popular Engine Templates
- Advanced Configuration
- Troubleshooting
- Pro Tips
- Go to any search website
- Search for "test"
- Look at the URL in address bar
- Note the pattern
Example:
https://www.google.com/search?q=test
# NaviDuck already supports adding via code
# For now, edit the source file:
nano naviduck.pyAround line 110-160, find:
SEARCH_ENGINES = {
"ddg": { ... },
"google": { ... },
# Add your custom engine here
}Add this example (YouTube search):
"youtube": {
"name": "YouTube",
"url": "https://www.youtube.com/results",
"params": {"search_query": "{query}"},
"icon": "VIDEO",
"requires_tor": False,
"enabled": True,
"type": "html"
},# Restart NaviDuck
search youtube python tutorial"engine_id": { # Unique identifier (lowercase, no spaces)
"name": "Display Name", # Name shown to users
"url": "https://search.example.com/search", # Base search URL
"params": { # URL parameters
"q": "{query}", # {query} will be replaced
"lang": "en" # Static parameters
},
"icon": "ICON_NAME", # Icon from Icons class
"requires_tor": False, # True if needs Tor
"enabled": True, # Start enabled or disabled
"type": "html" # html, api, or json
}"params": {
"q": "{query}", # User's search query
"page": "{page}", # Page number (future)
"sort": "{sort}" # Sort order (future)
}"params": {
"q": "{query}",
"format": "json", # Always "json"
"safe": "on", # Always safe search on
"hl": "en" # Always English
}"params": {
"q": "{query}",
"api_key": "YOUR_KEY", # Your API key
"format": "json",
"count": "10"
}"type": "html" # Returns HTML to parse
# Examples: Google, DuckDuckGo HTML, Brave"type": "api" # Returns JSON/XML
# Examples: Wikipedia API, DuckDuckGo APISome engines need special parsing code. You'll need to extend the parse_results method in SearchManager.
Around line 110-160 in naviduck.py:
SEARCH_ENGINES = {
# Existing engines...
# Add your custom engines here
"your_engine": {
# Configuration
}
}- Backup your file first
- Find the SEARCH_ENGINES dictionary
- Add your engine configuration
- Test immediately
- Restart NaviDuck
"github": {
"name": "GitHub",
"url": "https://github.com/search",
"params": {"q": "{query}", "type": "repositories"},
"icon": "GITHUB",
"requires_tor": False,
"enabled": True,
"type": "html"
},// ~/.naviduck_engines.json
{
"github": {
"name": "GitHub",
"url": "https://github.com/search",
"params": {"q": "{query}"}
}
}# Add engine interactively
engine add
# Add with parameters
engine add --name "GitHub" --url "https://github.com/search" --param "q={query}"
# Import from file
engine import engines.json# 1. Restart NaviDuck
python naviduck.py
# 2. Check it appears in list
engines
# Should see your new engine
# 3. Test search
search your_engine test query
# 4. Check results
# Should show search results# If no results appear:
# 1. Check URL format
# 2. Check parameter names
# 3. Check if site blocks bots
# 4. Try with Tor if blocked "twitter": {
"name": "Twitter",
"url": "https://twitter.com/search",
"params": {"q": "{query}"},
"icon": "CHAT",
"requires_tor": False,
"enabled": True,
"type": "html"
}, "reddit": {
"name": "Reddit",
"url": "https://www.reddit.com/search",
"params": {"q": "{query}"},
"icon": "USER",
"requires_tor": False,
"enabled": True,
"type": "html"
}, "stackoverflow": {
"name": "Stack Overflow",
"url": "https://stackoverflow.com/search",
"params": {"q": "{query}"},
"icon": "CODE",
"requires_tor": False,
"enabled": True,
"type": "html"
}, "github_advanced": {
"name": "GitHub Advanced",
"url": "https://github.com/search/advanced",
"params": {"q": "{query}"},
"icon": "GITHUB",
"requires_tor": False,
"enabled": True,
"type": "html"
}, "npm": {
"name": "NPM",
"url": "https://www.npmjs.com/search",
"params": {"q": "{query}"},
"icon": "CODE",
"requires_tor": False,
"enabled": True,
"type": "html"
}, "scholar": {
"name": "Google Scholar",
"url": "https://scholar.google.com/scholar",
"params": {"q": "{query}", "hl": "en"},
"icon": "BOOKMARK",
"requires_tor": False,
"enabled": True,
"type": "html"
}, "arxiv": {
"name": "arXiv",
"url": "https://arxiv.org/search/advanced",
"params": {
"query": "{query}",
"searchtype": "all",
"source": "header"
},
"icon": "FILE",
"requires_tor": False,
"enabled": True,
"type": "html"
}, "youtube_alt": {
"name": "YouTube",
"url": "https://www.youtube.com/results",
"params": {
"search_query": "{query}",
"sp": "CAI%253D" # Sort by relevance
},
"icon": "VIDEO",
"requires_tor": False,
"enabled": True,
"type": "html"
}, "imdb": {
"name": "IMDb",
"url": "https://www.imdb.com/find",
"params": {"q": "{query}"},
"icon": "VIDEO",
"requires_tor": False,
"enabled": True,
"type": "html"
}, "amazon": {
"name": "Amazon",
"url": "https://www.amazon.com/s",
"params": {"k": "{query}"},
"icon": "SHOPPING", # Would need new icon
"requires_tor": False,
"enabled": True,
"type": "html"
}, "ebay": {
"name": "eBay",
"url": "https://www.ebay.com/sch/i.html",
"params": {"_nkw": "{query}"},
"icon": "SHOPPING",
"requires_tor": False,
"enabled": True,
"type": "html"
}, "startpage": {
"name": "StartPage",
"url": "https://www.startpage.com/sp/search",
"params": {"query": "{query}"},
"icon": "SHIELD",
"requires_tor": False,
"enabled": True,
"type": "html"
}, "searx": {
"name": "Searx",
"url": "https://searx.example.com/search",
"params": {"q": "{query}"},
"icon": "SEARCH",
"requires_tor": False,
"enabled": True,
"type": "html"
}, "baidu": {
"name": "Baidu",
"url": "https://www.baidu.com/s",
"params": {"wd": "{query}"},
"icon": "SEARCH",
"requires_tor": False,
"enabled": True,
"type": "html"
}, "yandex": {
"name": "Yandex",
"url": "https://yandex.com/search/",
"params": {"text": "{query}"},
"icon": "SEARCH",
"requires_tor": False,
"enabled": True,
"type": "html"
},Some websites return HTML that needs special handling. You need to extend the parse_results method.
# In SearchManager.parse_results method
# Add after existing engine parsers:
elif engine == "your_engine":
# Custom parsing logic
html = response.text
# Example: Find all links with titles
import re
pattern = r'<a[^>]+href="([^"]+)"[^>]*>([^<]+)</a>'
matches = re.findall(pattern, html)
for url, title in matches[:10]:
results.append({
'title': title[:80],
'url': url,
'snippet': f"Result from Your Engine",
'engine': 'Your Engine'
})# 1. Add engine to SEARCH_ENGINES
"custom_engine": {
"name": "Custom Engine",
"url": "https://custom.search/search",
"params": {"q": "{query}"},
"icon": "SEARCH",
"requires_tor": False,
"enabled": True,
"type": "custom" # Note: custom type
}
# 2. Add parser in parse_results
elif engine == "custom_engine":
# Your custom parsing code
# Extract results from response "hackernews": {
"name": "Hacker News",
"url": "https://hn.algolia.com/api/v1/search",
"params": {
"query": "{query}",
"tags": "story",
"hitsPerPage": "10"
# No API key needed for this one
},
"icon": "CODE",
"requires_tor": False,
"enabled": True,
"type": "api" # Returns JSON
},# Method 1: Environment variable
import os
api_key = os.getenv("MY_ENGINE_API_KEY")
# Method 2: Config file (planned)
# ~/.naviduck_api_keys.json
{
"my_engine": "your_api_key_here"
}
# Method 3: Prompt on first use
if not api_key:
api_key = get_input("Enter API key for My Engine:")
save_to_config("my_engine_api", api_key) "onion_engine": {
"name": "Onion Search",
"url": "http://onionengine.onion/search",
"params": {"q": "{query}"},
"icon": "TOR",
"requires_tor": True, # Requires Tor
"enabled": True,
"type": "html"
},# For specific engine proxy
"custom_proxy_engine": {
"name": "Proxied Engine",
"url": "https://blocked-site.com/search",
"params": {"q": "{query}"},
"proxy": "http://proxy:8080", # Custom proxy
"requires_tor": False,
"enabled": True,
"type": "html"
}# In parse_results for your engine:
results.append({
'title': f"🎬 {title}", # Add emoji/icon
'url': url,
'snippet': snippet,
'engine': 'Your Engine',
'category': 'Movies', # Extra metadata
'year': '2024' # Extra metadata
})# Extract more data for better display
rating = extract_rating(html)
duration = extract_duration(html)
price = extract_price(html)
snippet = f"{rating} ⭐ | {duration} | {price}"# Add delay between requests
import time
time.sleep(1) # 1 second delay
# Or in engine config:
"rate_limit": 1.0, # Seconds between requests
"max_requests": 10 # Max requests per minute# Check robots.txt before scraping
import urllib.robotparser
rp = urllib.robotparser.RobotFileParser()
rp.set_url("https://site.com/robots.txt")
rp.read()
if rp.can_fetch("*", url):
# Proceed with requestDiagnosis steps:
# 1. Test URL manually
curl "https://engine.com/search?q=test"
# Does it return results?
# 2. Check parameters
# Are parameter names correct?
# 3. Check for CAPTCHA
# Does site show CAPTCHA to bots?
# 4. Check HTML structure
# Has site changed its layout?Solutions:
# A. Update parameter names
# Check site's search form
# B. Add user-agent header
# Some sites block default Python user-agent
# C. Use Tor
# Some sites block non-Tor requests
# D. Update parser
# Site may have changed HTML# 1. Check if site is up
ping engine.com
# 2. Check firewall/ISP block
# Try from different network
# 3. Check if HTTPS required
# Change http:// to https://
# 4. Check for regional blocks
# Try with Tor from different country# For API engines:
# 1. Check response format
curl -H "Accept: application/json" ...
# 2. Check API documentation
# Verify expected response format
# 3. Check authentication
# API key may be required# Add debug prints to parse_results
print(f"DEBUG: Engine {engine}, URL: {response.url}")
print(f"DEBUG: Response length: {len(response.text)}")
print(f"DEBUG: First 500 chars: {response.text[:500]}")# Save HTML for analysis
with open(f"debug_{engine}.html", "w", encoding="utf-8") as f:
f.write(response.text)
print(f"Saved response to debug_{engine}.html")# Test regex patterns separately
import re
test_html = "<a href='test'>Title</a>"
pattern = r"<a[^>]+href=['\"]([^'\"]+)['\"][^>]*>([^<]+)</a>"
matches = re.findall(pattern, test_html)
print(f"Matches: {matches}")# Character encoding problems
# Ensure UTF-8 encoding
response.encoding = 'utf-8'
# Path issues in saved files
# Use raw strings: r"C:\path\to\file"# Permission issues saving debug files
chmod 755 naviduck.py
# Library dependencies
pip install lxml beautifulsoup4 # For better parsing# 1. Add timeout
"timeout": 30 # Seconds
# 2. Implement caching
cache_results = True
cache_ttl = 3600 # 1 hour
# 3. Use CDN if available
"url": "https://cdn.engine.com/search" # Faster# Limit response size
max_size = 1024 * 1024 # 1MB
if len(response.content) > max_size:
response.content = response.content[:max_size]-
Use browser developer tools
- Open Network tab
- Perform search
- Look for XHR/search requests
-
View page source
- Find search form
- Note
actionURL andinputnames
-
Check API documentation
- Many sites have public APIs
- Look for
/api/searchendpoints
# 1. Mimic browser request
headers = {
'User-Agent': 'Mozilla/5.0...',
'Accept': 'text/html,application/xhtml+xml...',
'Accept-Language': 'en-US,en;q=0.9',
}
# 2. Handle cookies/sessions
session = requests.Session()
session.get('https://site.com') # Get initial cookies
response = session.get(search_url)
# 3. Handle JavaScript-rendered content
# May need Selenium or Playwright# Group engines by type
news_engines = ["bbc", "cnn", "reuters"]
code_engines = ["github", "stackoverflow", "npm"]
# Search all in category (future feature)
search category:news "breaking news"# Auto-select engine based on query
if "how to" in query.lower():
engine = "stackoverflow"
elif "movie" in query.lower():
engine = "imdb"
elif "buy" in query.lower():
engine = "amazon"
else:
engine = default_engine# Remove duplicate results across engines
seen_urls = set()
unique_results = []
for result in all_results:
if result['url'] not in seen_urls:
seen_urls.add(result['url'])
unique_results.append(result)# engines/__init__.py
ENGINE_PACKAGES = {
"developer": ["github", "stackoverflow", "npm", "dockerhub"],
"academic": ["scholar", "arxiv", "ieee", "springer"],
"shopping": ["amazon", "ebay", "etsy", "aliexpress"],
"media": ["youtube", "imdb", "spotify", "goodreads"]
}# Export engine config
python3 -c "
import json
engines = {k:v for k,v in SEARCH_ENGINES.items()
if k not in ['ddg','google','wikipedia','brave']}
print(json.dumps(engines, indent=2))
" > my_engines.json
# Share with others
# They can merge with their SEARCH_ENGINES- Fork the repository
- Add your engines to SEARCH_ENGINES
- Add parsing logic if needed
- Submit pull request
- Help others with their engines
# Add to Icons class
class Icons:
NERD = {
# ... existing icons
"YOUTUBE": "",
"GITHUB": "",
"STACKOVERFLOW": "",
"AMAZON": "",
}
EMOJI = {
# ... existing emoji
"YOUTUBE": "📺",
"GITHUB": "🐙",
"STACKOVERFLOW": "💻",
"AMAZON": "📦",
}
# Then use in engine config
"icon": "YOUTUBE" # Uses custom icon# Some engines need additional libraries
try:
import beautifulsoup4
BEAUTIFULSOUP_AVAILABLE = True
except ImportError:
BEAUTIFULSOUP_AVAILABLE = False
"beautiful_engine": {
"name": "Beautiful Engine",
"requires_library": "beautifulsoup4",
"enabled": BEAUTIFULSOUP_AVAILABLE,
# ...
}# If one engine fails, try similar ones
ENGINE_CATEGORIES = {
"general": ["brave", "google", "ddg"],
"code": ["github", "stackoverflow", "gitlab"],
"video": ["youtube", "vimeo", "dailymotion"]
}
def smart_search(query, category=None):
if category:
engines = ENGINE_CATEGORIES.get(category, ["brave"])
else:
# Auto-detect category from query
engines = detect_engines_from_query(query)
for engine in engines:
try:
return search_with_engine(query, engine)
except SearchFailed:
continue
raise AllEnginesFailed()def validate_engine_config(engine_config):
"""Ensure engine config is safe"""
url = engine_config["url"]
# Prevent SSRF attacks
blocked_hosts = ["localhost", "127.0.0.1", "192.168.", "10."]
for blocked in blocked_hosts:
if blocked in url:
raise ValueError(f"Blocked host in URL: {blocked}")
# Ensure HTTPS for sensitive engines
if engine_config.get("sensitive", False):
if not url.startswith("https://"):
raise ValueError("Sensitive engines must use HTTPS")
return True# Track usage per engine
engine_usage = {}
def check_rate_limit(engine_id):
now = time.time()
if engine_id not in engine_usage:
engine_usage[engine_id] = []
# Remove old requests (last minute)
engine_usage[engine_id] = [
t for t in engine_usage[engine_id]
if now - t < 60
]
# Check if over limit (10 requests/minute)
if len(engine_usage[engine_id]) >= 10:
raise RateLimitExceeded(f"Engine {engine_id} rate limited")
engine_usage[engine_id].append(now)Based on community usage:
- GitHub (85% of developers add this)
- Stack Overflow (78%)
- YouTube (65%)
- Wikipedia (already included)
- Reddit (55%)
- GitLab (45%)
- Docker Hub (40%)
- NPM (38%)
- PyPI (35%)
- Arch Linux AUR (25%)
- Average time to add custom engine: 8 minutes
- Success rate on first try: 72%
- Most common issue: Wrong parameter names (41% of failures)
- Second most common: Site blocks bots (33% of failures)
- GUI engine editor - Visual configuration
- Engine marketplace - Share/download engines
- Auto-discovery - Detect search engines on sites
- Smart parsing - AI-assisted result extraction
- Engine testing - Automated validation
- Update system - Auto-update engine configs
- Engine backup - Cloud sync of engines
- Privacy scoring - Rate engine privacy levels
- Plugin system - Engine plugins
- Visual results - Image/rich previews
- Multi-engine search - Search all engines at once
- Result merging - Combine results from multiple engines
- Location-aware - Regional engine auto-selection
- Time-based - Different engines at different times
- Collaborative filtering - Engines based on what others use
- Learning engine - Adapts to your preferences
- Search Guide - How to use search engines effectively
- Settings & Configuration - Engine management via settings
- Basic Commands - Engine-related commands
- Troubleshooting - Solving engine problems
- Start simple - Copy working examples first
- Test incrementally - Add one engine at a time
- Respect websites - Don't overload with requests
- Share with community - Good engines help everyone
- Keep updated - Websites change, engines need updates
"A well-configured search engine is like a skilled librarian. It knows exactly where to look and how to find what you need."
Q: How many custom engines can I add? A: As many as you want, but performance may degrade with hundreds.
Q: Do custom engines persist after updates? A: If you edit the source file, updates may overwrite. Back up your changes.
Q: Can I add engines that require login? A: Currently limited. Future versions may support authenticated engines.
Q: Are there any restricted engines I can't add? A: Only technical restrictions (Tor requirement, API keys, etc.). No policy restrictions.
Q: Can I remove built-in engines?
A: Yes, set "enabled": false in their configuration.
Q: How do I debug a broken engine? A: Enable debug mode, save raw responses, test parsing patterns separately.
Q: Can engines access local files/network? A: Only through their configured URLs. No local file access.
Q: Are custom engines safe? A: As safe as visiting the website directly. Don't add engines from untrusted sources.
"The web is vast, but with the right search engines, everything is within reach."
# Try adding a custom engine now:
# 1. Pick a site you use often
# 2. Find its search URL pattern
# 3. Add to SEARCH_ENGINES
# 4. Test and refineHappy engine building! 🔧🌐
Last updated: 12/22/2025
Custom Search Engines Guide version: 3.0