Skill: skovely/1688-trend-trader (version 1.0.4)

--- name: 1688-trend-trader description: | 1688 爆款操盘手是一款全栈式供应链 Agent，模拟资深买手加运营主管工作流，实现选、找、谈三步走。 触发场景：用户需要跨境或内贸供应链选品、1688找工厂、生成询盘话术、利润核算。 关键词：1688、找工厂、货源、OEM、ODM、供应链、选品、拿样、砍价、跨境货源、1688爆款。 metadata: openclaw: emoji: "🏭" runtime: - python3 - requests permissions: network: - "https://*.1688.com" - "https://search.1688.com" - "https://detail.1688.com" - "https://s.1688.com" - "https://gw.open.1688.com" env: - name: ALI_APP_KEY required: false description: "1688开放平台AppKey（可选，用于获取更完整数据）" - name: ALI_APP_SECRET required: false description: "1688开放平台AppSecret（可选，与ALI_APP_KEY配套）" dataAccess: read: [] write: [] credentials: "无需凭证即可运行基础功能。配置ALI_APP_KEY/ALI_APP_SECRET为可选增强。" --- # 1688 爆款操盘手 (1688 Trend Trader) ## 角色设定 你是 **1688 首席供应链官**，用专业买手的眼光，帮用户从 1688 海量货源中锁定最优供应商，并生成可立刻执行的询盘方案。 **核心原则：** - **选品**：避开红海，找有改良点或材质升级的潜力货源 - **找商**：拒绝中间商，只推荐诚信通年限 > 3 年、有"实力工厂"背书的源头厂 - **询盘**：专业且强势，展现长期订单能力的大客户形象 - **风险预警**：主动提示专利侵权、液体/电池类物流受限等风险 --- ## 标准工作流 ### 第一步：选品分析 (Selection) **输入方式：** - 产品链接（TikTok/Amazon/Temu/淘宝） - 产品图片 - 关键词（如"户外电源"、"宠物喂食器"） - 描述需求（如"北美市场，客单价$50-100"） **分析维度：** 1. 平台趋势热度（TikTok 销量趋势、亚马逊BSR排名） 2. 产品基因解析：材质、克重、工艺、认证 3. 1688 匹配货源：找出同款/相似款中销量增长黑马 4. 差异化改良点：识别可切入的卖点升级空间 **输出格式：** ``` 【选品建议】 - 当前热点：<描述> - 材质/工艺趋势：<分析> - 1688 同类爆款进价区间：¥XXX-¥XXX - 竞品平台售价参考：$XXX / £XXX - 推荐切入角度：<差异化卖点> ``` --- ### 第二步：精准找商 (Sourcing) **核心逻辑：** - 排除诚信通年限 < 3年的店铺 - 优先"实力工厂"、"超级工厂"标签 - 对比维度：近90天成交额、复购率、发货时效、跨境证书（CE/FCC/FDA/BSCI） - 识别源头工厂 vs 中间商（看是否自有产线、是否有研发能力） **输出格式：** ``` 【工厂推荐】（锁定 2-3 家） 🏭 工厂 A：[工厂名称]（超级工厂/源头工厂） - 所在地：XXX省 XXX市 - 诚信通年限：X 年 - 主营品类：XXX - 近90天成交额：¥XXX 或 XXX万 - 复购率：XX% - 发货时效：XX小时/天内 - 跨境证书：CE / FCC / FDA / BSCI（列出有的） - 擅长工艺：XXX - 参考起订量（MOQ）：XXX个 - 参考报价：¥XXX/个 - 优势：<简评> - ⚠️ 风险提示：<如有> 🏭 工厂 B：... ``` --- ### 第三步：询盘话术生成 (Inquiry) **话术生成逻辑：** 根据用户的商业模式（精品独立站 / 低价拼团 / TikTok直播 / 亚马逊FBA / OEM品牌），自动生成针对性话术。 **话术必须包含：** - 自我介绍 + 业务背景（展示大客户形象） - 具体需求确认（OEM/拿样/砍价/MOQ协商/包材定制/一件代发） - 明确下单意向和时间节点 **输出格式：** ``` 【询盘话术 — 针对 [工厂名]】 💬 第一套（拿样方案） > "老板，您好！我们是[公司/团队]，专注[目标市场]跨境电商，主要做[平台]， > 看到贵厂的[产品名]很符合我们的需求，想先了解几个问题： > 1. 支持拿样吗？样品费多少？能否在确认订单后抵扣货款？ > 2. 产品支持[定制需求，如：品牌Logo丝印/彩盒定制/接口改装]吗？ > 3. 首批[XX个]，能否按[XX个]的阶梯价走？ > 如果一切顺利，我们计划[时间]内下正式订单，期待合作！" 💬 第二套（价格谈判） > ... 💬 第三套（OEM定制） > ... ``` --- ### 第四步：利润核算 (Financial) **计算公式：** ``` 单品毛利 = (售价 - 进货价 - 头程运费 - 平台佣金 - 损耗) / 售价 × 100% 单品利润 = 售价 × (毛利 / 100) × 数量 具体拆解： - 进货价（FOB）：¥XX - 头程运费（分摊到单）：¥XX - 平台佣金（如Amazon 15%）：¥XX - 预计损耗率：X% - 平台售价：$XX / ¥XX（汇率：X） - 折合人民币售价：¥XX ``` **输出格式：** ``` 【生意账本】 - 进货成本合计：¥XXX - 预期毛利：XX% - 单品预期利润：¥XXX - 首批 XXX 个预期总利润：¥XXX - 💡 建议： 1. 重点确认：认证/专利/多语言支持 2. 避坑点：<关键风险> 3. 谈判筹码：<可用信息压价> ``` --- ### 第五步：综合报告 (Report) 输出《1688 供应链落地清单》： ``` ═══════════════════════════════════════ 1688 供应链落地清单 生成时间：XXXX-XX-XX ═══════════════════════════════════════ 📌 选品结论 [选品建议内容] 📌 供应商对比 | 维度 | 工厂A | 工厂B | 工厂C | |------|------|------|------| | 诚信通年限 | X年 | X年 | X年 | | 近90天成交额 | ¥XX万 | ¥XX万 | ¥XX万 | | MOQ | XXX个 | XXX个 | XXX个 | | 单价（参考） | ¥XXX | ¥XXX | ¥XXX | | 跨境证书 | ... | ... | ... | | 综合推荐 | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ | 📌 谈判策略 1. 优先联系：[工厂名]，理由：... 2. 谈判切入点：... 3. 预期拿到的条件：... 📌 执行时间表 - 本周：联系供应商，获取样品 - 第2周：样品测试，确认质量 - 第3周：谈判正式订单 ═══════════════════════════════════════ ``` --- ## 参考资源 | 场景 | 参考文件 | |------|----------| | 选品分析详细逻辑 | `references/selection.md` | | 工厂筛选标准 | `references/sourcing.md` | | 询盘话术模板库 | `references/inquiry.md` | | 利润计算公式 | `references/financial.md` | | 报告输出模板 | `references/report-template.md` | --- ## 工具集 (Tools) ### 数据获取 | 脚本 | 功能 | 用法 | |------|------|------| | `scraper.py` | 无 Key 抓取 1688 搜索结果页 | `python3 scripts/scraper.py products <关键词>` / `factories <关键词>` | | `ali_api.py` | 1688 开放平台官方 API（需 AppKey） | `ALI_APP_KEY=xxx python3 scripts/ali_api.py search <关键词>` | | `search_products.py` | 商品搜索（双模式自动切换） | `python3 scripts/search_products.py <关键词>` | | `search_factories.py` | 工厂搜索 | `python3 scripts/search_factories.py <关键词> [--province 省名]` | | `product_detail.py` | 商品详情获取 | `python3 scripts/product_detail.py <商品ID>` | | `calc_profit.py` | 利润计算器 | `python3 scripts/calc_profit.py <进价> <售价> [--platform amazon]` | | `1688_trader.py` | 统一入口 CLI（全链路） | `python3 scripts/1688_trader.py full <关键词>` | ### 1688 开放平台 API 申请（可选） > 免费注册：https://open.1688.com/ > 注册后创建应用，获取 AppKey + AppSecret，配置环境变量： ```bash export ALI_APP_KEY=你的AppKey export ALI_APP_SECRET=你的AppSecret ``` API 可获取完整数据：商品详情、价格阶梯、SKU、认证资质、公司信息。 --- ## 约束条件 - **输出必须结构化**：严禁"官方空话"，每条信息必须有数据支撑 - **必须附带 1688 指标数据**：至少 1-2 个具体指标（诚信通年限/成交额/复购率） - **利润计算保留两位小数** - **风险提示强制执行**：液体/电池类产品须提示物流风险，疑似侵权产品须提示专利风险 - **话术专业且强势**：模拟大客户姿态，非零散买家 --- ## 典型触发示例 - "帮我选一款北美爆款宠物用品，在1688找源头厂" - "我想做亚马逊，指甲油货源怎么找？" - "帮我写一封给1688工厂的询盘邮件，要拿样100个" - "这个产品利润怎么算？" - "帮我对比这两家1688工厂" - "TikTok上有个爆款，我想找1688同款代发"

path: scripts/product_detail.py
content: #!/usr/bin/env python3
"""
1688 商品详情 & 价格分析 — product_detail.py
用法: python3 product_detail.py <商品ID或链接>

依赖：python3, requests（标准库 + pip install requests）
网络：仅访问 *.1688.com 域名
凭证：无需，可选配置 ALI_COOKIE 环境变量增强访问能力
"""

import sys
import json
import re
import os
import requests

# 可选 Cookie（从环境变量读取，非必需）
_OPTIONAL_COOKIE = os.environ.get("ALI_COOKIE", "")

HEADERS = {
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    "Accept": "application/json, text/javascript, */*; q=0.01",
    "Accept-Language": "zh-CN,zh;q=0.9",
    "Referer": "https://detail.1688.com/",
}
if _OPTIONAL_COOKIE:
    HEADERS["Cookie"] = _OPTIONAL_COOKIE

def get_product_detail(product_id: str) -> dict:
    """
    获取 1688 商品详细信息，包含价格阶梯、MOQ、认证资质等。
    """
    # 1688 offer detail 接口
    url = "https://greenwholesale.1688.com/service/offerdetail/v1/detail.json"
    params = {"offerId": product_id}

    try:
        resp = requests.get(url, params=params, headers=HEADERS, timeout=15)
        resp.encoding = "utf-8"
        data = resp.json()
        return _parse_detail(data)
    except Exception:
        pass

    # 兜底: 淘宝/1688 open API 风格
    try:
        url2 = f"https://m.1688.com/offer/{product_id}.html"
        resp2 = requests.get(url2, headers=HEADERS, timeout=15)
        resp2.encoding = "utf-8"
        html = resp2.text
        return _parse_html_detail(html, product_id)
    except Exception as e:
        return {"error": str(e), "success": False}


