- W26: NestJS API + Next.js Web + Prisma + SQLite scaffold - W26: Lead 模型 + Crawler + 询价发布助手 (线索池 + 一键发布) - W29.x: MCP 集成 (Integration 模型 + connectMcp + publish_rfq 5 工具端到端跑通) - W30: 3 字段补充 (notes / quotationType / quotationDemand) - W31 P1: 自动填 enterpriseId / userId (Prisma companyUserId 字段 + get_app_identity tool + lead.service.ts publishArgs) - W31: detail 详情页 + admin UI 集成管理 (OpenAppListView + AuditLogListView) 关联: ~/Documents/02-平台开发/产品开发/F002-W31-交接文档.md GIT-AUDIT-REPORT-2026-06-24.md
149 lines
5.1 KiB
Python
149 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
商品列表解析脚本
|
||
用法: python3 parse_products.py <文件路径>
|
||
输出: JSON [{ "name": "商品名", "spec": "规格", "category": "分类" }, ...]
|
||
"""
|
||
|
||
import sys
|
||
import json
|
||
import os
|
||
|
||
def _read_excel(filepath: str):
|
||
import pandas as pd
|
||
is_unnamed_cols = False
|
||
|
||
# Attempt 1: default header=0
|
||
df = pd.read_excel(filepath, dtype=str)
|
||
raw_cols = list(df.columns)
|
||
unnamed_count = sum(1 for c in raw_cols if c.startswith('Unnamed'))
|
||
total_cols = len(raw_cols)
|
||
|
||
# If most columns are "Unnamed" or header looks like a title, retry with header=1
|
||
if unnamed_count > total_cols / 2 or total_cols == 1:
|
||
is_unnamed_cols = True
|
||
df = pd.read_excel(filepath, dtype=str, header=1)
|
||
|
||
# If still mostly unnamed, try without header and use first non-empty row as header
|
||
raw_cols = list(df.columns)
|
||
unnamed_count = sum(1 for c in raw_cols if c.startswith('Unnamed'))
|
||
if unnamed_count > len(raw_cols) / 2:
|
||
is_unnamed_cols = True
|
||
raw = pd.read_excel(filepath, dtype=str, header=None)
|
||
for i in range(min(5, len(raw))):
|
||
row = raw.iloc[i]
|
||
vals = [str(v).strip() for v in row if str(v).strip().lower() != 'nan']
|
||
if len(vals) > 2:
|
||
df = pd.read_excel(filepath, dtype=str, header=i)
|
||
break
|
||
|
||
return df
|
||
|
||
def _read_csv(filepath: str):
|
||
import pandas as pd
|
||
df = pd.read_csv(filepath, dtype=str, encoding='utf-8-sig')
|
||
return df
|
||
|
||
def _match_col(norm_map: dict, keywords: list[str]) -> str:
|
||
for kw in keywords:
|
||
nk = kw.lower().replace(' ', '').replace(' ', '')
|
||
if nk in norm_map:
|
||
return norm_map[nk]
|
||
# 模糊匹配:关键词是列名的子串
|
||
for kw in keywords:
|
||
nk = kw.lower().replace(' ', '').replace(' ', '')
|
||
for norm, orig in norm_map.items():
|
||
if nk in norm:
|
||
return orig
|
||
return ''
|
||
|
||
def parse_file(filepath: str) -> list[dict]:
|
||
ext = os.path.splitext(filepath)[1].lower()
|
||
|
||
if ext in ('.xlsx', '.xls'):
|
||
df = _read_excel(filepath)
|
||
elif ext == '.csv':
|
||
df = _read_csv(filepath)
|
||
else:
|
||
raise ValueError(f'不支持的文件格式: {ext}')
|
||
|
||
if df.empty:
|
||
return []
|
||
|
||
# 标准化列名
|
||
raw_cols = list(df.columns)
|
||
norm_map = {}
|
||
for c in raw_cols:
|
||
norm = c.strip().lower().replace(' ', '').replace(' ', '')
|
||
norm_map[norm] = c
|
||
|
||
name_col = _match_col(norm_map, ['商品名称', '商品名', '名称', '产品名称', '品名', 'name', 'productname', '产品名'])
|
||
spec_col = _match_col(norm_map, ['规格', '规格型号', '型号', 'spec', '规格描述', '规格参数'])
|
||
cat_col = _match_col(norm_map, ['分类', '商品分类', '品类', 'category', '类别', '类目'])
|
||
price_col = _match_col(norm_map, ['价格', '单价', '报价', 'price', '售价', '销售价', '报价(元)', '报价(元)', '含税价'])
|
||
unit_col = _match_col(norm_map, ['单位', '计量单位', 'unit', '数量单位'])
|
||
brand_col = _match_col(norm_map, ['品牌', 'brand', '厂家', '制造商', '生产厂家'])
|
||
qty_col = _match_col(norm_map, ['数量', 'quantity', '库存', '库存数量', 'qty'])
|
||
|
||
if not name_col and raw_cols:
|
||
name_col = raw_cols[0]
|
||
|
||
results = []
|
||
for _, row in df.iterrows():
|
||
name_raw = row.get(name_col)
|
||
if name_raw is None:
|
||
continue
|
||
name = str(name_raw).strip()
|
||
if not name or name.lower() == 'nan':
|
||
continue
|
||
spec = str(row.get(spec_col, '') or '').strip()
|
||
if spec.lower() == 'nan':
|
||
spec = ''
|
||
category = str(row.get(cat_col, '') or '').strip()
|
||
if category.lower() == 'nan':
|
||
category = ''
|
||
price = row.get(price_col)
|
||
if price is not None:
|
||
try:
|
||
price = float(str(price).replace(',', '').replace('¥', '').replace('¥', '').strip())
|
||
except (ValueError, TypeError):
|
||
price = 0
|
||
else:
|
||
price = 0
|
||
unit = str(row.get(unit_col, '') or '').strip()
|
||
if unit.lower() == 'nan':
|
||
unit = ''
|
||
brand = str(row.get(brand_col, '') or '').strip()
|
||
if brand.lower() == 'nan':
|
||
brand = ''
|
||
qty_raw = row.get(qty_col)
|
||
quantity = 0
|
||
if qty_raw is not None:
|
||
try:
|
||
quantity = int(float(str(qty_raw).replace(',', '').strip()))
|
||
except (ValueError, TypeError):
|
||
quantity = 0
|
||
results.append({
|
||
'name': name,
|
||
'spec': spec,
|
||
'category': category,
|
||
'price': price,
|
||
'unit': unit,
|
||
'brand': brand,
|
||
'quantity': quantity,
|
||
})
|
||
|
||
return results
|
||
|
||
|
||
if __name__ == '__main__':
|
||
if len(sys.argv) < 2:
|
||
print(json.dumps({'error': '请提供文件路径'}))
|
||
sys.exit(1)
|
||
try:
|
||
data = parse_file(sys.argv[1])
|
||
print(json.dumps(data, ensure_ascii=False))
|
||
except Exception as e:
|
||
print(json.dumps({'error': str(e)}, ensure_ascii=False))
|
||
sys.exit(1)
|