Screenshot API for E-Commerce: Automating Product Visuals
E-commerce operations generate more repetitive visual work than almost any other domain. Price checks, product page QA, competitor monitoring, catalog screenshots — each of these involves opening a browser, navigating to a URL, and capturing what's there. Screenshot APIs eliminate the human from this loop.
Here's how to build the common e-commerce automation patterns.
Pattern 1: Product Page Screenshots for Catalog QA
Before shipping a catalog update, verify that every product page renders correctly. Not just that it returns 200 — that the images load, the price is visible, and the add-to-cart button is present.
import requests
from pathlib import Path
from urllib.parse import urlparse
SCREENSHOT_API_KEY = "your-api-key"
SCREENSHOT_API_URL = "https://hermesforge.dev/api/screenshot"
def screenshot_product_pages(urls: list[str], output_dir: str = "./qa_screenshots"):
"""Screenshot a list of product pages for visual QA."""
output_path = Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)
results = []
for url in urls:
slug = urlparse(url).path.strip("/").replace("/", "_")
filename = f"{slug}.png"
response = requests.get(
SCREENSHOT_API_URL,
params={
"url": url,
"format": "png",
"width": 1440,
"height": 900,
"wait": "networkidle",
"full_page": "false", # Above-the-fold only for QA
},
headers={"X-API-Key": SCREENSHOT_API_KEY},
timeout=30,
)
if response.status_code == 200:
(output_path / filename).write_bytes(response.content)
results.append({"url": url, "file": filename, "status": "ok"})
else:
results.append({"url": url, "file": None, "status": f"failed: {response.status_code}"})
return results
# Usage: QA a batch of product URLs after a catalog deploy
product_urls = [
"https://your-store.com/products/widget-a",
"https://your-store.com/products/widget-b",
"https://your-store.com/products/widget-c",
]
results = screenshot_product_pages(product_urls)
failures = [r for r in results if r["status"] != "ok"]
if failures:
print(f"QA failures: {len(failures)}")
for f in failures:
print(f" {f['url']}: {f['status']}")
Pattern 2: Price Monitoring with Visual Evidence
Price scrapers often break when sites update their HTML structure. Visual evidence captures the rendered price regardless of DOM changes:
import re
from datetime import datetime
def capture_price_evidence(product_url: str, product_id: str) -> dict:
"""
Capture a screenshot of a product page as price evidence.
Returns metadata dict with file path and timestamp.
"""
timestamp = datetime.utcnow().strftime("%Y%m%d_%H%M%S")
filename = f"price_{product_id}_{timestamp}.png"
output_dir = Path("./price_evidence")
output_dir.mkdir(exist_ok=True)
response = requests.get(
SCREENSHOT_API_URL,
params={
"url": product_url,
"format": "png",
"width": 1280,
"height": 800,
"wait": "networkidle",
"full_page": "false",
},
headers={"X-API-Key": SCREENSHOT_API_KEY},
timeout=30,
)
if response.status_code == 200:
filepath = output_dir / filename
filepath.write_bytes(response.content)
return {
"product_id": product_id,
"url": product_url,
"screenshot": str(filepath),
"captured_at": datetime.utcnow().isoformat(),
}
return {"error": f"Screenshot failed: {response.status_code}"}
The visual evidence serves two purposes: debugging when your parser breaks (you can see what changed), and compliance (some industries require documented evidence of advertised prices at specific timestamps).
Pattern 3: Competitor Monitoring
Track how competitor product pages evolve over time:
import hashlib
import json
from pathlib import Path
COMPETITOR_PRODUCTS = {
"competitor_a_widget": "https://competitor-a.com/product/widget",
"competitor_b_widget": "https://competitor-b.com/products/widget",
}
MONITOR_DIR = Path("./competitor_monitor")
MONITOR_DIR.mkdir(exist_ok=True)
def capture_and_diff(product_key: str, url: str) -> dict:
"""Capture competitor page, detect if it changed since last capture."""
response = requests.get(
SCREENSHOT_API_URL,
params={"url": url, "format": "png", "width": 1280, "height": 800, "wait": "networkidle"},
headers={"X-API-Key": SCREENSHOT_API_KEY},
timeout=30,
)
if response.status_code != 200:
return {"error": f"Failed: {response.status_code}"}
content_hash = hashlib.md5(response.content).hexdigest()
hash_file = MONITOR_DIR / f"{product_key}_hash.txt"
timestamp = datetime.utcnow().isoformat()
changed = False
if hash_file.exists():
previous_hash = hash_file.read_text().strip()
changed = previous_hash != content_hash
if changed or not hash_file.exists():
# Save screenshot when page changes (or first capture)
filename = f"{product_key}_{timestamp.replace(':', '-')}.png"
(MONITOR_DIR / filename).write_bytes(response.content)
hash_file.write_text(content_hash)
return {
"product_key": product_key,
"url": url,
"changed": changed,
"hash": content_hash,
"captured_at": timestamp,
}
def run_competitor_monitoring():
results = []
for key, url in COMPETITOR_PRODUCTS.items():
result = capture_and_diff(key, url)
results.append(result)
if result.get("changed"):
print(f"CHANGED: {key} — {url}")
return results
This captures screenshots only when pages change, minimizing API calls. Run it daily or on a schedule.
Pattern 4: Social Share Preview QA
Before running a product campaign, verify the OG image appears correctly when shared:
def verify_og_preview(product_url: str) -> dict:
"""
Check how a product URL appears when shared on social media.
Uses a social card preview renderer to see exactly what will appear.
"""
# Use a social card preview service URL
preview_service = f"https://www.opengraph.xyz/url/{requests.utils.quote(product_url)}"
response = requests.get(
SCREENSHOT_API_URL,
params={
"url": preview_service,
"format": "png",
"width": 1200,
"height": 900,
"wait": "networkidle",
},
headers={"X-API-Key": SCREENSHOT_API_KEY},
timeout=30,
)
if response.status_code == 200:
filename = f"og_preview_{hashlib.md5(product_url.encode()).hexdigest()[:8]}.png"
Path("./og_qa").mkdir(exist_ok=True)
(Path("./og_qa") / filename).write_bytes(response.content)
return {"status": "ok", "file": filename}
return {"status": "failed"}
Rate Limit Planning for E-Commerce Scale
E-commerce catalogs can be large. A 500-product catalog QA run requires 500 API calls. Structure this around your tier:
| Tier | Daily Limit | Full Catalog QA | Competitor Monitoring (10 products) |
|---|---|---|---|
| Free | 10/day | 50 days | 1 product/day |
| Starter | 200/day | 2.5 days | Daily |
| Pro | 1000/day | Same day | 100 products/day |
| Business | 5000/day | Same day (5x buffer) | 500 products/day |
For catalog QA: only screenshot pages that changed since last deploy. Use content hashes or deployment timestamps to skip unchanged pages.
For competitor monitoring: screenshot on change detection, not on schedule. A competitor's page that hasn't changed doesn't need a new screenshot.
Integrating with Your CI/CD Pipeline
Add visual QA to your deployment pipeline:
#!/usr/bin/env python3
"""
pre-deploy-check.py — run before pushing a catalog update.
Exits non-zero if any product page screenshots fail.
"""
import sys
import requests
from pathlib import Path
STAGING_URLS = [
"https://staging.your-store.com/products/featured-1",
"https://staging.your-store.com/products/featured-2",
]
results = screenshot_product_pages(STAGING_URLS, output_dir="./ci_screenshots")
failures = [r for r in results if r["status"] != "ok"]
if failures:
print(f"Visual QA failed for {len(failures)} pages:")
for f in failures:
print(f" {f['url']}: {f['status']}")
sys.exit(1)
print(f"Visual QA passed: {len(results)} pages OK")
sys.exit(0)
Screenshots are stored in ./ci_screenshots/ — attach them as build artifacts so the QA team can review them even when the test passes.
hermesforge.dev — screenshot API. Free tier: 50 calls/day (free key). Starter: $4/30 days (200/day). Pro: $9 (1000/day). Business: $29 (5000/day).