If you are looking for these devices for research purposes, the standard Shodan search queries usually involve looking for the HTTP title or the server name:

If you want a custom update script, use the shodan command-line interface.

Installation:

pip install shodan
shodan init YOUR_API_KEY

Automated Update Script (webcamxp_updater.sh):

#!/bin/bash
# Define the search query
QUERY='title:"WebcamXP 5"'
# Run the search and save to a timestamped file
shodan search --limit 100 --fields ip_str,port,http.title "$QUERY" > webcamxp_results_$(date +%Y%m%d_%H%M%S).txt
# Optional: diff with previous file to see changes
if [ -f webcamxp_latest.txt ]; then
    echo "Changes since last update:"
    diff webcamxp_latest.txt webcamxp_results_$(date +%Y%m%d_%H%M%S).txt
fi
# Symlink to "latest"
ln -sf webcamxp_results_$(date +%Y%m%d_%H%M%S).txt webcamxp_latest.txt

Set a Cron Job (update every 4 hours):

crontab -e
# Add line: 
0 */4 * * * /home/user/webcamxp_updater.sh

To narrow down results to live, accessible feeds, combine filters:

title:"WebcamXP 5" http.status:200 -"404" country:US

CONFIG_FILE = "webcamxp_config.json" REPORT_FILE = "webcamxp_instances.csv" LOG_FILE = "webcamxp_updates.log"

class WebcamXPShodanSearcher: def init(self, api_key): self.api = shodan.Shodan(api_key) self.instances = []

def search_webcamxp(self, max_pages=3):
    """Search Shodan for WebcamXP 5 instances"""
    queries = [
        '"WebcamXP 5" "Server:"',
        'title:"WebcamXP 5"',
        '"WebcamXP 5" port:8080,8081',
    ]
all_results = []
for query in queries:
        print(f"[*] Searching with query: query")
        try:
            # Paginate through results
            for page in range(1, max_pages + 1):
                results = self.api.search(query, page=page)
                print(f"    Found len(results['matches']) results on page page")
for match in results['matches']:
                    instance = self.parse_instance(match)
                    if instance and instance not in all_results:
                        all_results.append(instance)
if page >= results['total'] // 100:
                    break
except shodan.APIError as e:
            print(f"    Error: e")
            continue
self.instances = all_results
    return self.instances
def parse_instance(self, match):
    """Parse Shodan match into structured instance data"""
    try:
        # Extract webcam feed URL
        ip = match.get('ip_str', '')
        port = match.get('port', 80)
        protocol = 'https' if match.get('ssl') else 'http'
        base_url = f"protocol://ip:port"
# Common WebcamXP paths
        possible_paths = ['/', '/view/viewer_index.shtml', '/cgi-bin/viewer/video.jpg']
feed_url = None
        for path in possible_paths:
            test_url = base_url + path
            if self.check_url_accessible(test_url):
                feed_url = test_url
                break
instance = {
            'ip': ip,
            'port': port,
            'url': base_url,
            'feed_url': feed_url,
            'timestamp': datetime.now().isoformat(),
            'organization': match.get('org', 'Unknown'),
            'location': f"match.get('city', 'Unknown'), match.get('country_name', 'Unknown')",
            'server_header': match.get('http', {}).get('server', ''),
            'title': match.get('http', {}).get('title', ''),
        }
        return instance
    except Exception as e:
        print(f"    Parse error: e")
        return None
def check_url_accessible(self, url, timeout=5):
    """Check if webcam feed URL is accessible"""
    try:
        response = requests.get(url, timeout=timeout, verify=False)
        return response.status_code == 200
    except:
        return False
def generate_report(self):
    """Generate CSV report of found instances"""
    if not self.instances:
        print("[!] No instances found to report")
        return
with open(REPORT_FILE, 'w', newline='', encoding='utf-8') as csvfile:
        fieldnames = ['ip', 'port', 'url', 'feed_url', 'timestamp', 
                     'organization', 'location', 'server_header', 'title']
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(self.instances)
print(f"[+] Report saved to REPORT_FILE (len(self.instances) instances)")
def update_report_with_current_status(self):
    """Update existing report with current online status"""
    if not os.path.exists(REPORT_FILE):
        print("[!] No existing report to update")
        return
# Read existing report
    with open(REPORT_FILE, 'r', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        instances = list(reader)
# Check each instance
    for instance in instances:
        status = self.check_url_accessible(instance['url'])
        instance['status_checked'] = datetime.now().isoformat()
        instance['online'] = 'Yes' if status else 'No'
# Write updated report
    with open(REPORT_FILE, 'w', newline='', encoding='utf-8') as f:
        fieldnames = list(instances[0].keys())
        writer = csv.DictWriter(f, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(instances)
print(f"[+] Report updated at datetime.now().isoformat()")

def main(): parser = argparse.ArgumentParser(description='WebcamXP 5 Shodan Search & Update Tool') parser.add_argument('--api-key', help='Shodan API key (or set SHODAN_API_KEY env var)') parser.add_argument('--search', action='store_true', help='Perform new search') parser.add_argument('--update', action='store_true', help='Update existing report') parser.add_argument('--max-pages', type=int, default=3, help='Max search pages')

args = parser.parse_args()
# Get API key
api_key = args.api_key or os.environ.get('SHODAN_API_KEY')
if not api_key:
    print("[!] Please provide Shodan API key via --api-key or SHODAN_API_KEY env var")
    return
searcher = WebcamXPShodanSearcher(api_key)
if args.search:
    print("[*] Searching for WebcamXP 5 instances...")
    instances = searcher.search_webcamxp(max_pages=args.max_pages)
    print(f"[+] Found len(instances) unique instances")
    searcher.generate_report()
if args.update:
    print("[*] Updating existing report...")
    searcher.update_report_with_current_status()
if not args.search and not args.update:
    print("[!] Please specify --search or --update")

if name == "main": main()

# Initial search
python webcamxp_search.py --search --max-pages 5