URL Visual Monitor
URL Visual Monitor is a lightweight Python tool that periodically captures screenshots of a specified web page and detects visual differences between consecutive snapshots. If a change is detected, it triggers an alert by sending a POST request to a configured webhook URL (e.g., Slack, Zapier, or custom endpoint).
This tool is ideal for tracking visual changes on websites where content updates aren't exposed via APIs — such as marketing pages, competitor sites, or public dashboards. It helps you stay proactive by notifying you the moment a visual shift occurs.
Features
- Automated full-page screenshot capture using headless Chrome
- Visual diff detection via structural similarity (SSIM) from OpenCV
- Configurable polling interval and image similarity threshold
- Webhook alerting with timestamp and image attachment (via file server or cloud upload placeholder)
- Lightweight and easy to deploy
Setup
- Install dependencies:
pip install selenium opencv-python numpy pillow requests
Ensure ChromeDriver is installed and in PATH, or specify its location.
Run the script:
python main.py --url "https://example.com" --webhook "https://your-webhook.com" --interval 300
Configuration
-
--url: Target website to monitor -
--webhook: Endpoint to notify on change -
--interval: Time (seconds) between checks (default: 300) -
--threshold: Image similarity threshold (0.95 = 95% similar; default: 0.98)
Notes
Screenshots are stored temporarily in the screenshots/ directory. The tool compares the latest screenshot with the previous one using SSIM. A drop below the threshold triggers the webhook.
For production use, consider adding cloud storage (e.g., S3) for images and enhancing error handling.
Dependencies
- selenium: For browser automation
- opencv-python: For image comparison
- numpy, pillow: Image processing
- requests: For sending webhook alerts
Maintained for simplicity and reliability with minimal external services.
import argparse
import time
import requests
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from PIL import Image
import cv2
import numpy as np
import os
# Direct runnable script
SCREENSHOT_DIR = 'screenshots'
OS.makedirs(SCREENSHOT_DIR, exist_ok=True)
def take_screenshot(url, path):
options = Options()
options.add_argument('--headless')
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
driver = webdriver.Chrome(options=options)
try:
driver.get(url)
time.sleep(3)
driver.save_screenshot(path)
return True
except Exception as e:
print(f'Error taking screenshot: {e}')
return False
finally:
driver.quit()
def images_different(prev_img_path, new_img_path, thresh=0.98):
img1 = cv2.imread(prev_img_path, 0)
img2 = cv2.imread(new_img_path, 0)
img2 = cv2.resize(img2, img1.shape[::-1])
score, _ = cv2.compareSSIM(img1, img2, full=True)
return score < thresh
def send_webhook(webhook_url, message, image_path=None):
files = {'file': open(image_path, 'rb')} if image_path else None
try:
requests.post(webhook_url, data={'text': message}, files=files)
except Exception as e:
print(f'Webhook error: {e}')
finally:
if files:
files['file'].close()
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--url', required=True, help='URL to monitor')
parser.add_argument('--webhook', required=True, help='Webhook URL to ping')
parser.add_argument('--interval', type=int, default=300, help='Check interval in seconds')
parser.add_argument('--threshold', type=float, default=0.98, help='SSIM threshold for change detection')
args = parser.parse_args()
prev_screenshot = os.path.join(SCREENSHOT_DIR, 'prev.png')
curr_screenshot = os.path.join(SCREENSHOT_DIR, 'curr.png')
# Initial screenshot
if not take_screenshot(args.url, prev_screenshot):
print('Failed to take initial screenshot.')
return
print('Monitoring started...')
while True:
time.sleep(args.interval)
if not take_screenshot(args.url, curr_screenshot):
continue
if os.path.exists(prev_screenshot) and images_different(prev_screenshot, curr_screenshot, args.threshold):
msg = f'Visual change detected on {args.url} at {time.strftime("%Y-%m-%d %H:%M:%S")}'
send_webhook(args.webhook, msg, curr_screenshot)
os.replace(curr_screenshot, prev_screenshot)
print('Change detected and reported.')
else:
os.replace(curr_screenshot, prev_screenshot)
if __name__ == '__main__':
main()
Top comments (0)