- 新增 aio-mcp 项目框架 - 新增 .trae/skills/ OCR/SKU 设计工具 - 更新 auto-check 配置和 stress 包 - 删除 fnhelp 文档目录 - 更新 Docker 压力测试脚本
259 lines
8.2 KiB
Python
259 lines
8.2 KiB
Python
import json
|
|
import re
|
|
import os
|
|
|
|
# Load OCR results
|
|
with open(r"D:\workspace\membank\售价\ocr_results.json", "r", encoding="utf-8") as f:
|
|
all_results = json.load(f)
|
|
|
|
def extract_sku_v4(text_lines):
|
|
"""
|
|
Robust extraction:
|
|
Pattern in PDD backend: SKU_name ... [price1] ... [5.00] ... [price2]
|
|
where price2 = price1 - 5
|
|
"""
|
|
results = []
|
|
|
|
# Build full text with confidences
|
|
items = [(t[0].strip(), float(t[1]) if t[1] else 0.0) for t in text_lines]
|
|
|
|
i = 0
|
|
while i < len(items):
|
|
text, conf = items[i]
|
|
|
|
# Check for price patterns - find lines with two prices and 5.00 in between
|
|
# Look ahead for price patterns
|
|
prices_in_window = []
|
|
window_items = []
|
|
|
|
for j in range(i, min(i + 8, len(items))):
|
|
t = items[j][0]
|
|
nums = re.findall(r'(\d{2,4}\.\d{2})', t)
|
|
if nums:
|
|
prices_in_window.extend([float(n) for n in nums])
|
|
window_items.append((j, t, [float(n) for n in nums]))
|
|
|
|
if not prices_in_window:
|
|
i += 1
|
|
continue
|
|
|
|
prices_set = set(round(p, 2) for p in prices_in_window)
|
|
|
|
# Try to find original price and discounted price (diff = 5)
|
|
found_prices = []
|
|
for p in sorted(prices_set):
|
|
if round(p - 5, 2) in prices_set and p > 15:
|
|
found_prices.append((round(p - 5, 2), p)) # (discounted, original)
|
|
elif round(p + 5, 2) in prices_set and p > 15:
|
|
found_prices.append((p, round(p + 5, 2)))
|
|
|
|
if not found_prices:
|
|
i += 1
|
|
continue
|
|
|
|
# Now find the SKU name - look backwards from the price
|
|
# The SKU name is typically 1-3 lines before the price
|
|
sku_name = None
|
|
for j in range(i-1, max(0, i-5), -1):
|
|
t = items[j][0]
|
|
# Must contain CPU or platform identifiers
|
|
if re.search(r'(J1900|j1900|J3160|j3160|i3|I3|i5|I5|1037[Uu]|5005[Uu]|N5105|16G[Bb]|预装系统|双盘位|单盘位|三盘位|四盘位|黑裙|黑群晖|飞牛|蜗牛|Nas|NAS|盘位)', t):
|
|
# Not a pure UI element
|
|
if not re.match(r'^(商品|营销|官方|拼多多|商家|查看|修改|活动|价格|关注|折扣|券后|ID)', t):
|
|
sku_name = t
|
|
break
|
|
|
|
if sku_name:
|
|
for discounted, original in found_prices:
|
|
results.append({
|
|
'sku': sku_name,
|
|
'price': discounted,
|
|
'original_price': original,
|
|
'confidence': conf
|
|
})
|
|
|
|
i += 1
|
|
|
|
return results
|
|
|
|
|
|
def normalize_sku_key(name):
|
|
"""Create a normalized key for deduplication by extracting core identifiers"""
|
|
name = name.strip()
|
|
# Extract CPU type
|
|
cpu = ''
|
|
cpu_match = re.search(r'(J1900|j1900|J3160|j3160|i3|I3|i5|I5|1037[Uu]|5005[Uu]|N5105|N5095|J3355)', name)
|
|
if cpu_match:
|
|
cpu = cpu_match.group(1).upper().replace('J1900','J1900').replace('J3160','J3160')
|
|
|
|
# Extract memory
|
|
mem = ''
|
|
mem_match = re.search(r'(\d+[Gg])[+]?(\d+[Gg])?', name)
|
|
if mem_match:
|
|
mem = mem_match.group(0).upper()
|
|
|
|
# Extract form factor
|
|
form = ''
|
|
if '单盘' in name or '单盤' in name:
|
|
form = '单盘'
|
|
elif '双盘' in name or '雙盤' in name:
|
|
form = '双盘'
|
|
elif '三盘' in name:
|
|
form = '三盘'
|
|
elif '四盘' in name:
|
|
form = '四盘'
|
|
|
|
# System USB
|
|
if '16G' in name.upper() or '16g' in name:
|
|
if 'windows' in name.lower():
|
|
return 'USB:16GB:windows'
|
|
elif '飞牛' in name or '蜗牛' in name:
|
|
return 'USB:16GB:飞牛'
|
|
return f'USB:16GB:{name[:20]}'
|
|
|
|
return f"{cpu}:{mem}:{form}"
|
|
|
|
|
|
def clean_sku_name(name):
|
|
"""Clean up OCR artifacts in SKU name"""
|
|
name = name.strip()
|
|
# Remove known noise
|
|
name = re.sub(r'关注券5元优惠券', '', name)
|
|
name = re.sub(r'店铺关注券5元优惠券', '', name)
|
|
name = re.sub(r'[>\s]+$', '', name)
|
|
name = re.sub(r'^[>\s]+', '', name)
|
|
name = re.sub(r'\s+', ' ', name)
|
|
return name
|
|
|
|
|
|
# Collect all unique SKUs
|
|
all_entries = []
|
|
for img_name, lines in all_results.items():
|
|
entries = extract_sku_v4(lines)
|
|
all_entries.extend(entries)
|
|
|
|
# Deduplicate - keep highest confidence per normalized key
|
|
dedup = {}
|
|
for e in all_entries:
|
|
key = normalize_sku_key(e['sku'])
|
|
if key and key not in dedup:
|
|
dedup[key] = e
|
|
elif key and e['confidence'] > dedup[key]['confidence']:
|
|
dedup[key] = e
|
|
|
|
# Sort by price
|
|
sorted_entries = sorted(dedup.values(), key=lambda x: x['price'])
|
|
|
|
# Print for debugging
|
|
print("=" * 60)
|
|
print("Extracted unique SKUs:")
|
|
for i, e in enumerate(sorted_entries):
|
|
key = normalize_sku_key(e['sku'])
|
|
print(f" [{key}] {clean_sku_name(e['sku'])} -> ${e['price']} (orig: ${e['original_price']})")
|
|
|
|
# Build markdown
|
|
md = []
|
|
md.append("# SKU 售价汇总")
|
|
md.append("")
|
|
md.append("> 从拼多多商家后台「商品价格管理」截图 OCR 识别提取")
|
|
md.append(f"> 提取日期: 2026-06-16")
|
|
md.append(f"> 图片数量: {len(all_results)} 张")
|
|
md.append(f"> 识别 SKU: {len(sorted_entries)} 个(已去重合并)")
|
|
md.append("")
|
|
md.append("---")
|
|
md.append("")
|
|
|
|
# Group by platform
|
|
groups = {
|
|
'系统U盘 (16GB)': [],
|
|
'单盘位 - 1037U': [],
|
|
'双盘位 - 1037U': [],
|
|
'单盘位 - J1900': [],
|
|
'双盘位 - J1900': [],
|
|
'单盘位 - J3160': [],
|
|
'双盘位 - J3160': [],
|
|
'单盘位 - i3': [],
|
|
'双盘位 - i3': [],
|
|
'双盘位 - i5': [],
|
|
'三盘位/四盘位 - i3/i5': [],
|
|
'5005U 系列': [],
|
|
'其他NAS/黑裙晖': [],
|
|
}
|
|
|
|
for e in sorted_entries:
|
|
name = e['sku']
|
|
key = normalize_sku_key(name)
|
|
|
|
if 'USB' in key:
|
|
groups['系统U盘 (16GB)'].append(e)
|
|
elif '1037U' in key:
|
|
if '单盘' in key:
|
|
groups['单盘位 - 1037U'].append(e)
|
|
else:
|
|
groups['双盘位 - 1037U'].append(e)
|
|
elif 'J1900' in key:
|
|
if '单盘' in key:
|
|
groups['单盘位 - J1900'].append(e)
|
|
else:
|
|
groups['双盘位 - J1900'].append(e)
|
|
elif 'J3160' in key:
|
|
if '单盘' in key:
|
|
groups['单盘位 - J3160'].append(e)
|
|
else:
|
|
groups['双盘位 - J3160'].append(e)
|
|
elif 'I3' in key:
|
|
if '三盘' in key or '四盘' in key:
|
|
groups['三盘位/四盘位 - i3/i5'].append(e)
|
|
elif '单盘' in key:
|
|
groups['单盘位 - i3'].append(e)
|
|
else:
|
|
groups['双盘位 - i3'].append(e)
|
|
elif 'I5' in key:
|
|
if '三盘' in key or '四盘' in key:
|
|
groups['三盘位/四盘位 - i3/i5'].append(e)
|
|
else:
|
|
groups['双盘位 - i5'].append(e)
|
|
elif '5005U' in key:
|
|
groups['5005U 系列'].append(e)
|
|
else:
|
|
groups['其他NAS/黑裙晖'].append(e)
|
|
|
|
for group_name in groups:
|
|
entries = groups[group_name]
|
|
if not entries:
|
|
continue
|
|
|
|
md.append(f"## {group_name}")
|
|
md.append("")
|
|
md.append("| SKU 配置 | 券后售价 (¥) | 券前原价 (¥) | 优惠 |")
|
|
md.append("|---------|------------:|------------:|-----|")
|
|
|
|
for e in entries:
|
|
name = clean_sku_name(e['sku'])
|
|
md.append(f"| {name} | {e['price']:.2f} | {e['original_price']:.2f} | -5.00 |")
|
|
|
|
md.append("")
|
|
|
|
md.append("---")
|
|
md.append("")
|
|
md.append("## 说明")
|
|
md.append("")
|
|
md.append("- **优惠**: 统一使用店铺关注券 -5.00 元")
|
|
md.append("- **数据来源**: 拼多多商家后台「商品价格管理」页面截图")
|
|
md.append("- **识别工具**: RapidOCR (rapidocr-onnxruntime)")
|
|
md.append("- **注意事项**: OCR 识别的 SKU 名称可能有碎片化/错字,建议对比后台实际数据确认")
|
|
md.append("- **价格日期**: 2026-06-05 (截图时间)")
|
|
md.append("")
|
|
|
|
# Fix the price summary line
|
|
overall_min = min(e['price'] for e in sorted_entries) if sorted_entries else 0
|
|
overall_max = max(e['price'] for e in sorted_entries) if sorted_entries else 0
|
|
md.append(f"> 价格范围: ¥{overall_min:.2f} ~ ¥{overall_max:.2f}")
|
|
|
|
output_path = r"D:\workspace\membank\售价\SKU售价汇总.md"
|
|
with open(output_path, "w", encoding="utf-8") as f:
|
|
f.write("\n".join(md))
|
|
|
|
print(f"\n\nSaved to: {output_path}")
|
|
print(f"Total unique SKUs: {len(sorted_entries)}")
|