← back to Japan Enrich
japan: scrape_gg_sg_brands.py — resolve the 242 Singapore distributors (correct selector: psg-single-brands + /product_brand/)
d0a51a931cf8cb15a0824e9f12844464fabe3c2f · 2026-07-06 18:46:01 -0700 · Steve
Files touched
Diff
commit d0a51a931cf8cb15a0824e9f12844464fabe3c2f
Author: Steve <steve@designerwallcoverings.com>
Date: Mon Jul 6 18:46:01 2026 -0700
japan: scrape_gg_sg_brands.py — resolve the 242 Singapore distributors (correct selector: psg-single-brands + /product_brand/)
---
scrape_gg_sg_brands.py | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 57 insertions(+)
diff --git a/scrape_gg_sg_brands.py b/scrape_gg_sg_brands.py
new file mode 100644
index 0000000..ceffa7b
--- /dev/null
+++ b/scrape_gg_sg_brands.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python3
+"""Scrape the distributor brand for the 242 Singapore (goodrichglobal) wallcovering patterns
+that came in blank. Singapore markup differs from Thailand: class="psg-single-brands" +
+/product_brand/<slug>/ (Thailand was ...-main + /brand/). Updates the distributor field on
+those staging rows + the brand map. Parallel headless, $0 local, resumable-ish (idempotent)."""
+import json, os, re, subprocess, html as H
+from concurrent.futures import ThreadPoolExecutor, as_completed
+
+HERE = os.path.dirname(__file__)
+STAGING = os.path.join(HERE, 'staging', 'sangetsu-staging.jsonl')
+BRANDS = os.path.join(HERE, 'staging', 'sangetsu-brands.json')
+CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
+# brand link: /singapore/product_brand/<slug>/ ... >Name</a> (class psg-single-brands)
+LINK = re.compile(r'/product_brand/([a-z0-9-]+)/"[^>]*class="psg-single-brands"[^>]*>([^<]+)<', re.I)
+ALT = re.compile(r'class="psg-single-brands"[^>]*>([^<]+)</a>', re.I)
+
+def brand(url):
+ try:
+ h = subprocess.run([CHROME,"--headless=new","--disable-gpu","--no-sandbox","--virtual-time-budget=12000","--dump-dom",url],
+ capture_output=True, timeout=45, text=True, errors='ignore').stdout
+ m = LINK.search(h or '')
+ if m: return url, H.unescape(m.group(2).strip()), m.group(1)
+ a = ALT.search(h or '')
+ if a: return url, H.unescape(a.group(1).strip()), None
+ return url, None, None
+ except Exception:
+ return url, None, None
+
+def main():
+ rows = [json.loads(l) for l in open(STAGING) if l.strip()]
+ targets = [r['source_url'] for r in rows if r.get('status') == 'gg-sg-new' and not r.get('distributor')]
+ print(f"scraping brand for {len(targets)} Singapore patterns")
+ got = {}
+ with ThreadPoolExecutor(max_workers=6) as ex:
+ done = 0
+ for f in as_completed({ex.submit(brand, u): u for u in targets}):
+ u, dist, slug = f.result()
+ if dist: got[u] = {"distributor": dist, "slug": slug}
+ done += 1
+ if done % 25 == 0: print(f" {done}/{len(targets)} … last={dist}")
+ # rewrite staging distributor for those rows
+ for r in rows:
+ if r['source_url'] in got:
+ r['distributor'] = got[r['source_url']]['distributor']
+ with open(STAGING, 'w') as out:
+ for r in rows: out.write(json.dumps(r, ensure_ascii=False) + '\n')
+ # merge brand map
+ bm = json.load(open(BRANDS)) if os.path.exists(BRANDS) else {}
+ bm.update(got)
+ json.dump(bm, open(BRANDS, 'w'), ensure_ascii=False, indent=0)
+ from collections import Counter
+ c = Counter(v['distributor'] for v in got.values())
+ print(f"\nresolved {len(got)}/{len(targets)} distributors:")
+ for k, n in c.most_common(): print(f" {n:4d} {k}")
+
+if __name__ == '__main__':
+ main()
← d2fb29e japan: nginx-fix-conflict.sh — strip the 19 distributor host
·
back to Japan Enrich
·
japan: get-all-live.sh — push all new patterns (Thailand-unc 52c7000 →