product-recognition-demo/recognization/data/external-eval/kewpie/_fetch.py
kiyo9w 306ea9b3e8 test(external): score 8 independent-source images, 0 false accepts
Eight images for sku_l04, sku_l07 and sku_l09 from six source domains, scored
through the shipped LocalLaneAdapter and DecisionPolicy against
stock-lopia-clean: 4 correct accepts, 4 abstains, no false accepts.

Leak-tested first: the highest template match of any image against
add/image6.jpg is 0.675, so none is a crop of the evaluation photo.

The result that matters is sku_l07. It has no enrolled image, and both its
images accept through the text lane alone - one reads ラッキー フヨネーズ, the
other the rear label's 製造者:丸和油脂株式会社 with 内容量:200g, which
together name the row 丸和油脂 ラッキーマヨネーズ 200g. The text lane's
coverage claim holds on data that was not used to build it.

The four l04 abstentions are correct behaviour: l04 is also unenrolled and its
reads are weak (one reads only コと旨 タ 味, another scores 0.379 against a
0.45 accept line).

Also records the l12 reconciliation, the Kewpie 450 g research, and two
reusable pan clips with 11 rejected candidates for a future Tier-3 replay.
Nothing here is Tier 2 or Tier 3 and every manifest says so.
2026-09-29 03:13:16 +09:00

127 lines
3.9 KiB
Python

"""Fetch helper for the kewpie external-proxy collection.
Kept in the owned directory so every download is reproducible from the
manifest's source_page. Not part of the product code.
"""
import gzip
import json
import re
import ssl
import sys
import urllib.parse
import urllib.request
try:
import certifi
_CTX = ssl.create_default_context(cafile=certifi.where())
except ImportError: # pragma: no cover - certifi ships with this repo's venv
_CTX = ssl.create_default_context()
UA = (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
)
def get(url, referer=None, lang="ja,en-US;q=0.9,en;q=0.8"):
req = urllib.request.Request(url)
req.add_header("User-Agent", UA)
req.add_header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
req.add_header("Accept-Language", lang)
req.add_header("Accept-Encoding", "gzip, deflate")
if referer:
req.add_header("Referer", referer)
with urllib.request.urlopen(req, timeout=40, context=_CTX) as r:
raw = r.read()
if r.headers.get("Content-Encoding") == "gzip":
raw = gzip.decompress(raw)
return raw, r.headers.get("Content-Type", "")
def text(url, **kw):
raw, ctype = get(url, **kw)
for enc in ("utf-8", "shift_jis", "euc-jp"):
try:
return raw.decode(enc)
except UnicodeDecodeError:
continue
return raw.decode("utf-8", "replace")
def download(url, path, referer=None):
raw, ctype = get(url, referer=referer)
with open(path, "wb") as f:
f.write(raw)
return len(raw), ctype
def amazon_search(query, limit=20):
"""Return [(asin, title)] from an amazon.co.jp search."""
h = text("https://www.amazon.co.jp/s?k=" + urllib.parse.quote(query))
out = []
for block in re.split(r'(?=<div[^>]*data-asin="[A-Z0-9]{10}")', h):
m = re.match(r'<div[^>]*data-asin="([A-Z0-9]{10})"', block)
if not m:
continue
asin = m.group(1)
t = re.search(r'<h2[^>]*aria-label="([^"]+)"', block) or re.search(r"<h2[^>]*>(.*?)</h2>", block, re.S)
title = re.sub(r"<[^>]+>", "", t.group(1)).strip() if t else ""
if title and (asin, title) not in out:
out.append((asin, title))
if len(out) >= limit:
break
return out
def _json_after(h, marker, opener="[", closer="]"):
"""Extract the JSON literal that follows `marker` in an Amazon page."""
i = h.find(marker)
if i < 0:
return None
i = h.find(opener, i)
if i < 0:
return None
depth, j = 0, i
while j < len(h):
if h[j] == opener:
depth += 1
elif h[j] == closer:
depth -= 1
if depth == 0:
break
j += 1
try:
return json.loads(h[i : j + 1])
except json.JSONDecodeError:
return None
def amazon_gallery(asin):
"""Return [(hiRes, variant)] for every gallery image on an amazon.co.jp page."""
h = text("https://www.amazon.co.jp/dp/" + asin)
m = re.search(r"'colorImages':\s*\{\s*'initial':\s*A\.\$\.parseJSON\('(\[.*?\])'\)", h, re.S)
if not m:
return []
blob = m.group(1).encode().decode("unicode_escape")
try:
data = json.loads(blob)
except json.JSONDecodeError:
return []
return [(d.get("hiRes"), d.get("variant")) for d in data]
def amazon_images(asin):
"""Return [(hiRes, variant)] for every gallery image on an amazon.co.jp page."""
return amazon_gallery(asin)
if __name__ == "__main__":
cmd = sys.argv[1]
if cmd == "asearch":
for asin, title in amazon_search(sys.argv[2]):
print(asin, "|", title)
elif cmd == "aimg":
for url, variant in amazon_gallery(sys.argv[2]):
print(url, "|", variant)
elif cmd == "get":
print(text(sys.argv[2])[: int(sys.argv[3]) if len(sys.argv) > 3 else 3000])