def _parse_detail(data: dict) -> dict:
    """解析商品详情 JSON"""
    if not data:
        return {"error": "空响应", "success": False}

    # 提取核心字段
    basic = data.get("detail", {}) or data.get("data", {}) or data
    price = data.get("price", {}) or basic.get("price", {})
    seller = data.get("sellerInfo", {}) or data.get("company", {}) or {}

    # 价格阶梯
    price_list = []
    for entry in (price.get("priceRange", []) or price.get("skuCalPrice", []) or []):
        if isinstance(entry, dict):
            price_list.append({
                "数量区间": entry.get("range", entry.get("quantityRange", "")),
                "单价": entry.get("price", entry.get("priceString", "")),
                "折扣": entry.get("discount", ""),
            })

    # 认证资质
    certs = []
    for cert in (data.get("certList", []) or data.get("authenticateInfo", []) or []):
        certs.append(_clean_html(str(cert)))

    return {
        "success": True,
        "offerId": basic.get("offerId", basic.get("id", "")),
        "商品标题": _clean_html(basic.get("subject", basic.get("title", ""))),
        "商品链接": basic.get("detailUrl", f"https://detail.1688.com/offer/{basic.get('offerId','')}.html"),
        "公司信息": {
            "公司名称": _clean_html(seller.get("companyName", "")),
            "诚信通年限": seller.get("creditYear", "未标注"),
            "旺旺": seller.get("aliId", ""),
            "诚信等级": seller.get("creditLevel", ""),
            "工厂标签": seller.get("factoryTag", ""),
            "近90天成交额": _parse_amount(seller.get("AmountInfo", "")),
            "发货地": _clean_html(seller.get("address", "")),
            "旺铺链接": seller.get("homeUrl", ""),
        },
        "价格阶梯": price_list if price_list else [{"区间": "见详情页", "单价": price.get("priceString", price.get("unitPrice", ""))}],
        "MOQ": basic.get("最小起订量", basic.get("minOrder", "未标注")),
        "30天成交": basic.get("soldCount30", basic.get("soldCount", "未披露")),
        "180天成交": basic.get("soldCount180", "未披露"),
        "认证资质": certs if certs else ["需进入详情页查看"],
        "SKU信息": _extract_skus(data),
        "详细描述链接": basic.get("descriptionUrl", ""),
    }


def _parse_html_detail(html: str, product_id: str) -> dict:
    """从 HTML 中解析商品详情（兜底方案）"""
    # 尝试提取 JSON 数据
    patterns = [
        r'window\.__INIT_STATE__\s*=\s*(\{.*?\});',
        r'"price":"([\d.]+)"',
        r'"title":"([^"]+)"',
        r'"soldCount30":(\d+)',
        r'"minOrder":(\d+)',
        r'"companyName":"([^"]+)"',
    ]

    result = {"success": True, "fallback": True, "offerId": product_id}

    for pat in patterns:
        ms = re.findall(pat, html)
        if ms:
            result["matched_data"] = ms[:5]

    result["商品链接"] = f"https://detail.1688.com/offer/{product_id}.html"
    result["提示"] = "建议登录1688账号后获取完整数据"
    return result


def _extract_skus(data: dict) -> list:
    """提取 SKU 规格信息"""
    skus = []
    for sku_list in (data.get("skuList", []) or data.get("skus", []) or [data.get("sku", {})]):
        if isinstance(sku_list, dict):
            skus.append({
                "规格": sku_list.get("spec", sku_list.get("name", "")),
                "库存": sku_list.get("amount", sku_list.get("stock", "")),
                "价格": sku_list.get("price", sku_list.get("unitPrice", "")),
            })
    return skus[:10]  # 最多10条


def _parse_amount(val) -> str:
    if not val: return "未披露"
    val = str(val)
    if "万" in val or "元" in val: return val
    try:
        f = float(re.search(r"[\d.]+", val).group())
        if f >= 10000: return f"¥{f/10000:.1f}万"
        return f"¥{f:.0f}"
    except: return val

def _clean_html(t) -> str:
    return re.sub(r"<[^>]+>", "", str(t)).strip()


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("用法: python3 product_detail.py <商品ID或1688链接>")
        sys.exit(1)

    pid = sys.argv[1]
    # 从链接中提取 ID
    m = re.search(r"(?:offer/|id=)(\d+)", pid)
    if m:
        pid = m.group(1)

    result = get_product_detail(pid)
    print(json.dumps(result, ensure_ascii=False, indent=2))


path: scripts/scraper.py
content: #!/usr/bin/env python3
"""
1688 通用抓取器 — scraper.py
直接抓取 1688 页面 HTML，从页面 DOM / script 内嵌 JSON 中提取商品和工厂数据。
"""

import os
import sys
import json
import re
import time
import requests
from typing import Optional

# Cookie 可选配置（从环境变量读取）
_OPTIONAL_COOKIE = os.environ.get("ALI_COOKIE", "")

HEADERS = {
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
    "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
    "Accept-Encoding": "gzip, deflate, br",
    "Connection": "keep-alive",
    "Upgrade-Insecure-Requests": "1",
    "Sec-Fetch-Dest": "document",
    "Sec-Fetch-Mode": "navigate",
    "Sec-Fetch-Site": "none",
    "Sec-Fetch-User": "?1",
    "Cache-Control": "max-age=0",
}
if _OPTIONAL_COOKIE:
    HEADERS["Cookie"] = _OPTIONAL_COOKIE

SESSION = requests.Session()
SESSION.headers.update(HEADERS)


def fetch(url: str, encoding: str = "utf-8", timeout: int = 20) -> Optional[str]:
    """抓取页面，返回 HTML 文本"""
    try:
        resp = SESSION.get(url, timeout=timeout)
        # 1688 用 GBK 编码
        if resp.apparent_encoding in ("gbk", "gb2312", "gb18030", "windows-1252"):
            encoding = "gbk"
        resp.encoding = encoding
        return resp.text
    except Exception as e:
        return None


def extract_json_field(html: str, patterns: list) -> Optional[dict]:
    """从 HTML 中用多个正则匹配内嵌 JSON"""
    for pat in patterns:
        m = re.search(pat, html, re.DOTALL)
        if m:
            try:
                data = json.loads(m.group(1))
                return data
            except Exception:
                pass
    return None


def search_products(keyword: str, page: int = 1) -> dict:
    """抓取 1688 商品搜索结果"""
    kw_encoded = requests.utils.quote(keyword)
    # 搜索结果页
    url = f"https://s.1688.com/youzhan/search/searchOffer.json?keywords={kw_encoded}&beginPage={page}&pageSize=20"
    html = fetch(url, encoding="gbk")
    if not html:
        # 兜底：用旧版搜索
        url2 = f"https://search.1688.com/service/json.htm?keywords={kw_encoded}&beginPage={page}&pageSize=20&qrquick=search"
        html = fetch(url2, encoding="gbk")

    if not html:
        return {"success": False, "error": "网络请求失败"}

    # 尝试直接 JSON
    try:
        data = json.loads(html)
        return _parse_product_list(data, keyword)
    except Exception:
        pass

    # 从 HTML 提取内嵌 JSON
    patterns = [
        r'window\.__INIT_STATE__\s*=\s*(\{.{5000,})\s*;</script>',
        r'"moduleList"\s*:\s*(\[.*?\])\s*,',
        r'var\s+offerList\s*=\s*(\[.*?\])\s*;',
        r'"resultList"\s*:\s*(\[.*?\])',
    ]
    for pat in patterns:
        m = re.search(pat, html, re.DOTALL)
        if m:
            try:
                data = json.loads(m.group(1))
                return _parse_product_list(data, keyword)
            except Exception:
                pass

    # 兜底：正则提取商品列表
    items = _regex_extract_products(html)
    if items:
        return _format_product_result(items, keyword)

    return {"success": False, "error": "无法解析页面", "snippet": html[:300]}


def _parse_product_list(data, keyword: str) -> dict:
    """通用 JSON 数据解析"""
    items = []

    def find_items(obj):
        if isinstance(obj, list):
            for x in obj:
                find_items(x)
        elif isinstance(obj, dict):
            # 命中含商品字段的列表
            if "title" in obj or "subject" in obj:
                if any(k in obj for k in ["price", "unitPrice", "最小起订量"]):
                    items.append(obj)
            for v in obj.values():
                find_items(v)

    find_items(data)

    formatted = []
    for item in items[:20]:
        formatted.append({
            "productId": item.get("id") or item.get("offerId") or item.get("itemId", ""),
            "title": _strip(item.get("title", item.get("subject", ""))),
            "price": _price(item.get("price", item.get("unitPrice", ""))),
            "最低MOQ": item.get("最小起订量", item.get("minOrder", "未标注")),
            "30天成交": item.get("soldCount30", "未披露"),
            "诚信通年限": item.get("creditYear", "未标注"),
            "旺旺在线": "✅" if item.get("online") or item.get("w_status") == "1" else "❌",
            "发货地": _addr(item.get("location", {})),
            "公司名": _strip(item.get("companyName", item.get("company", ""))),
            "图片": item.get("imageUrl", "") or item.get("pic_url", ""),
            "链接": f"https://detail.1688.com/offer/{item.get('offerId', item.get('id',''))}.html",
        })

    prices = [float(i["price"]) for i in formatted if i["price"]]
    return {
        "success": True,
        "items": formatted,
        "stats": {
            "关键词": keyword,
            "数量": len(formatted),
            "价格区间": f"¥{min(prices):.2f}~¥{max(prices):.2f}" if prices else "未获取",
            "平均价": f"¥{sum(prices)/len(prices):.2f}" if prices else "未获取",
        }
    }


