← back to Kravet Sheet Sync 2026 04 20
bulk_image_check.py
138 lines
#!/usr/bin/env python3
"""
Bulk-validate Kravet image URLs for every wallcovering row (~10,184).
URL pattern: https://www.kravet.com/media/catalog/product{image_file_hires}
Runs HEAD requests, persists result to kravet_sheet_fields:
- image_url_hires (computed)
- image_url_lores (computed)
- image_hires_status (http code)
- image_checked_at
Concurrency: 20 threads, ~20 rps → ~10min total.
"""
import json, subprocess, urllib.request, urllib.error, sys, time
from concurrent.futures import ThreadPoolExecutor, as_completed
BASE = "https://www.kravet.com/media/catalog/product"
SSH = ["ssh", "root@45.61.58.125"]
UA = {"User-Agent": "Mozilla/5.0 (dw-sync bot)"}
def pg_cmd(sql: str) -> str:
r = subprocess.run(
SSH + [f'PGPASSWORD=DW2024! psql -h 127.0.0.1 -U dw_admin -d dw_unified -At -F "|" -c "{sql}"'],
capture_output=True, text=True, check=True)
return r.stdout
def pg_exec(sql: str) -> None:
subprocess.run(
SSH + [f'PGPASSWORD=DW2024! psql -h 127.0.0.1 -U dw_admin -d dw_unified -c "{sql}"'],
capture_output=True, text=True, check=True)
# 1) Add columns
print("[1/4] adding columns to kravet_sheet_fields ...")
pg_exec("""
ALTER TABLE kravet_sheet_fields
ADD COLUMN IF NOT EXISTS image_url_hires text,
ADD COLUMN IF NOT EXISTS image_url_lores text,
ADD COLUMN IF NOT EXISTS image_hires_status int,
ADD COLUMN IF NOT EXISTS image_checked_at timestamptz
""")
# 2) Fetch ALL rows with any image filename (hires OR lores)
print("[2/4] pulling all rows with any image filename ...")
rows_out = pg_cmd("""
SELECT mfr_sku_norm, COALESCE(image_file_hires,''), COALESCE(image_file_lores,'')
FROM kravet_sheet_fields
WHERE COALESCE(image_file_hires,'') <> '' OR COALESCE(image_file_lores,'') <> ''
""")
rows = [line.split("|") for line in rows_out.strip().split("\n") if line]
print(f" {len(rows)} rows to check")
def head(url: str) -> int:
try:
req = urllib.request.Request(url, headers=UA, method="HEAD")
with urllib.request.urlopen(req, timeout=10) as r:
return r.status
except urllib.error.HTTPError as e:
return e.code
except Exception:
return 0
# 3) HEAD the best URL: prefer hires, fall back to lores
print("[3/4] validating URLs (40 concurrent, hires→lores fallback) ...")
results = [] # (norm, hires_url, lores_url, status_of_best, best_kind)
done = 0
t0 = time.time()
def check_one(norm, hires, lores):
hu = BASE + hires if hires else ""
lu = BASE + lores if lores else ""
status = 0; kind = ""
if hu:
status = head(hu); kind = "hires"
if status != 200 and lu:
ls = head(lu)
if ls == 200:
status = ls; kind = "lores"
elif status == 0:
status = ls; kind = "lores"
return (norm, hu, lu, status, kind)
with ThreadPoolExecutor(max_workers=40) as ex:
futs = [ex.submit(check_one, n, h, l) for n, h, l in rows]
for f in as_completed(futs):
results.append(f.result())
done += 1
if done % 1000 == 0:
rps = done / max(1, time.time() - t0)
print(f" {done}/{len(rows)} {rps:.1f}/s")
ok = sum(1 for r in results if r[3] == 200)
err = len(results) - ok
hires_count = sum(1 for r in results if r[4] == "hires")
lores_count = sum(1 for r in results if r[4] == "lores")
print(f" DONE: {ok} OK ({hires_count} hires / {lores_count} lores), {err} failed, {time.time()-t0:.0f}s")
# 4) Upsert back to PG
print("[4/4] writing back to PG ...")
import tempfile, csv
with tempfile.NamedTemporaryFile("w", suffix=".csv", delete=False, newline="") as tf:
w = csv.writer(tf)
for norm, hu, lu, status, kind in results:
w.writerow([norm, hu, lu, status, kind])
tmp = tf.name
subprocess.run(["scp", tmp, "root@45.61.58.125:/tmp/kravet_image_check.csv"], check=True)
pg_exec("""
DROP TABLE IF EXISTS _img_tmp;
CREATE TEMP TABLE _img_tmp (mfr_sku_norm text, hu text, lu text, status int) ON COMMIT DROP;
""")
# Use a single session (temp table needs same session)
pg_exec("ALTER TABLE kravet_sheet_fields ADD COLUMN IF NOT EXISTS image_best_kind text")
r = subprocess.run(SSH + ["""PGPASSWORD=DW2024! psql -h 127.0.0.1 -U dw_admin -d dw_unified <<'SQL'
CREATE TEMP TABLE _img_tmp (mfr_sku_norm text, hu text, lu text, status int, kind text);
\\copy _img_tmp FROM '/tmp/kravet_image_check.csv' WITH (FORMAT csv);
UPDATE kravet_sheet_fields s
SET image_url_hires = NULLIF(t.hu,''),
image_url_lores = NULLIF(t.lu,''),
image_hires_status = t.status,
image_best_kind = NULLIF(t.kind,''),
image_checked_at = now()
FROM _img_tmp t
WHERE s.mfr_sku_norm = t.mfr_sku_norm;
SELECT COUNT(*) FILTER (WHERE image_hires_status=200) AS ok,
COUNT(*) FILTER (WHERE image_best_kind='hires') AS hires,
COUNT(*) FILTER (WHERE image_best_kind='lores') AS lores,
COUNT(*) FILTER (WHERE image_hires_status<>200 AND image_hires_status IS NOT NULL) AS bad
FROM kravet_sheet_fields;
SQL
"""], capture_output=True, text=True)
print(r.stdout)
print("done.")