文件
phonerail/BOM/_src/visit.py
T

92 行
4.4 KiB
Python
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
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())