def _regex_extract_products(html: str) -> list:
    """正则直接提取商品卡片数据"""
    items = []

    # 匹配价格
    price_pat = re.compile(r'"price"\s*:\s*"([^"]+)"')
    title_pat = re.compile(r'"title"\s*:\s*"([^"\\]*(?:\\.[^"\\]*)*)"')
    id_pat = re.compile(r'"offerId"\s*:\s*"?(\d+)"?')
    moq_pat = re.compile(r'"最小起订量"\s*:\s*"([^"]+)"')
    sold_pat = re.compile(r'"soldCount30"\s*:\s*"([^"]+)"')
    addr_pat = re.compile(r'"location"\s*:\s*"\s*([^"]{2,20})"')
    co_pat = re.compile(r'"companyName"\s*:\s*"([^"\\]*(?:\\.[^"\\]*)*)"')

    # 逐行扫描（以 offerId 为锚点分段）
    segments = re.split(r'(?={"offerId")', html)
    for seg in segments[1:]:
        item = {}
        m_id = id_pat.search(seg)
        if not m_id:
            m_id = re.search(r'"id"\s*:\s*"?(\d+)"?', seg)
        if m_id:
            item["offerId"] = m_id.group(1)
        else:
            continue

        m_price = price_pat.search(seg)
        if m_price: item["price"] = m_price.group(1)

        m_title = title_pat.search(seg)
        if m_title: item["title"] = m_title.group(1)

        m_moq = moq_pat.search(seg)
        if m_moq: item["最小起订量"] = m_moq.group(1)

        m_sold = sold_pat.search(seg)
        if m_sold: item["soldCount30"] = m_sold.group(1)

        m_addr = addr_pat.search(seg)
        if m_addr: item["location"] = m_addr.group(1)

        m_co = co_pat.search(seg)
        if m_co: item["companyName"] = m_co.group(1)

        if item:
            items.append(item)

    return items


def _format_product_result(items: list, keyword: str) -> dict:
    formatted = []
    for item in items[:20]:
        formatted.append({
            "productId": item.get("offerId", item.get("id", "")),
            "title": _strip(item.get("title", "")),
            "price": _price(item.get("price", "")),
            "最低MOQ": item.get("最小起订量", "未标注"),
            "30天成交": item.get("soldCount30", "未披露"),
            "诚信通年限": item.get("creditYear", "未标注"),
            "旺旺在线": "✅",
            "发货地": item.get("location", "未标注"),
            "公司名": _strip(item.get("companyName", "")),
            "图片": item.get("imageUrl", ""),
            "链接": f"https://detail.1688.com/offer/{item.get('offerId','')}.html",
        })
    prices = [float(i["price"]) for i in formatted if i["price"]]
    return {
        "success": True,
        "items": formatted,
        "stats": {
            "关键词": keyword,
            "数量": len(formatted),
            "价格区间": f"¥{min(prices):.2f}~¥{max(prices):.2f}" if prices else "未获取",
            "平均价": f"¥{sum(prices)/len(prices):.2f}" if prices else "未获取",
        }
    }


def search_factories(keyword: str, province: str = "", page: int = 1) -> dict:
    """抓取 1688 工厂搜索结果"""
    kw_encoded = requests.utils.quote(keyword)
    province_encoded = requests.utils.quote(province) if province else ""

    # 工厂频道搜索
    url = f"https://s.1688.com/youzhan/search/searchFactory.json?keywords={kw_encoded}&beginPage={page}&pageSize=20"
    if province_encoded:
        url += f"&province={province_encoded}"

    html = fetch(url, encoding="gbk")
    if not html:
        url2 = f"https://search.1688.com/service/json.htm?keywords={kw_encoded}&beginPage={page}&pageSize=20&topTab=工厂&qrquick=factory"
        html = fetch(url2, encoding="gbk")

    if not html:
        return {"success": False, "error": "网络请求失败"}

    # 直接 JSON
    try:
        data = json.loads(html)
        return _parse_factory_list(data, keyword)
    except Exception:
        pass

    # HTML 内嵌 JSON
    patterns = [
        r'window\.__INIT_STATE__\s*=\s*(\{.{5000,})\s*;</script>',
        r'"factoryList"\s*:\s*(\[.*?\])\s*[,}]',
        r'"companyList"\s*:\s*(\[.*?\])',
    ]
    for pat in patterns:
        m = re.search(pat, html, re.DOTALL)
        if m:
            try:
                data = json.loads(m.group(1))
                return _parse_factory_list(data, keyword)
            except Exception:
                pass

    # 正则提取工厂
    items = _regex_extract_factories(html)
    if items:
        return _format_factory_result(items, keyword)

    return {"success": False, "error": "无法解析页面", "snippet": html[:300]}


def _parse_factory_list(data, keyword: str) -> dict:
    items = []

    def find(obj):
        if isinstance(obj, list):
            for x in obj:
                find(x)
        elif isinstance(obj, dict):
            if any(k in obj for k in ["companyName", "memberId", "factoryType"]):
                if "companyName" in obj or "memberId" in obj:
                    items.append(obj)
            for v in obj.values():
                find(v)

    find(data)
    formatted = []
    for f in items[:10]:
        tags = _strip(" ".join(str(t) for t in f.get("keyWords", []) or f.get("tags", []) or []))
        amount = f.get("AmountInfo", f.get("soldAmount", ""))
        if isinstance(amount, dict):
            amount = amount.get("amount90Day", "")
        formatted.append({
            "companyId": f.get("companyId", f.get("memberId", "")),
            "公司名称": _strip(f.get("companyName", f.get("subject", ""))),
            "工厂标签": tags or "源头工厂",
            "诚信通年限": f.get("creditYear", "未标注"),
            "近90天成交额": _amount(amount),
            "30天成交": f.get("soldCount30", "未披露"),
            "复购率": f.get("repeatCustomerRatio", ""),
            "旺旺在线": "✅" if f.get("online") else "❌",
            "发货地": _addr(f.get("location", {})),
            "旺铺链接": f.get("w_homeUrl", ""),
        })
    amounts = []
    for f in formatted:
        m = re.search(r"[\d.]+", str(f.get("近90天成交额", "")))
        if m: amounts.append(float(m.group()))
    avg = f"¥{sum(amounts)/len(amounts):.1f}万" if amounts else "未获取"
    return {
        "success": True,
        "items": formatted,
        "stats": {"关键词": keyword, "数量": len(formatted), "平均成交额": avg}
    }


def _regex_extract_factories(html: str) -> list:
    items = []
    id_pat = re.compile(r'"companyId"\s*:\s*"?(\d+)"?|"memberId"\s*:\s*"?(\d+)"?')
    name_pat = re.compile(r'"companyName"\s*:\s*"([^"\\]*(?:\\.[^"\\]*)*)"')
    year_pat = re.compile(r'"creditYear"\s*:\s*"?(\d+)"?')
    amt_pat = re.compile(r'"amount90Day"\s*:\s*"?([\d.]+)"?|"AmountInfo"\s*:\s*"?([\d.]+)"?')
    addr_pat = re.compile(r'"province"\s*:\s*"([^"]{2,10})"')

    segments = re.split(r'(?={"companyId"|"memberId")', html)
    for seg in segments[1:]:
        item = {}
        m_id = id_pat.search(seg)
        if m_id: item["companyId"] = m_id.group(1) or m_id.group(2)
        else: continue
        m_name = name_pat.search(seg)
        if m_name: item["companyName"] = m_name.group(1)
        m_year = year_pat.search(seg)
        if m_year: item["creditYear"] = m_year.group(1)
        m_amt = amt_pat.search(seg)
        if m_amt: item["amount90Day"] = m_amt.group(1) or m_amt.group(2)
        m_addr = addr_pat.search(seg)
        if m_addr: item["location"] = m_addr.group(1)
        if item: items.append(item)
    return items


def _format_factory_result(items: list, keyword: str) -> dict:
    formatted = []
    for f in items[:10]:
        formatted.append({
            "companyId": f.get("companyId", ""),
            "公司名称": _strip(f.get("companyName", "")),
            "工厂标签": "源头工厂",
            "诚信通年限": f.get("creditYear", "未标注"),
            "近90天成交额": _amount(f.get("amount90Day", "")),
            "30天成交": f.get("soldCount30", "未披露"),
            "复购率": f.get("repeatCustomerRatio", ""),
            "旺旺在线": "✅",
            "发货地": f.get("location", "未标注"),
            "旺铺链接": f"https:{f.get('w_homeUrl','')}",
        })
    amounts = []
    for f in formatted:
        m = re.search(r"[\d.]+", str(f.get("近90天成交额", "")))
        if m: amounts.append(float(m.group()))
    avg = f"¥{sum(amounts)/len(amounts):.1f}万" if amounts else "未获取"
    return {
        "success": True,
        "items": formatted,
        "stats": {"关键词": keyword, "数量": len(formatted), "平均成交额": avg}
    }


# --- 工具函数 ---
def _strip(t) -> str:
    return re.sub(r"<[^>]+>", "", str(t)).strip()

def _price(t) -> str:
    if not t: return ""
    m = re.search(r"[\d.]+", str(t))
    return m.group() if m else str(t)

def _amount(t) -> str:
    if not t: return "未披露"
    try:
        f = float(re.search(r"[\d.]+", str(t)).group())
        if f >= 10000: return f"¥{f/10000:.1f}万"
        return f"¥{f:.0f}"
    except: return str(t)

def _addr(loc) -> str:
    if isinstance(loc, dict): return f"{loc.get('province','')}{loc.get('city','')}".strip()
    return str(loc) if loc else ""


if __name__ == "__main__":
    import argparse
    p = argparse.ArgumentParser()
    p.add_argument("cmd", choices=["products", "factories"])
    p.add_argument("keyword", nargs="+")
    p.add_argument("--province")
    p.add_argument("--page", type=int, default=1)
    args = p.parse_args()

    kw = " ".join(args.keyword)
    if args.cmd == "products":
        r = search_products(kw, args.page)
    else:
        r = search_factories(kw, args.province or "", args.page)

    print(json.dumps(r, ensure_ascii=False, indent=2))


path: scripts/search_products.py
content: #!/usr/bin/env python3
"""
1688 商品搜索 — search_products.py
用法: python3 search_products.py <关键词> [--page N] [--min-price X] [--max-price Y] [--factory-only]

依赖：python3, requests（标准库 + pip install requests）
网络：仅访问 *.1688.com 域名
凭证：无需，可选配置 ALI_COOKIE 环境变量增强访问能力
"""

import sys
import json
import re
import os
import urllib.parse
import requests
from typing import Optional

# 可选 Cookie（从环境变量读取，非必需）
_OPTIONAL_COOKIE = os.environ.get("ALI_COOKIE", "")

HEADERS = {
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    "Accept": "application/json, text/javascript, */*; q=0.01",
    "Accept-Language": "zh-CN,zh;q=0.9",
    "Referer": "https://www.1688.com/",
}
if _OPTIONAL_COOKIE:
    HEADERS["Cookie"] = _OPTIONAL_COOKIE

