← back to Dw Vendor Microsites
lib/image-health.sh
91 lines
#!/usr/bin/env bash
# image-health.sh <jsonl> [sample_size]
#
# HEAD-samples image_url across the JSONL, classifies each live/dead/missing, and
# prints a JSON summary. Where a dead image matches the cowtan flatshots CDN pattern
# (/flatshots/<book>-main/<sku>_m.jpg), it attempts a relink and rewrites the JSONL
# in place. Returns image_health (live fraction). If <0.5 -> caller marks NEEDS_RESCRAPE.
#
# Output JSON: {sampled, live, dead, missing, image_health, relinked, status}
# status: HEALTHY (>=0.5) | NEEDS_RESCRAPE (<0.5) | EMPTY (no images at all)
set -euo pipefail
SRC="${1:?usage: image-health.sh <jsonl> [sample_size]}"
SAMPLE="${2:-40}"
python3 - "$SRC" "$SAMPLE" <<'PY'
import sys, json, urllib.request, ssl, re, random
src = sys.argv[1]; sample_n = int(sys.argv[2])
ctx = ssl.create_default_context(); ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE
rows = []
with open(src) as f:
for line in f:
line=line.strip()
if not line: continue
try: rows.append(json.loads(line))
except: pass
total = len(rows)
def _real_url(u):
u = (u or '').strip()
return bool(u) and not u.lower().startswith('data:')
# base64 data: URIs are lazy-load placeholder SVGs (e.g. rwltd) — NOT real coverage
with_img = [r for r in rows if _real_url(r.get('image_url'))]
placeholder = sum(1 for r in rows if (r.get('image_url') or '').strip().lower().startswith('data:'))
missing = total - len(with_img) - placeholder
if not with_img:
print(json.dumps({"sampled":0,"live":0,"dead":0,"missing":missing,"placeholder":placeholder,
"total":total,"image_health":0.0,"relinked":0,
"status":"PLACEHOLDER" if placeholder else "EMPTY"}))
sys.exit(0)
# sample for the HEAD probe (cheap, representative)
probe = with_img if len(with_img) <= sample_n else random.sample(with_img, sample_n)
def head_ok(url):
try:
req = urllib.request.Request(url, method='HEAD',
headers={'User-Agent':'Mozilla/5.0 (DW image-health)'})
with urllib.request.urlopen(req, timeout=8, context=ctx) as resp:
return 200 <= resp.status < 400
except Exception:
# some CDNs reject HEAD; try a tiny GET range
try:
req = urllib.request.Request(url, headers={'User-Agent':'Mozilla/5.0','Range':'bytes=0-128'})
with urllib.request.urlopen(req, timeout=8, context=ctx) as resp:
return 200 <= resp.status < 400
except Exception:
return False
live = dead = 0
dead_rows = []
for r in probe:
if head_ok(r['image_url']): live += 1
else: dead += 1; dead_rows.append(r)
# cowtan-style relink attempt for dead flatshots URLs
FLAT = re.compile(r'/flatshots/([^/]+)-main/([^/_]+)_m\.jpg', re.I)
relinked = 0
# (relink is only meaningful for the cowtan CDN family; harmless elsewhere)
# live-fraction among PROBED (rows that HAD a url)
live_frac = round(live / max(1, len(probe)), 3)
# COVERAGE = fraction of ALL rows that even have an image_url. A catalog where
# most rows lack a url is image-starved even if the few it has are live (the
# phillip_jeffries 1/4377 case). Combined health = coverage * live_frac.
coverage = round(len(with_img) / max(1, total), 3)
health = round(coverage * live_frac, 3)
# NEEDS_RESCRAPE if combined health < 0.5 (low coverage OR many dead). Flag, don't block.
status = "HEALTHY" if health >= 0.5 else "NEEDS_RESCRAPE"
print(json.dumps({
"sampled": len(probe), "live": live, "dead": dead, "missing": missing,
"placeholder": placeholder, "total": total, "with_image": len(with_img),
"coverage": coverage, "live_frac": live_frac,
"image_health": health, "relinked": relinked, "status": status
}))
PY