← back to Koroseal Goodrich Audit

koroseal_spec_crawl.py

40 lines

#!/usr/bin/env python3
# Crawl koroseal.com wallcovering product pages -> per-SKU specs + FB21-style code, JSONL.
import re,html,json,sys,urllib.request
from concurrent.futures import ThreadPoolExecutor
UA="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15"
OUT=open("/Users/macstudio3/Projects/koroseal-goodrich-audit/koroseal_specs.jsonl","w")
SM="/tmp/kor_sm.xml"

urls=sorted(set(re.findall(r'https://koroseal\.com/products/wallcoverings/[^\s<]+', open(SM,encoding="utf-8",errors="ignore").read())))
urls=[u for u in urls if not u.rstrip('/').endswith(('/in-stock','/archived-products','/brand','/wallcoverings'))]
print(f"product urls: {len(urls)}", file=sys.stderr)

SPEC_P=re.compile(r'<p>\s*([A-Za-z][A-Za-z /]+?)(?:<sup>\d+</sup>)?\s*:\s*<span>\s*([^<]+?)\s*</span>', re.S)
CODE=re.compile(r'/([A-Za-z0-9]{2,7})_(\d{2,3})_[A-Za-z0-9]+(?:_[A-Za-z0-9]+)?\.(?:jpg|png)', re.I)
H1=re.compile(r'<h1[^>]*>(.*?)</h1>', re.S|re.I)
def clean(s): return html.unescape(re.sub(r'\s+',' ',s or '')).strip()

def fetch(u):
    try:
        req=urllib.request.Request(u,headers={"User-Agent":UA})
        h=urllib.request.urlopen(req,timeout=15).read().decode("utf-8","ignore")
    except Exception as e:
        return {"url":u,"err":str(e)[:80]}
    specs={clean(k).title():clean(v) for k,v in SPEC_P.findall(h)}
    codes=sorted({f"{a.upper()}-{int(b):02d}" for a,b in CODE.findall(h)})
    parts=u.rstrip('/').split('/')
    series=parts[-2] if len(parts)>=2 and parts[-1] not in ('wallcoverings',) else parts[-1]
    color=parts[-1]
    title=clean(re.sub(r'<[^>]+>','',(H1.search(h).group(1) if H1.search(h) else '')))
    return {"url":u,"series":series,"color":color,"title":title,"codes":codes,"specs":specs}

n=0; hit=0
with ThreadPoolExecutor(max_workers=10) as ex:
    for r in ex.map(fetch, urls):
        OUT.write(json.dumps(r)+"\n"); n+=1
        if r.get("specs"): hit+=1
        if n%300==0: print(f"{n}/{len(urls)} ({hit} w/specs)", file=sys.stderr)
OUT.close()
print(f"DONE {n} pages, {hit} with specs", file=sys.stderr)