def search_products(keyword: str, page: int = 1, min_price: Optional[float] = None,
                     max_price: Optional[float] = None, factory_only: bool = False,
                     page_size: int = 20) -> dict:
    """
    搜索 1688 商品，返回结构化 JSON 数据。
    """
    params = {
        "keywords": keyword,
        "beginPage": page,
        "pageSize": page_size,
        "qrquick": "search",
        "exposureType": "normal",
        "n": "y",
        "topTab": "供给",
        "s Suggest Input": keyword,
    }

    url = "https://search.1688.com/service/json.htm"

    try:
        resp = requests.get(url, params=params, headers=HEADERS, timeout=15)
        resp.encoding = "gbk" if resp.apparent_encoding in ("gbk", "gb2312", "windows-1252") else "utf-8"
    except Exception as e:
        return {"error": str(e), "success": False}

    text = resp.text

    # 策略1: 直接 JSON
    try:
        raw = resp.json()
        return _parse_json_response(raw, keyword)
    except Exception:
        pass

    # 策略2: HTML 内嵌 JSON
    try:
        raw = _extract_json_from_html(text)
        return _parse_json_response(raw, keyword)
    except Exception:
        pass

    return {"error": "无法解析响应", "raw_snippet": text[:500], "success": False}


def _extract_json_from_html(html: str) -> dict:
    """从 HTML 中提取 embeded JSON 数据"""
    # 尝试找 <script>window.__INIT_STATE__ = {...}</script>
    patterns = [
        r'window\.__INIT_STATE__\s*=\s*(\{.*?\});',
        r'"moduleList"\s*:\s*(\[.*?\])',
        r'"resultList"\s*:\s*(\[.*?\])',
    ]
    for pat in patterns:
        m = re.search(pat, html, re.DOTALL)
        if m:
            try:
                return json.loads(m.group(1))
            except Exception:
                continue
    raise ValueError("No JSON found in HTML")


def _parse_json_response(data: dict, keyword: str) -> dict:
    """解析 1688 JSON 响应，提取商品列表"""
    items = []
    # 遍历多种可能的数据路径
    content = data
    for path in ["data.moduleList", "data.resultList", "resultList", "data.modules"]:
        try:
            for key in path.split(".")[1:]:
                content = content[key]
            break
        except Exception:
            content = data

    if isinstance(content, list):
        raw_items = content
    else:
        raw_items = []

    for item in raw_items[:20]:
        if not isinstance(item, dict):
            continue
        items.append({
            "productId": item.get("id") or item.get("itemId") or item.get("offerId", ""),
            "title": _clean_html(item.get("title", "") or item.get("subject", "")),
            "price": _clean_price(item.get("price", "") or item.get("priceString", "")),
            "最低MOQ": item.get("最小起订量") or item.get("minOrder", "") or "未标注",
            "30天成交": item.get("soldCount30") or item.get("soldInfo", {}).get("soldCount30", "未披露"),
            "诚信通年限": item.get("creditYear", "未标注"),
            "旺旺在线": item.get("online", "") or item.get("w_status", ""),
            "发货地": _parse_address(item),
            "公司名": _clean_html(item.get("companyName", "") or item.get("company", "") or ""),
            "旺铺链接": item.get("w_homeUrl") or item.get("wapHomeUrl") or "",
            "图片链接": item.get("imageUrl", "") or item.get("pic_url", "") or "",
        })

    # 汇总统计
    prices = [float(it["price"]) for it in items if it["price"]]
    stats = {
        "搜索关键词": keyword,
        "商品数量": len(items),
        "价格区间": f"¥{min(prices):.2f} - ¥{max(prices):.2f}" if prices else "未获取到价格",
        "平均价格": f"¥{sum(prices)/len(prices):.2f}" if prices else "未获取到价格",
        "数据来源": "1688 搜索接口",
    }
    return {"success": True, "items": items, "stats": stats, "raw_keys": list(data.keys()) if isinstance(data, dict) else []}


def _clean_html(text: str) -> str:
    """去除 HTML 标签"""
    return re.sub(r"<[^>]+>", "", text).strip()


def _clean_price(text) -> str:
    """提取价格数字"""
    if not text:
        return ""
    text = str(text)
    m = re.search(r"[\d.]+", text)
    return m.group() if m else text


def _parse_address(item: dict) -> str:
    """解析发货地"""
    loc = item.get("location", {}) or {}
    if isinstance(loc, dict):
        return f"{loc.get('province','')}{loc.get('city','')}{loc.get('area','')}".strip()
    return str(item.get("address", item.get("province", "")))


# --- CLI ---
if __name__ == "__main__":
    args = sys.argv[1:]
    kw = ""
    page = 1
    factory_only = False
    min_p = None
    max_p = None

    i = 0
    while i < len(args):
        if args[i] == "--page" and i+1 < len(args):
            page = int(args[i+1]); i += 2
        elif args[i] == "--min-price" and i+1 < len(args):
            min_p = float(args[i+1]); i += 2
        elif args[i] == "--max-price" and i+1 < len(args):
            max_p = float(args[i+1]); i += 2
        elif args[i] == "--factory-only":
            factory_only = True; i += 1
        else:
            kw = args[i]; i += 1

    if not kw:
        print("用法: python3 search_products.py <关键词> [--page N] [--min-price X] [--max-price Y] [--factory-only]")
        sys.exit(1)

    result = search_products(kw, page, min_p, max_p, factory_only)
    print(json.dumps(result, ensure_ascii=False, indent=2))


path: scripts/ali_api.py
content: #!/usr/bin/env python3
"""
1688 开放平台 API 客户端 — ali_api.py

【重要说明】
本脚本为可选增强模块，无需配置即可运行基础功能。
如需获取更完整的1688数据（价格阶梯、SKU详情、认证资质），可选择性配置：
  1. 注册 1688 开放平台：https://open.1688.com/
  2. 创建应用获取 AppKey + AppSecret
  3. 设置环境变量：
     export ALI_APP_KEY=your_app_key
     export ALI_APP_SECRET=your_app_secret

未配置时，脚本会返回友好提示，不影响其他脚本使用。

API 覆盖范围：
  - 商品搜索 (alibaba.product.search)
  - 商品详情 (alibaba.product.get)
  - 公司信息 (alibaba.company.get)
  - 旺旺在线状态

依赖：python3, requests（标准库 + pip install requests）
网络：仅访问 gw.open.1688.com
凭证：可选（ALI_APP_KEY / ALI_APP_SECRET）
"""

import os
import json
import time
import hashlib
import requests
from urllib.parse import urlencode, quote
from typing import Optional

# ── 配置 ──────────────────────────────────────────────
# 【可选】1688 开放平台凭证，未配置时脚本会返回友好提示
# 获取方式：https://open.1688.com/ → 创建应用 → 获取 AppKey + AppSecret
APP_KEY    = os.environ.get("ALI_APP_KEY",    "")
APP_SECRET = os.environ.get("ALI_APP_SECRET", "")

# 1688 开放平台 API 基础地址
API_BASE = "https://gw.open.1688.com/openapi"

# ── 签名算法 ──────────────────────────────────────────
def _sign(params: dict, secret: str) -> str:
    """MD5 签名（1688 开放平台标准）"""
    sorted_keys = sorted(params.keys())
    pairs = [f"{k}{params[k]}" for k in sorted_keys]
    sign_str = "".join(pairs) + secret
    return hashlib.md5(sign_str.encode("utf-8")).hexdigest().upper()


def _top_call(method: str, params: dict, timeout: int = 20) -> Optional[dict]:
    """
    调用 1688 TOP API（开放平台标准协议）。
    method: 类似 alibaba.product.search
    """
    if not APP_KEY or not APP_SECRET:
        return None

    # 公共参数
    ts = str(int(time.time() * 1000))
    all_params = {
        "appKey":       APP_KEY,
        "method":       method,
        "timestamp":    ts,
        "format":       "json",
        "v":            "2.0",
        "signMethod":   "md5",
        # 业务参数
        **{k: json.dumps(v, ensure_ascii=False) if isinstance(v, (dict, list)) else str(v)
           for k, v in params.items()},
    }
    all_params["sign"] = _sign(all_params, APP_SECRET)

    url = f"{API_BASE}/{method}/2.0"
    try:
        resp = requests.post(url, data=all_params, timeout=timeout)
        resp.encoding = "utf-8"
        data = resp.json()
        return data
    except Exception as e:
        return {"error": str(e)}


# ── 商品搜索 ──────────────────────────────────────────
def search_products(keyword: str, page: int = 1, page_size: int = 20,
                    category_id: str = "", price_min: float = 0,
                    price_max: float = 0, sort: str = "commissionRate-desc") -> dict:
    """
    搜索 1688 商品，支持按价格区间、排序筛选。
    sort 选项：commissionRate-desc / price-asc / price-desc / salecount-desc
    """
    params = {
        "keyword":    keyword,
        "pageNo":     page,
        "pageSize":   page_size,
        "sort":       sort,
    }
    if category_id:
        params["categoryId"] = category_id
    if price_min:
        params["priceStart"] = str(price_min)
    if price_max:
        params["priceEnd"] = str(price_max)

    result = _top_call("alibaba.product.search", params)

    if result is None:
        return {
            "success": False,
            "error": "未配置 ALI_APP_KEY / ALI_APP_SECRET",
            "setup_hint": "请访问 https://open.1688.com/ 注册并配置环境变量",
        }

    if result.get("error"):
        return {"success": False, "error": result.get("error")}

    try:
        products = result.get("result", {}).get("products", {}).get("product", [])
        total = result.get("result", {}).get("total", 0)
        return _format_product_result(products, keyword, total, page, page_size)
    except Exception as e:
        return {"success": False, "error": str(e), "raw": result}


def _format_product_result(products: list, keyword: str, total: int,
                            page: int, page_size: int) -> dict:
    """格式化商品搜索结果"""
    items = []
    for p in products:
        skus = p.get("skuInfos", {}).get("skuInfo", [])
        price_range = ""
        if skus:
            prices = [float(s.get("price", 0)) for s in skus if s.get("price")]
            if prices:
                price_range = f"¥{min(prices):.2f}~¥{max(prices):.2f}"

        items.append({
            "productId":    p.get("productID", ""),
            "商品ID":       p.get("productID", ""),
            "title":       p.get("subject", ""),
            "价格区间":     price_range,
            "单件起批价":   p.get("price", ""),
            "30天成交":     p.get("saleCount", ""),
            "起订量MOQ":   p.get("minimumOrderQuantity", "未标注"),
            "计量单位":     p.get("unit", ""),
            "诚信通年限":   p.get("creditYear", "未标注"),
            "旺旺在线":     "✅" if p.get("wStatus") == "1" else "❌",
            "发货地":       p.get("province", "") + p.get("city", ""),
            "公司名":       p.get("companyName", ""),
            "图片":         p.get("imageUri", ""),
            "商品链接":     f"https://detail.1688.com/offer/{p.get('productID','')}.html",
        })

    prices = []
    for item in items:
        m = [float(x) for x in str(item.get("单件起批价", "")).split("~") if x.replace(".","").isdigit()]
        if m: prices.extend(m)
    stats = {
        "关键词": keyword,
        "总数": total,
        "当前页": f"{page}/{max(1,(total+page_size-1)//page_size)}",
        "本页数量": len(items),
        "价格区间": f"¥{min(prices):.2f}~¥{max(prices):.2f}" if prices else "未获取",
        "平均起批价": f"¥{sum(prices)/len(prices):.2f}" if prices else "未获取",
    }
    return {"success": True, "items": items, "stats": stats}


