92 行
4.4 KiB
Python
92 行
4.4 KiB
Python
import asyncio, json, os, re, math, time
|
||
import cdp
|
||
exec(open('build.py', encoding='utf-8').read().split('def relevant')[0]) # CORE, SPECIAL, norm, cap_tokens ...
|
||
PLAN = json.load(open('cost_plan.json', encoding='utf-8'))
|
||
Qd = {x['sid']: x for x in json.load(open('queries.json', encoding='utf-8'))}
|
||
OUT = 'cost_detail.json'
|
||
out = json.load(open(OUT, encoding='utf-8')) if os.path.exists(OUT) else {}
|
||
|
||
SKUJS = r'''(()=>{const r=(window.__ICE_APP_CONTEXT__||{}).loaderData?.home?.data?.res; if(!r) return null;
|
||
const props=(r.skuBase?.props||[]).map(p=>({name:p.name,values:(p.values||[]).map(v=>v.name)}));
|
||
return JSON.stringify({title:r.item?.title,props})})()'''
|
||
CLICK = r'''((txt)=>{const el=[...document.querySelectorAll("span,div")].filter(e=>e.children.length<=2&&e.innerText&&e.innerText.trim()===txt);if(!el.length)return false;el[el.length-1].click();return true})'''
|
||
|
||
def tokens(p):
|
||
sid, q = p['sid'], Qd[p['sid']]['q']
|
||
if '贴片电阻' in q:
|
||
v = q.split()[1].upper(); m = re.match(r'([\d.]+)([KMR])', v); num, u = m.group(1), m.group(2)
|
||
f = {v, num + u, num + u + 'Ω'}
|
||
if '.' in num: a, b = num.split('.'); f.add(f'{a}{u}{b}')
|
||
return [f], [q.split()[0]]
|
||
if '贴片电容' in q:
|
||
return [cap_tokens(p['desc'])], [re.search(r'(\d+(\.\d)?V)', p['desc']).group(1).upper()]
|
||
c = CORE.get(sid, '')
|
||
return [{c}] if c else [], []
|
||
|
||
def score_sku(name, p):
|
||
must, nice = tokens(p)
|
||
U = name.upper()
|
||
s = 0
|
||
for grp in must:
|
||
if any(re.search(r'(?<![\d.])' + re.escape(t.upper()) + r'(?![\d])', U) or (len(t) >= 5 and norm(t) in norm(name)) for t in grp if t):
|
||
s += 5
|
||
for t in nice:
|
||
if re.search(r'(?<![\d.])' + re.escape(t.upper()), U): s += 2
|
||
return s
|
||
|
||
def parse_price(txt, title):
|
||
i = txt.find('¥', max(0, txt.find(title[:12])) if title else 0)
|
||
if i < 0: i = txt.find('¥')
|
||
if i < 0: return None
|
||
seg = txt[i + 1:i + 60].split('\n'); val = ''
|
||
for s in seg:
|
||
s = s.strip()
|
||
if not s: continue
|
||
if re.fullmatch(r'[\d.]+|\.\d+|-|~', s): val += s
|
||
elif val: break
|
||
else: break
|
||
return val or None
|
||
|
||
async def main():
|
||
ws = await cdp.open_ws()
|
||
for n, p in enumerate(PLAN):
|
||
if p['sid'] in out and out[p['sid']].get('price'): continue
|
||
rec = None
|
||
for c in p['cands'][:2]:
|
||
url = ('https://detail.tmall.com/item.htm?id=' if c['tmall'] else 'https://item.taobao.com/item.htm?id=') + c['id']
|
||
await cdp.send(ws, 'Page.navigate', {'url': url})
|
||
info = None
|
||
for k in range(20):
|
||
await asyncio.sleep(1)
|
||
v = await cdp.ev(ws, SKUJS)
|
||
if v: info = json.loads(v); break
|
||
if not info:
|
||
t = await cdp.ev(ws, 'location.href+" "+document.title')
|
||
print(' no page', c['id'], t, flush=True)
|
||
if t and ('login' in t or '验证' in t or 'punish' in t):
|
||
print('CAPTCHA/LOGIN — waiting', flush=True)
|
||
for w in range(120):
|
||
await asyncio.sleep(5)
|
||
if await cdp.ev(ws, SKUJS): break
|
||
await asyncio.sleep(30); continue
|
||
chosen = []
|
||
for pr in info['props']:
|
||
vals = pr['values']
|
||
if len(vals) <= 1:
|
||
chosen.append(vals[0] if vals else None); continue
|
||
best = max(vals, key=lambda x: (score_sku(x, p), -vals.index(x)))
|
||
chosen.append(best)
|
||
await cdp.ev(ws, '(' + CLICK + ')(' + json.dumps(best) + ')')
|
||
await asyncio.sleep(1.2)
|
||
txt = await cdp.ev(ws, 'document.body.innerText') or ''
|
||
price = parse_price(txt, info['title'] or '')
|
||
ship = re.search(r'快递[::]\s*([^\n]+)', txt)
|
||
rec = dict(url=url, shop=c['shop'], title=info['title'], props=info['props'], chosen=chosen,
|
||
sku_score=[score_sku(x, p) for x in chosen if x], price=price, ship=ship.group(1) if ship else None)
|
||
break
|
||
out[p['sid']] = rec or {'price': None}
|
||
json.dump(out, open(OUT, 'w', encoding='utf-8'), ensure_ascii=False, indent=1)
|
||
print(n, p['mid'], rec and (rec['price'], rec['chosen'], rec['ship']), flush=True)
|
||
await asyncio.sleep(30)
|
||
asyncio.run(main())
|