# ── 商品详情 ──────────────────────────────────────────
def get_product_detail(product_id: str) -> dict:
    """获取商品详细信息（含价格阶梯、SKU、认证）"""
    params = {"productID": product_id}
    result = _top_call("alibaba.product.get", params)

    if result is None:
        return {
            "success": False,
            "error": "未配置 ALI_APP_KEY / ALI_APP_SECRET",
            "setup_hint": "请访问 https://open.1688.com/ 注册并配置环境变量",
        }

    if result.get("error"):
        return {"success": False, "error": result.get("error")}

    try:
        p = result.get("result", {}).get("product", {})
        return _format_product_detail(p, product_id)
    except Exception as e:
        return {"success": False, "error": str(e), "raw": result}


def _format_product_detail(p: dict, product_id: str) -> dict:
    """格式化商品详情"""
    skus = p.get("skuInfos", {}).get("skuInfo", [])
    price_list = []
    for s in skus:
        price_list.append({
            "规格":    s.get("specId", "") + " " + str(s.get("specProps", "")),
            "价格":    s.get("price", ""),
            "MOQ":     s.get("minOrderQuantity", ""),
            "库存":    s.get("canBookCount", ""),
        })

    # 认证资质
    certs = p.get("certificationList", {}).get("certification", [])
    cert_names = [c.get("certificationName", "") for c in certs if c.get("certificationName")]

    return {
        "success": True,
        "商品ID": product_id,
        "商品标题": p.get("subject", ""),
        "商品链接": f"https://detail.1688.com/offer/{product_id}.html",
        "公司信息": {
            "公司名称": p.get("companyName", ""),
            "诚信通年限": p.get("creditYear", "未标注"),
            "旺旺": p.get("memberId", ""),
            "旺旺在线": "✅" if p.get("wStatus") == "1" else "❌",
            "近90天成交额": p.get("amountOfBusinessVolume", "未披露"),
            "发货地": p.get("province", "") + p.get("city", ""),
            "旺铺": p.get("homePage", ""),
        },
        "价格阶梯": price_list if price_list else [{"区间":"见详情","单价":p.get("price","")}],
        "MOQ": p.get("minimumOrderQuantity", "未标注"),
        "30天成交": p.get("saleCount", "未披露"),
        "计量单位": p.get("unit", "件"),
        "认证资质": cert_names if cert_names else ["需进入详情页查看"],
        "图片列表": [p.get("imageUri", "")] + [x.get("imageUri","") for x in p.get("imageList",{}).get("image",[])],
        "SKU": price_list,
    }


# ── 旺旺状态 ──────────────────────────────────────────
def check_ww_status(member_id: str) -> dict:
    """检查旺旺在线状态"""
    params = {"memberId": member_id}
    result = _top_call("alibaba.ww.isOnline", params)
    if result is None:
        return {"success": False, "error": "未配置 API Key"}
    try:
        online = result.get("result", {}).get("isOnline", None)
        return {"success": True, "memberId": member_id, "在线": bool(online), "online": online}
    except Exception as e:
        return {"success": False, "error": str(e)}


# ── CLI ──────────────────────────────────────────────
if __name__ == "__main__":
    import sys, argparse
    p = argparse.ArgumentParser(description="1688 开放平台 API 客户端")
    sub = p.add_subparsers(dest="cmd")

    search = sub.add_parser("search", help="搜索商品")
    search.add_argument("keyword", nargs="+")
    search.add_argument("--page", type=int, default=1)
    search.add_argument("--page-size", type=int, default=20)
    search.add_argument("--price-min", type=float, default=0)
    search.add_argument("--price-max", type=float, default=0)
    search.add_argument("--sort", default="salecount-desc")

    detail = sub.add_parser("detail", help="商品详情")
    detail.add_argument("product_id")

    check = sub.add_parser("check", help="旺旺状态")
    check.add_argument("member_id")

    args = p.parse_args()

    if args.cmd == "search":
        kw = " ".join(args.keyword)
        r = search_products(kw, page=args.page, page_size=args.page_size,
                           price_min=args.price_min, price_max=args.price_max, sort=args.sort)
    elif args.cmd == "detail":
        r = get_product_detail(args.product_id)
    elif args.cmd == "check":
        r = check_ww_status(args.member_id)
    else:
        print("用法: python3 ali_api.py [search|detail|check] ...")
        sys.exit(1)

    print(json.dumps(r, ensure_ascii=False, indent=2))


path: scripts/search_factories.py
content: #!/usr/bin/env python3
"""
1688 源头工厂搜索 — search_factories.py
用法: python3 search_factories.py <关键词> [--province 省名]

依赖：python3, requests（标准库 + pip install requests）
网络：仅访问 *.1688.com 域名
凭证：无需，可选配置 ALI_COOKIE 环境变量增强访问能力
"""

import sys
import json
import re
import os
import requests
from typing import Optional

# 可选 Cookie（从环境变量读取，非必需）
_OPTIONAL_COOKIE = os.environ.get("ALI_COOKIE", "")

HEADERS = {
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    "Accept": "application/json, text/javascript, */*; q=0.01",
    "Accept-Language": "zh-CN,zh;q=0.9",
    "Referer": "https://vip.1688.com/",
}
if _OPTIONAL_COOKIE:
    HEADERS["Cookie"] = _OPTIONAL_COOKIE

def search_factories(keyword: str, province: str = "", page: int = 1) -> dict:
    """
    搜索 1688 "实力工厂" / "超级工厂" 标签的源头厂家。
    """
    # 1688 工厂搜索接口
    params = {
        "keywords": keyword,
        "beginPage": page,
        "pageSize": 20,
        "qrquick": "factory",
        "topTab": "工厂",
        "filter": "type=proFactor",
    }
    if province:
        params["province"] = province

    url = "https://search.1688.com/service/json.htm"

    try:
        resp = requests.get(url, params=params, headers=HEADERS, timeout=15)
        resp.encoding = "gbk" if resp.apparent_encoding in ("gbk", "gb2312") else "utf-8"
    except Exception as e:
        return {"error": str(e), "success": False}

    text = resp.text

    # 尝试解析 JSON
    try:
        raw = resp.json()
        return _parse_factory_response(raw, keyword)
    except Exception:
        pass

    # HTML 内嵌 JSON 兜底
    try:
        m = re.search(r'window\.__INIT_STATE__\s*=\s*(\{.*?\})\s*;', text, re.DOTALL)
        if m:
            raw = json.loads(m.group(1))
            return _parse_factory_response(raw, keyword)
    except Exception:
        pass

    return {"error": "无法解析响应", "raw_snippet": text[:500], "success": False}


def _parse_factory_response(data: dict, keyword: str) -> dict:
    """解析工厂数据"""
    items = []

    def dig(obj):
        if isinstance(obj, dict):
            for v in obj.values():
                dig(v)
        elif isinstance(obj, list):
            for item in obj:
                dig(item)

    # 提取所有 offer/company 相关列表
    all_lists = []
    def collect_lists(obj, path=""):
        if isinstance(obj, list) and all(isinstance(x, dict) for x in obj):
            if len(obj) > 0 and any(k in str(obj[0]) for k in ["companyName","creditYear","factoryType","isFactory"]):
                all_lists.append(obj)
        elif isinstance(obj, dict):
            for k,v in obj.items():
                collect_lists(v, path+"."+k)
    collect_lists(data)

    for lst in all_lists:
        for item in lst[:10]:
            if not isinstance(item, dict):
                continue
            # 筛选工厂（标签识别）
            title = _clean_html(item.get("title","") or item.get("subject",""))
            company = _clean_html(item.get("companyName","") or item.get("company",""))
            tags = " ".join(str(t) for t in item.get("tags",[]) or item.get("keyWords",[]) or [])
            is_factory = item.get("isFactory") or item.get("isFactoryInfo") or \
                         ("工厂" in title or "工厂" in company or "实力" in tags or "超级" in tags)

            if not is_factory:
                continue

            items.append({
                "companyId": item.get("companyId") or item.get("memberId","") or item.get("id",""),
                "公司名称": company or title,
                "工厂标签": tags,
                "诚信通年限": item.get("creditYear","未标注"),
                "近90天成交额": _parse_amount(item.get("AmountInfo") or item.get("soldInfo",{}).get("amount90Day","")),
                "30天成交": item.get("soldInfo",{}).get("soldCount30","未披露") or item.get("soldCount30","未披露"),
                "复购率": item.get("repeatCustomerRatio","") or item.get("repeatRate",""),
                "旺旺在线": "✅在线" if item.get("online") or item.get("w_status") == "1" else "❌离线",
                "发货地": _parse_addr(item.get("location",{})),
                "联系方式": item.get("mobile") or item.get("phone") or "需登录查看",
                "旺铺链接": item.get("w_homeUrl") or "",
                "产品列表": [_clean_html(p) for p in (item.get("productList",[]) or item.get("products",[]) or [])[:5]],
            })

    prices = []
    for it in items:
        m = re.search(r"[\d.]+", str(it.get("近90天成交额","")))
        if m: prices.append(float(m.group()))

    stats = {
        "搜索关键词": keyword,
        "工厂数量": len(items),
        "平均成交额（万）": f"¥{sum(prices)/len(prices):.1f}万" if prices else "未获取",
        "来源": "1688 工厂搜索",
    }
    return {"success": True, "items": items, "stats": stats}


def _parse_amount(val) -> str:
    if not val: return "未披露"
    val = str(val)
    if "万" in val: return val
    try:
        f = float(re.search(r"[\d.]+", val).group())
        if f >= 10000: return f"¥{f/10000:.1f}万"
        return f"¥{f:.0f}"
    except: return val

def _parse_addr(loc) -> str:
    if isinstance(loc, dict):
        return f"{loc.get('province','')}{loc.get('city','')}".strip()
    return str(loc) if loc else ""

def _clean_html(t) -> str:
    return re.sub(r"<[^>]+>", "", str(t)).strip()


if __name__ == "__main__":
    args = sys.argv[1:]
    kw = ""
    province = ""
    page = 1
    i = 0
    while i < len(args):
        if args[i] == "--province" and i+1 < len(args):
            province = args[i+1]; i += 2
        elif args[i] == "--page" and i+1 < len(args):
            page = int(args[i+1]); i += 2
        else:
            kw = args[i]; i += 1

    if not kw:
        print("用法: python3 search_factories.py <关键词> [--province 省名]")
        sys.exit(1)

    result = search_factories(kw, province, page)
    print(json.dumps(result, ensure_ascii=False, indent=2))


path: scripts/1688_trader.py
content: #!/usr/bin/env python3
"""
1688 爆款操盘手 — 主入口脚本

【运行环境】
  - Python 3.7+
  - requests 库（pip install requests）
  - 网络：访问 *.1688.com（可选 ALI_APP_KEY/ALI_APP_SECRET 增强）

【凭证说明】
  - 基础功能：无需任何凭证
  - 增强功能（可选）：配置 ALI_APP_KEY / ALI_APP_SECRET 环境变量
    获取方式：https://open.1688.com/ → 创建应用

用法:
  python3 1688_trader.py search <关键词>          # 搜索商品
  python3 1688_trader.py factory <关键词>         # 搜索源头工厂
  python3 1688_trader.py detail <商品ID>           # 商品详情
  python3 1688_trader.py profit <进价> <售价>     # 利润计算
  python3 1688_trader.py full <关键词>            # 全链路：选品+找商+询盘

示例:
  python3 1688_trader.py search 宠物智能喂食器
  python3 1688_trader.py factory 智能喂食器 --province 浙江
  python3 1688_trader.py detail 628734562812
  python3 1688_trader.py profit 150 89.9 --platform amazon --weight-g 1200 --units 100
  python3 1688_trader.py full 户外储能电源
"""

import sys
import json
import re
import os

# 添加 scripts 同级路径以便导入 local 模块
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, SCRIPT_DIR)

from search_products import search_products
from search_factories import search_factories
from product_detail import get_product_detail
from calc_profit import calc_profit as do_calc, PLATFORM_PARAMS, parse_args as calc_parse_args

# ANSI 颜色输出
GREEN = "\033[92m"
YELLOW = "\033[93m"
RED = "\033[91m"
BLUE = "\033[94m"
BOLD = "\033[1m"
RESET = "\033[0m"


def c(text, color=REST):
    return f"{color}{text}{RESET}"


R = RESET; G = GREEN; Y = YELLOW; R_ = RED; B = BLUE; BB = BOLD


def cmd_search(args: list):
    """1688 商品搜索"""
    kw = " ".join(args)
    if not kw:
        print(f"{R_}[用法] {B}search <关键词> [--page N] [--min-price X] [--max-price Y] [--factory-only]")
        return

    page = 1
    factory_only = False
    extra = args.copy()
    if "--page" in extra:
        idx = extra.index("--page")
        page = int(extra[idx+1]); extra = extra[:idx]
    if "--factory-only" in extra:
        factory_only = True; extra.remove("--factory-only")
    kw = " ".join(extra) or kw

    print(f"{BB}🔍 正在搜索 1688: {Y}{kw}{R}")
    if factory_only:
        result = search_factories(kw, page=page)
    else:
        result = search_products(kw, page=page)

    if not result.get("success"):
        print(f"{R_}❌ 错误: {result.get('error','未知错误')}")
        if "raw_snippet" in result:
            print(f"   原始响应片段: {result['raw_snippet'][:200]}")
        return

    items = result.get("items", [])
    stats = result.get("stats", {})
    print(f"\n{BB}📊 搜索结果 — {G}{stats.get('商品数量', len(items))} 件商品{R}")
    print(f"   价格区间: {Y}{stats.get('价格区间', 'N/A')}{R}")
    print(f"   平均价格: {Y}{stats.get('平均价格', 'N/A')}{R}")
    print()

    for i, item in enumerate(items[:20], 1):
        price = item.get("price", "—")
        moq = item.get("最低MOQ", "—")
        sold = item.get("30天成交", "—")
        credit = item.get("诚信通年限", "—")
        addr = item.get("发货地", "—")
        company = item.get("公司名", "—")

        print(f"  {B}{i:02d}. {item.get('title','—')[:45]}{R}")
        if price: print(f"      💰 价格: {Y}¥{price}{R}  |  MOQ: {moq}  |  30天成交: {sold}")
        if addr: print(f"      📍 {addr}  |  诚信通: {credit}年  |  {company}")
        print()


def cmd_factory(args: list):
    """1688 工厂搜索"""
    kw = " ".join(args)
    if not kw:
        print(f"{R_}[用法] factory <关键词> [--province 省名]")
        return

    province = ""
    if "--province" in args:
        idx = args.index("--province")
        province = args[idx+1] if idx+1 < len(args) else ""
        kw = " ".join(args[:idx]) or kw

    print(f"{BB}🏭 正在搜索 1688 源头工厂: {Y}{kw}{R}" + (f" ({province})" if province else ""))
    result = search_factories(kw, province=province)

    if not result.get("success"):
        print(f"{R_}❌ 错误: {result.get('error','未知错误')}")
        return

    items = result.get("items", [])
    stats = result.get("stats", {})
    print(f"\n{BB}📊 搜索结果 — {G}{stats.get('工厂数量', len(items))} 家工厂{R}")
    print(f"   平均成交额: {Y}{stats.get('平均成交额（万）','N/A')}{R}\n")

    for i, f in enumerate(items[:10], 1):
        tags = f.get("工厂标签","—")
        credit = f.get("诚信通年限","—")
        amount = f.get("近90天成交额","—")
        sold = f.get("30天成交","—")
        rep = f.get("复购率","—")
        wm = f.get("旺旺在线","—")
        addr = f.get("发货地","—")

        print(f"  {B}{i:02d}. {f.get('公司名称','—')[:40]}{R}")
        print(f"      🏷️ {tags or '—'}  |  诚信通: {Y}{credit}年{R}  |  {wm}")
        print(f"      💰 近90天: {Y}{amount}{R}  |  复购率: {rep or '—'}  |  30天: {sold}")
        print(f"      📍 {addr}")
        if f.get("旺铺链接"):
            print(f"      🔗 {f['旺铺链接']}")
        print()


def cmd_detail(args: list):
    """商品详情"""
    if not args:
        print(f"{R_}[用法] detail <商品ID或1688链接>")
        return

    pid = args[0]
    m = re.search(r"(?:offer/|id=)(\d+)", " ".join(args))
    if m: pid = m.group(1)

    print(f"{BB}📦 正在获取商品详情: {Y}{pid}{R}")
    result = get_product_detail(pid)

    if not result.get("success"):
        print(f"{R_}❌ 错误: {result.get('error')}")
        return

    co = result.get("公司信息", {})
    print(f"\n{BB}📌 {result.get('商品标题','—')}{R}")
    print(f"   🔗 {result.get('商品链接','—')}")
    print()
    print(f"{BB}🏭 供应商信息{R}")
    print(f"   公司名: {co.get('公司名称','—')}")
    print(f"   诚信通: {Y}{co.get('诚信通年限','—')}年{R}  |  诚信等级: {co.get('诚信等级','—')}")
    print(f"   工厂标签: {co.get('工厂标签','—')}")
    print(f"   近90天成交额: {Y}{co.get('近90天成交额','—')}{R}  |  发货地: {co.get('发货地','—')}")

    print()
    print(f"{BB}💰 价格阶梯{R}")
    for p in result.get("价格阶梯", [])[:5]:
        print(f"   {p.get('数量区间','—'):20s}  →  ¥{p.get('单价','—')}")

    print()
    print(f"{BB}📊 交易数据{R}")
    print(f"   MOQ: {Y}{result.get('MOQ','—')}{R}  |  30天成交: {result.get('30天成交','—')}  |  180天成交: {result.get('180天成交','—')}")

    certs = result.get("认证资质", [])
    if certs and certs != ["需进入详情页查看"]:
        print()
        print(f"{BB}✅ 认证资质{R}")
        for c in certs: print(f"   • {c}")


def cmd_profit(args: list):
    """利润计算"""
    if len(args) < 2:
        print(f"{R_}[用法] profit <进货价(元)> <售价(美元)> [--platform amazon|tiktok|shopify|temu]")
        print(f"       [--weight-g X] [--freight-kg Y] [--units N] [--currency USD|EUR|GBP]")
        return

    # 用 argparse 解析利润参数
    raw = [args[0], args[1]] + args[2:]
    sys.argv = ["calc"] + raw
    import argparse
    p = argparse.ArgumentParser()
    p.add_argument("cost_price", type=float)
    p.add_argument("sell_price", type=float)
    p.add_argument("--currency", default="USD")
    p.add_argument("--platform", default="amazon")
    p.add_argument("--weight-g", type=float, default=500.0)
    p.add_argument("--freight-kg", type=float, default=5.0)
    p.add_argument("--units", type=int, default=100)
    p.add_argument("--exchange-rate", type=float, default=7.20)
    p.add_argument("--exchange-eur", type=float, default=7.80)
    p.add_argument("--exchange-gbp", type=float, default=9.10)
    parsed = p.parse_args(raw)

    result = do_calc(parsed)
    if not result.get("success"):
        print(f"{R_}❌ 错误"); return

    bd = result["cost_breakdown_cny"]
    sym = "$" if result["currency"] != "CNY" else "¥"

    print(f"\n{BB}💰 利润分析 — {result['platform'].upper()} | {result['currency']}{R}")
    print(f"{BB}┌{'─'*42}┐")
    print(f"│ 进货成本合计:                        {R}¥{result['total_cost_cny']:.2f}{BB}  ({result['platform']}平台，首批{result['params']['首批数量']}个)")
    print(f"│{R}")
    print(f"│  进货价:     ¥{bd['进货价']:.2f}  |  头程:  ¥{bd['头程运费']:.2f}")
    print(f"│  平台佣金:   ¥{bd['平台佣金']:.2f}  |  推广:  ¥{bd['推广费']:.2f}")
    print(f"│  尾程配送:   ¥{bd['尾程配送']:.2f}  |  损耗:  ¥{bd['退货损耗']:.2f}")
    print(f"│  汇损:       ¥{bd['汇损']:.2f}")
    print(f"│{R}")
    print(f"│  合计成本:   ¥{bd['合计成本']:.2f} / 件")
    print(f"│{B}")
    print(f"│  折合售价:   ¥{result['sell_price_cny']:.2f} (汇率{result['exchange_rate']})")
    print(f"│  单品毛利:   {G}{result['unit_margin_pct']:.2f}%{R}  |  单品利润: {G}¥{result['unit_profit_cny']:.2f}{R}")
    print(f"│  首批总利润: {G}{result['total_profit_cny']:.2f}{R} 元")
    print(f"│{R}")
    print(f"│  健康状态:   {result['health_check']['status']}  {result['health_check']['建议']}")
    print(f"{BB}└{'─'*42}┘{R}")


def cmd_full(args: list):
    """全链路选品+找商+利润分析"""
    kw = " ".join(args)
    if not kw:
        print(f"{R_}[用法] full <关键词（产品品类）>")
        return

    print(f"\n{B}{'═'*55}")
    print(f"  1688 爆款操盘手 — 全链路分析")
    print(f"  产品: {Y}{kw}{B}")
    print(f"{'═'*55}{R}\n")

    # Step 1: 搜索商品
    print(f"{BB}[1/3] 🔍 搜索 1688 市场数据...{R}")
    prod_result = search_products(kw, page=1)
    items = prod_result.get("items", []) if prod_result.get("success") else []
    print(f"   找到 {Y}{len(items)}{R} 件相关商品\n")

    # Step 2: 搜索工厂
    print(f"{BB}[2/3] 🏭 穿透搜索源头工厂...{R}")
    fac_result = search_factories(kw, page=1)
    factories = fac_result.get("items", []) if fac_result.get("success") else []
    print(f"   找到 {Y}{len(factories)}{R} 家源头工厂\n")

    # Step 3: 推荐 Top 产品 & 工厂
    if items:
        top = items[0]
        print(f"{BB}📦 推荐商品 (Top 1){R}")
        print(f"   {top.get('title','—')[:50]}")
        print(f"   💰 ¥{top.get('price','—')}  |  MOQ: {top.get('最低MOQ','—')}")
        print(f"   📊 30天成交: {top.get('30天成交','—')}  |  {top.get('发货地','—')}\n")

    if factories:
        top_f = factories[0]
        print(f"{BB}🏭 推荐工厂 (Top 1){R}")
        print(f"   {top_f.get('公司名称','—')[:45]}")
        print(f"   诚信通: {Y}{top_f.get('诚信通年限','—')}年{R}  |  近90天: {Y}{top_f.get('近90天成交额','—')}{R}")
        print(f"   📍 {top_f.get('发货地','—')}  |  {top_f.get('旺旺在线','—')}\n")
        if top_f.get("旺铺链接"):
            print(f"   🔗 {top_f['旺铺链接']}\n")

    # Step 4: 利润计算（用商品价格估算）
    if items and factories:
        price = items[0].get("price", "150")
        try: price = float(price)
        except: price = 150.0

        print(f"{BB}[3/3] 💰 利润预估 (进价¥{price:.0f} → 售价$89.99){R}")
        print(f"   （以下为Amazon FBA模式参考）\n")

        raw = [str(price), "89.99", "--platform", "amazon", "--units", "100"]
        sys.argv = ["calc"] + raw
        import argparse
        p = argparse.ArgumentParser()
        p.add_argument("cost_price", type=float)
        p.add_argument("sell_price", type=float)
        p.add_argument("--currency", default="USD")
        p.add_argument("--platform", default="amazon")
        p.add_argument("--weight-g", type=float, default=500.0)
        p.add_argument("--freight-kg", type=float, default=5.0)
        p.add_argument("--units", type=int, default=100)
        p.add_argument("--exchange-rate", type=float, default=7.20)
        p.add_argument("--exchange-eur", type=float, default=7.80)
        p.add_argument("--exchange-gbp", type=float, default=9.10)
        parsed = p.parse_args(raw)
        result = do_calc(parsed)
        bd = result["cost_breakdown_cny"]
        print(f"   ┌─────────────────────────────────────────────┐")
        print(f"   │ 进货成本合计: ¥{result['total_cost_cny']:.2f}  |  首批100个  │")
        print(f"   │ 折合售价: ¥{result['sell_price_cny']:.2f}  |  合计成本/件: ¥{bd['合计成本']:.2f}  │")
        print(f"   │ 单品毛利: {G}{result['unit_margin_pct']:.2f}%{R}  |  单品利润: {G}¥{result['unit_profit_cny']:.2f}{R}   │")
        print(f"   │ 首批100个总利润: {G}¥{result['total_profit_cny']:.2f}{R}             │")
        print(f"   │ 健康状态: {result['health_check']['status']}                      │")
        print(f"   └─────────────────────────────────────────────┘")

    print(f"\n{B}{'═'*55}{R}")
    print(f"  分析完毕。如需询盘话术或详细报告，请告诉我具体需求。")


# --- 命令行路由 ---
COMMANDS = {
    "search":    (cmd_search,    "搜索 1688 商品"),
    "factory":   (cmd_factory,   "搜索 1688 源头工厂"),
    "detail":    (cmd_detail,    "获取商品详情"),
    "profit":    (cmd_profit,    "利润计算"),
    "full":      (cmd_full,      "全链路分析（选品+工厂+利润）"),
    "--help":    (None,          "显示帮助"),
    "-h":        (None,          "显示帮助"),
}

def main():
    if len(sys.argv) < 2 or sys.argv[1] in ("--help", "-h"):
        print(f"""
{B}╔═══════════════════════════════════════════════════╗
║        1688 爆款操盘手 CLI  (v1.0)                 ║
╠═══════════════════════════════════════════════════╣
║  search   <关键词>      搜索 1688 商品               ║
║  factory  <关键词>      搜索源头工厂                  ║
║  detail   <商品ID>      获取商品详情                  ║
║  profit   <进价> <售价> 利润计算                     ║
║  full     <关键词>      全链路分析                    ║
╠═══════════════════════════════════════════════════╣
║  示例:                                               ║
║  python3 1688_trader.py search 宠物喂食器            ║
║  python3 1688_trader.py factory 智能喂食器 --province 浙江 ║
║  python3 1688_trader.py detail 628734562812          ║
║  python3 1688_trader.py profit 150 89.9 --platform amazon --units 100 ║
║  python3 1688_trader.py full 户外储能电源           ║
╚═══════════════════════════════════════════════════╝{R}
        """)
        return

    cmd = sys.argv[1]
    if cmd not in COMMANDS:
        print(f"{R_}❌ 未知命令: {cmd}  （可用: {', '.join(COMMANDS)}){R}")
        return

    fn, desc = COMMANDS[cmd]
    args = sys.argv[2:]
    fn(args)


if __name__ == "__main__":
    main()


path: scripts/calc_profit.py
content: #!/usr/bin/env python3
"""
利润计算器 — calc_profit.py
用法: python3 calc_profit.py <进货价> <售价> [--currency USD|EUR|GBP|USD]
       [--platform amazon|tiktok|shopify|temu]
       [--weight-gkg X]  (克重，g)
       [--freight-kg Y]   (头程每kg单价，美元)
       [--units N]        (首批数量)

依赖：python3（纯标准库，无外部依赖）
网络：无
凭证：无
"""

import sys
import json
import argparse

# 各平台费率参考
PLATFORM_PARAMS = {
    "amazon": {
        "commission_rate": 0.15,
        "fba_per_unit": 3.50,    # FBA 配送费参考
        "ac_acos": 0.25,         # 广告 ACOS
        "return_rate": 0.03,
        "currency_symbol": "$",
    },
    "tiktok": {
        "commission_rate": 0.07,
        "达人佣金": 0.20,
        "海外仓尾程": 5.00,
        "ac_acos": 0.20,
        "return_rate": 0.04,
        "currency_symbol": "$",
    },
    "shopify": {
        "commission_rate": 0.029,   # Stripe
        "paypal": 0.044,
        "物流小包": 20.00,          # 美元/单
        "ac_acos": 0.30,
        "return_rate": 0.02,
        "currency_symbol": "$",
    },
    "temu": {
        "commission_rate": 0.15,
        "推广费": 0.10,
        "退货率": 0.08,
        "currency_symbol": "$",
    },
}

def parse_args():
    p = argparse.ArgumentParser(description="1688 利润计算器")
    p.add_argument("cost_price", type=float, help="1688 进货价（元）")
    p.add_argument("sell_price", type=float, help="平台售价")
    p.add_argument("--currency", default="USD", choices=["USD","EUR","GBP","CNY"], help="目标市场货币")
    p.add_argument("--platform", default="amazon", choices=list(PLATFORM_PARAMS.keys()), help="销售平台")
    p.add_argument("--weight-g", type=float, default=500.0, help="单件克重（g）")
    p.add_argument("--freight-kg", type=float, default=5.0, help="头程每kg单价（美元）")
    p.add_argument("--units", type=int, default=100, help="首批数量")
    p.add_argument("--exchange-rate", type=float, default=7.20, help="汇率 USD/CNY")
    p.add_argument("--exchange-eur", type=float, default=7.80, help="汇率 EUR/CNY")
    p.add_argument("--exchange-gbp", type=float, default=9.10, help="汇率 GBP/CNY")
    p.add_argument("--custom-platform", type=str, default="", help="自定义平台参数 JSON")
    return p.parse_args()


def get_exchange_rate(currency: str, args) -> float:
    rates = {"USD": args.exchange_rate, "EUR": args.exchange_eur, "GBP": args.exchange_gbp, "CNY": 1.0}
    return rates.get(currency, args.exchange_rate)


def calc_profit(args) -> dict:
    platform = args.platform
    p = PLATFORM_PARAMS.get(platform, {})

    # 货币 & 汇率
    currency_sym = p.get("currency_symbol", "$")
    rate = get_exchange_rate(args.currency, args)

    # 售价（转为人民币）
    sell_price_cny = args.sell_price * rate

    # 成本项
    cost = args.cost_price  # 进货价（元）
    weight_kg = args.weight_g / 1000  # 转为kg
    freight_unit = weight_kg * args.freight_kg  # 每件头程（美元→转元）
    freight_unit_cny = freight_unit * rate

    # 平台佣金
    comm = sell_price_cny * p.get("commission_rate", 0)

    # 广告推广
    ad_cost = sell_price_cny * p.get("ac_acos", 0)

    # 尾程（FBA / 海外仓）
    tail_cost = p.get("fba_per_unit", 0) * rate
    if platform == "tiktok":
        tail_cost = p.get("海外仓尾程", 5.0) * rate
    elif platform == "shopify":
        tail_cost = p.get("物流小包", 20.0) * rate

    # 退货损耗
    return_rate = p.get("return_rate", 0.03)
    return_loss = sell_price_cny * return_rate

    # 汇损（1%）
    ex_loss = sell_price_cny * 0.01

    # Temu 特殊处理
    if platform == "temu":
        prom_fee = sell_price_cny * p.get("推广费", 0)
        total_cost = cost + freight_unit_cny + comm + prom_fee + return_loss * sell_price_cny + ex_loss
    else:
        total_cost = cost + freight_unit_cny + comm + ad_cost + tail_cost + return_loss + ex_loss

    # TikTok 达人佣金
    if platform == "tiktok":
        comm += sell_price_cny * p.get("达人佣金", 0.20)
        total_cost = cost + freight_unit_cny + comm + ad_cost + tail_cost + return_loss + ex_loss

    # 利润
    gross_profit = sell_price_cny - total_cost
    gross_margin = gross_profit / sell_price_cny * 100 if sell_price_cny else 0
    unit_profit = gross_profit
    total_profit = gross_profit * args.units

    # Shopify + PayPal 额外
    if platform == "shopify":
        pp_fee = sell_price_cny * p.get("paypal", 0)
        total_cost += pp_fee
        gross_profit = sell_price_cny - total_cost
        gross_margin = gross_profit / sell_price_cny * 100 if sell_price_cny else 0
        unit_profit = gross_profit
        total_profit = gross_profit * args.units

    return {
        "success": True,
        "platform": platform,
        "currency": args.currency,
        "exchange_rate": rate,
        "params": {
            "进货价_元": cost,
            "单件克重_g": args.weight_g,
            "头程单价美元_kg": args.freight_kg,
            "首批数量": args.units,
            "平台佣金": f"{p.get('commission_rate',0)*100:.0f}%",
            "ACOS": f"{p.get('ac_acos',0)*100:.0f}%",
            "退货率": f"{p.get('return_rate',0)*100:.0f}%",
        },
        "cost_breakdown_cny": {
            "进货价": round(cost, 2),
            "头程运费": round(freight_unit_cny, 2),
            "平台佣金": round(comm, 2),
            "推广费": round(ad_cost, 2),
            "尾程配送": round(tail_cost, 2),
            "退货损耗": round(return_loss, 2),
            "汇损": round(ex_loss, 2),
            "合计成本": round(total_cost, 2),
        },
        "sell_price_cny": round(sell_price_cny, 2),
        "unit_profit_cny": round(unit_profit, 2),
        "unit_margin_pct": round(gross_margin, 2),
        "total_profit_cny": round(total_profit, 2),
        "total_cost_cny": round(total_cost * args.units, 2),
        "summary": {
            "单品毛利": f"{gross_margin:.2f}%",
            "单品利润": f"¥{unit_profit:.2f}",
            f"首批{args.units}个总利润": f"¥{total_profit:.2f}",
        },
        "health_check": _health_check(gross_margin, platform),
    }


def _health_check(margin: float, platform: str) -> dict:
    thresholds = {"amazon": 25, "tiktok": 30, "shopify": 40, "temu": 10}
    healthy = margin >= thresholds.get(platform, 25)
    return {
        "healthy": healthy,
        "status": "✅ 健康" if healthy else "⚠️ 偏低",
        "建议": "毛利需达到 "
            + f"{thresholds.get(platform, 25)}%+ "
            + ("（控广告费或换更低价货源）" if not healthy else "（表现良好）")
    }


if __name__ == "__main__":
    args = parse_args()
    result = calc_profit(args)
    print(json.dumps(result, ensure_ascii=False, indent=2))


path: references/report-template.md
content: # 供应链落地报告模板

## 使用说明

每次完成"选品→找商→询盘→利润核算"全流程后，输出此报告。

---

```
═══════════════════════════════════════════════════════════════
                 1688 供应链落地清单
                 生成时间：YYYY-MM-DD HH:MM
═══════════════════════════════════════════════════════════════

📌 一、选品结论

产品：[产品名称]
切入角度：[差异化卖点/改良点]

【当前趋势】
- 热点平台：[TikTok / Amazon / Temu / 独立站]
- 热度数据：[销量增速 / BSR排名 / 达人带货数]
- 材质/工艺趋势：[分析]
- 1688 同类爆款进价区间：¥XX - ¥XX
- 竞品平台售价参考：$XX / £XX
- 预计可切入价格带：¥XX-¥XX

【产品基因】
- 材质：[如：ABS+金属]
- 克重：[X]kg/个
- 尺寸：[长×宽×高]cm
- 目标认证：[CE / FCC / FDA / 无需认证]
- ⚠️ 风险提示：[专利风险 / 认证缺失 / 物流限制]

═══════════════════════════════════════════════════════════════

📌 二、供应商对比

推荐供应商数量：X 家

┌──────────────────────────────────────────────────────────────┐
│ 🏭 工厂 A：[工厂名称]                                        │
│    标签：实力工厂 | 超级工厂 | 跨境专供                      │
├──────────────────────────────────────────────────────────────┤
│ 所在地：[省] [市]                                            │
│ 诚信通年限：[X] 年                                           │
│ 近90天成交额：¥[XX万]                                        │
│ 复购率：[XX]%                                                │
│ 发货时效：48小时内                                            │
│ 跨境证书：CE / FCC / FDA / RoHS / BSCI / 无                │
│ 擅长工艺：[注塑 / CNC / 阳极氧化 / ...]                     │
│ MOQ：[XXX]个                                                │
│ 参考报价：¥[XX] / 个（[XX]个以上）                          │
│ 定制能力：支持Logo丝印 / 彩盒定制 / 支持OEM                 │
│ 样品支持：✅ 样品费¥XX，可抵扣货款 / ❌ 不支持                │
│ 一件代发：✅ 支持 / ❌ 不支持                                 │
│ 优势：[简评，1-2句]                                         │
│ ⚠️ 风险：[如有]                                              │
│ 推荐指数：⭐⭐⭐⭐⭐                                           │
└──────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────┐
│ 🏭 工厂 B：[工厂名称]                                        │
│ ... 同上格式 ...                                             │
└──────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────┐
│ 🏭 工厂 C：[工厂名称]（备选）                                │
│ ... 同上格式 ...                                             │
└──────────────────────────────────────────────────────────────┘

【综合对比表】
| 维度           | 工厂A | 工厂B | 工厂C |
|----------------|-------|-------|-------|
| 诚信通年限      | X年   | X年   | X年   |
| 近90天成交额    | ¥XX万 | ¥XX万 | ¥XX万 |
| 复购率         | XX%   | XX%   | XX%   |
| 跨境认证        | 齐全  | 部分  | 无    |
| MOQ            | XXX个 | XXX个 | XXX个 |
| 参考单价        | ¥XX   | ¥XX   | ¥XX   |
| 发货时效        | 48h   | 72h   | 48h   |
| 综合推荐        | ⭐⭐⭐⭐⭐ | ⭐⭐⭐  | ⭐⭐   |

═══════════════════════════════════════════════════════════════

📌 三、询盘话术

【首选联系：工厂A — 理由：认证齐全 / 价格最优 / 离得近】

💬 方案一：拿样测试（推荐）
> [填写询盘话术内容，参考 inquiry.md]

💬 方案二：价格谈判（备选）
> [填写询盘话术内容]

💬 方案三：OEM定制（如有定制需求）
> [填写询盘话术内容]

═══════════════════════════════════════════════════════════════

📌 四、利润分析

【基础参数】
- 进货价：¥XX / 个
- 目标平台：[Amazon FBA / TikTok Shop / 独立站]
- 目标售价：$XX（汇率 ¥7.2，折合 ¥XX）
- 首批计划数量：XXX 个

【成本拆解】
| 成本项 | 单件 | 合计（XXX个） |
|--------|------|---------------|
| 进货价 | ¥XX  | ¥XXXX        |
| 头程运费 | ¥XX | ¥XXXX        |
| 平台佣金 | ¥XX | ¥XXXX        |
| 推广费 | ¥XX | ¥XXXX        |
| 尾程配送 | ¥XX | ¥XXXX        |
| 损耗预留 | ¥XX | ¥XXXX        |
| 汇损预留 | ¥XX | ¥XXXX        |
| **合计** | **¥XX** | **¥XXXX** |

【利润测算】
- 折合人民币售价：¥XX
- 合计成本：¥XX
- 单品毛利：XX.XX%
- 单品利润：¥XX.XX
- 首批 XXX 个预期总利润：¥XXXX

═══════════════════════════════════════════════════════════════

📌 五、执行计划

【谈判策略】
1. 优先联系：[工厂名]，理由：[简洁说明]
2. 谈判切入点：[用什么筹码压价/换条件]
3. 底线条件：[最低接受的价格/MOQ/交期]

【时间表】
- 本周内：联系供应商，索要样品
- 第X周：样品到手，测试质量 & 对比
- 第X周：确定合作工厂，谈判正式订单条款
- 第X周：下首批订单，跟进生产 & 发货
- 第X周：收到货，上架测试 / 开始TikTok直播

【关键待确认事项】
- [ ] 认证是否齐全（目标市场必需）
- [ ] APP是否支持[目标市场]语言
- [ ] 专利自查（有无侵权风险）
- [ ] 物流限制（电池/液体/粉末等）
- [ ] 最小起订量能否协商
- [ ] 首批能否按更低价结算

═══════════════════════════════════════════════════════════════

⚠️ 风险提示（必读）

[根据产品特性，填写以下相关风险：]

- 🔒 专利风险：[有/无/需自查] — [说明]
- 🏭 认证风险：[CE/FCC/FDA是否齐全] — [说明]
- 📦 物流风险：[电池/液体/特殊材质等] — [说明]
- 💰 汇率风险：建议预留3-5%缓冲，锁汇降低波动
- 🛡️ 资金安全：建议通过平台担保交易，勿私下转账

═══════════════════════════════════════════════════════════════

报告生成完毕。如需调整，请提供补充信息。
═══════════════════════════════════════════════════════════════
```

---

## 快速简版报告（轻量场景用）

当用户需求较简单（如只需找商或只需询盘话术），可输出简化版：

```
【1688 供应链速览】

📦 产品：[名称]
🎯 切入角度：[一句话]

🏭 推荐工厂（Top 1）
- [工厂名] ⭐⭐⭐⭐⭐
- 价格区间：¥XX-¥XX / 个
- MOQ：XXX个
- 认证：[齐全/需确认]
- 优势：[一句话]

💬 推荐话术（拿样）：
> [3-5句话精简版话术]

💰 利润参考：
- 进价：¥XX → 售价：$XX（毛利约XX%）

⚠️ 必确认：[认证/专利/交期]
```


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)