joy-cli/scripts/manhuang/probe-ai-account-quotes.py
feibisi 71caf4af96 feat(manhuang): 采购雷达 — 货架事件、商家信任与噪声标
SKU 通用流程:history 差分产出 shelf_events(价/有货,unknown≠售罄),
shops trust 分进 tab-caches/黑市面板;礼品卡面值假差标 noise;
ensure-shops --record-outcome 回填我方履约。设计见 manhuang-procurement-radar.md。
2026-07-13 13:57:46 +08:00

1367 lines
61 KiB
Python
Executable file
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""定点探针AI 账号/订阅 + 相邻额度 + 注册基建 比价盘P0/P1/P2
策略(非 RSS
- 每 SKU 最多 1 个 GGSel 搜索探针(控时)
- 官方 MSRP 作对照(无灰市命中时展示锚价)
- SuperGrok 保留类目页 + Plati 已知 item
写入:~/.cache/manhuang/quotes/ai-accounts.json
"""
from __future__ import annotations
import json
import re
import time
import urllib.error
import urllib.parse
import urllib.request
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
UA = "joy-manhuang-quote-probe/1.3 (+local-monitor; scheduled-cache)"
CACHE_DIR = Path.home() / ".cache" / "manhuang" / "quotes"
OUT = CACHE_DIR / "ai-accounts.json"
COMMUNITY = CACHE_DIR / "community-prices.json"
HISTORY = CACHE_DIR / "history"
TTL_S = 3 * 3600 # 非 --force 时,缓存未过期则直接复用
SLEEP_S = 0.25
FETCH_TIMEOUT = 16
def _ggsel(query: str, title_filter: str, channel: str = "GGSel") -> dict[str, Any]:
q = urllib.parse.quote(query)
return {
"channel_id": "ggsel",
"channel": channel,
"kind": "marketplace",
"url": f"https://ggsel.net/en/catalog?query={q}",
"parser": "ggsel_catalog",
"title_filter": title_filter,
}
def _ggsel_path(path: str, title_filter: str, channel: str = "GGSel") -> dict[str, Any]:
"""固定类目路径(比 search query 更稳。path 如 chatgpt / claude-ai / cursor。"""
path = path.strip("/")
return {
"channel_id": "ggsel",
"channel": channel,
"kind": "marketplace",
"url": f"https://ggsel.net/en/catalog/{path}",
"parser": "ggsel_catalog",
"title_filter": title_filter,
}
def _funpay_lot(lot_id: str, channel: str = "FunPay") -> dict[str, Any]:
"""FunPay 分区 lot 页(如 Claude 3173 / ChatGPT 3559"""
lid = str(lot_id).strip("/")
return {
"channel_id": "funpay",
"channel": channel,
"kind": "marketplace",
"url": f"https://funpay.com/en/lots/{lid}/",
"parser": "funpay_lots",
"lot_id": lid,
}
def _sku(
sid: str,
label: str,
category: str,
*,
msrp: float | None,
msrp_note: str,
msrp_url: str,
unit: str = "mo",
query: str | None = None,
title_filter: str | None = None,
probes: list[dict[str, Any]] | None = None,
monitor: str = "",
decide: str = "",
tier: str = "p0",
) -> dict[str, Any]:
official = None
if msrp is not None:
official = {
"price": float(msrp),
"currency": "USD",
"unit": unit,
"note": msrp_note,
"url": msrp_url,
}
prs = list(probes or [])
if query and title_filter and not prs:
prs = [_ggsel(query, title_filter)]
elif query and title_filter:
# keep explicit probes; also ensure at least one ggsel if none marketplace
if not any(p.get("kind") == "marketplace" for p in prs):
prs.append(_ggsel(query, title_filter))
return {
"id": sid,
"label": label,
"category": category,
"tier": tier,
"official_msrp": official,
"probes": prs,
"monitor": monitor or f"{label} 灰市最低 vs 官方锚",
"decide": decide or "能自开不买号;灰市需验货",
}
# ── P0 AI 服务/账号 ──────────────────────────────────────────
# ── P1 AI 相邻额度/算力 ──────────────────────────────────────
# ── P2 注册基建 ──────────────────────────────────────────────
SKUS: list[dict[str, Any]] = [
# —— 已有核心 ——
{
"id": "supergrok",
"label": "SuperGrok 账号/订阅",
"category": "account",
"tier": "p0",
"official_msrp": {
"price": 30.0, "currency": "USD", "unit": "mo",
"note": "xAI SuperGrok 档常见标价", "url": "https://x.ai/grok",
},
"probes": [
{"channel_id": "ggsel", "channel": "GGSel", "kind": "marketplace",
"url": "https://ggsel.net/en/catalog/grok-accounts", "parser": "ggsel_catalog"},
{"channel_id": "ggsel", "channel": "GGSel Grok", "kind": "marketplace",
"url": "https://ggsel.net/en/catalog/grok", "parser": "ggsel_catalog"},
{"channel_id": "plati-market", "channel": "Plati.Market", "kind": "marketplace",
"url": "https://plati.market/itm/ai-grok-super-grok-account-subscription-1-3-6-12-months/5816980?lang=en-US",
"parser": "plati_item"},
_funpay_lot("3175"), # Grok Subscription
_funpay_lot("3174"), # Grok Accounts
],
"monitor": "GGSel/Plati/FunPay SuperGrok vs 官方月费",
"decide": "灰市号风险高;紧急才买",
},
_sku("openai", "OpenAI / ChatGPT Plus", "account", tier="p0",
msrp=20.0, msrp_note="ChatGPT Plus $20/mo", msrp_url="https://openai.com/chatgpt/pricing/",
probes=[
_ggsel("chatgpt plus", r"chatgpt|gpt.?plus|openai.?plus|chat.?gpt"),
_ggsel_path("chatgpt", r"chatgpt|gpt|openai|plus"),
_ggsel("chatgpt account", r"chatgpt|openai|gpt"),
_funpay_lot("3559"), # ChatGPT Subscription
_funpay_lot("1355"), # ChatGPT Accounts
],
decide="共享号易封;能自开 Plus 不买号"),
_sku("chatgpt_team", "ChatGPT Team / Business", "account", tier="p0",
msrp=25.0, msrp_note="Team 约 $2530/席/月", msrp_url="https://openai.com/chatgpt/team/",
probes=[
_ggsel("chatgpt team", r"chatgpt|team|business|workspace"),
_ggsel("chatgpt business", r"chatgpt|business|team"),
],
decide="席位拆卖多假;企业自开"),
_sku("openai_api", "OpenAI API 额度/Key", "api_credit", tier="p0",
msrp=None, msrp_note="按量计费(对照用无固定月费)", msrp_url="https://openai.com/api/pricing/",
unit="credit",
probes=[
_ggsel("openai api key", r"openai|api.?key|gpt.?key|chatgpt.?api"),
_ggsel("chatgpt api", r"api|key|token|credit|gpt"),
],
decide="卡密/共享 key 风险极高;优先官方充值"),
_sku("claude", "Claude Pro / Max", "account", tier="p0",
msrp=20.0, msrp_note="Claude Pro $20/mo", msrp_url="https://claude.ai/pricing",
probes=[
_ggsel("claude pro", r"claude|anthropic"),
_ggsel_path("claude", r"claude|anthropic|pro|max"),
_ggsel("claude ai", r"claude|anthropic"),
_funpay_lot("3173"), # Claude Subscription
_funpay_lot("3172"), # Claude Accounts
],
decide="优先官方 Pro"),
_sku("claude_team", "Claude Team", "account", tier="p0",
msrp=30.0, msrp_note="Team 席位量级对照", msrp_url="https://claude.ai/pricing",
query="claude team", title_filter=r"claude|team|anthropic|workspace",
decide="团队席自开;灰市共享勿用生产"),
_sku("claude_api", "Claude / Anthropic API", "api_credit", tier="p0",
msrp=None, msrp_note="按量", msrp_url="https://www.anthropic.com/pricing",
unit="credit", query="claude api key", title_filter=r"claude|anthropic|api\s*key",
decide="官方 Console 充值优先"),
_sku("gemini", "Gemini Advanced / AI Pro", "account", tier="p0",
msrp=19.99, msrp_note="Google AI Pro ≈ $19.99/mo", msrp_url="https://gemini.google/subscriptions/",
probes=[
_ggsel("gemini advanced", r"gemini|google.?ai|advanced|ai.?pro"),
_ggsel("google one ai", r"gemini|google|ai.?pro|one"),
],
decide="常绑 Google One注意区服"),
_sku("gemini_api", "Gemini API / AI Studio", "api_credit", tier="p0",
msrp=None, msrp_note="免费档+按量", msrp_url="https://ai.google.dev/pricing",
unit="credit", query="gemini api key", title_filter=r"gemini|google\s*ai|studio|api",
decide="免费档够用则不买;付费走官方"),
_sku("cursor", "Cursor Pro", "subscription", tier="p0",
msrp=20.0, msrp_note="Cursor Pro $20/mo", msrp_url="https://cursor.com/pricing",
probes=[
_ggsel("cursor pro", r"cursor"),
_ggsel_path("cursor", r"cursor|pro|ide"),
_ggsel("cursor ai", r"cursor"),
],
decide="编码刚需;灰市共享易封号"),
_sku("cursor_biz", "Cursor Business", "subscription", tier="p0",
msrp=40.0, msrp_note="Business 档量级对照", msrp_url="https://cursor.com/pricing",
query="cursor business", title_filter=r"cursor|business|team",
decide="团队走官方 Business"),
_sku("copilot", "GitHub Copilot Pro", "subscription", tier="p0",
msrp=10.0, msrp_note="Copilot Pro $10/mo · Business $19", msrp_url="https://github.com/features/copilot#pricing",
query="github copilot", title_filter=r"copilot|github",
decide="教育/开源免费资格先查;否则官方"),
_sku("windsurf", "Windsurf / Cascade", "subscription", tier="p0",
msrp=15.0, msrp_note="订阅档量级对照", msrp_url="https://windsurf.com/pricing",
query="windsurf", title_filter=r"windsurf|cascade|codeium",
decide="与 Cursor 比性价比后自开"),
_sku("perplexity", "Perplexity Pro", "subscription", tier="p0",
msrp=20.0, msrp_note="Pro $20/mo", msrp_url="https://www.perplexity.ai/pro",
query="perplexity pro", title_filter=r"perplexity|pro",
decide="检索刚需可自开"),
_sku("midjourney", "Midjourney", "subscription", tier="p0",
msrp=10.0, msrp_note="Basic $10 起", msrp_url="https://www.midjourney.com/account",
query="midjourney", title_filter=r"midjourney|mj\b",
decide="共享号多;订阅自开更稳"),
_sku("runway", "Runway", "subscription", tier="p0",
msrp=15.0, msrp_note="Standard 档量级", msrp_url="https://runwayml.com/pricing",
query="runway ml", title_filter=r"runway",
decide="视频生成贵;按项目自开"),
_sku("kling", "Kling / 可灵", "subscription", tier="p0",
msrp=10.0, msrp_note="国内档位对照(美元约)", msrp_url="https://klingai.com/",
query="kling ai", title_filter=r"kling|可灵|kuaishou",
decide="区服与支付方式影响价"),
_sku("xai_api", "xAI / Grok API 额度", "api_credit", tier="p0",
msrp=None, msrp_note="按量", msrp_url="https://x.ai/api",
unit="credit", query="grok api", title_filter=r"grok|xai|x\.ai|api",
decide="与 SuperGrok 账号拆开监控"),
_sku("openrouter", "OpenRouter 余额", "api_credit", tier="p0",
msrp=None, msrp_note="预充值按量", msrp_url="https://openrouter.ai/credits",
unit="credit",
probes=[
_ggsel("openrouter", r"open.?router"),
# community overlay 会补 token 指数价
],
decide="统一多模型采购优选官方充值;价看 GitHub 周更表"),
_sku("poe", "Poe 订阅", "subscription", tier="p0",
msrp=19.99, msrp_note="Poe 订阅档", msrp_url="https://poe.com/",
query="poe subscription", title_filter=r"\bpoe\b|quora",
decide="多模型入口;灰市点券慎买"),
_sku("notion_ai", "Notion AI", "subscription", tier="p0",
msrp=10.0, msrp_note="Notion AI add-on 量级", msrp_url="https://www.notion.so/pricing",
query="notion ai", title_filter=r"notion",
decide="绑 Workspace自开"),
_sku("ms_copilot", "Microsoft Copilot 365", "subscription", tier="p0",
msrp=30.0, msrp_note="Copilot for M365 量级/用户", msrp_url="https://www.microsoft.com/microsoft-365/copilot",
query="microsoft copilot", title_filter=r"copilot|microsoft\s*365|office",
decide="企业协议走官方"),
_sku("v0", "v0 / Lovable / Bolt 类", "subscription", tier="p0",
msrp=20.0, msrp_note="生成式前端订阅量级", msrp_url="https://v0.dev/pricing",
query="v0 dev", title_filter=r"\bv0\b|lovable|bolt\.new|boltai",
decide="按项目用量选官方"),
_sku("clinepass", "ClinePass", "subscription", tier="p0",
msrp=9.99, msrp_note="$9.99/mo 开源权重额度", msrp_url="https://cline.bot/cline-pass",
probes=[{"channel_id": "official-msrp", "channel": "官方 ClinePass", "kind": "msrp",
"url": "https://cline.bot/cline-pass", "parser": "official_clinepass"},
_ggsel("clinepass", r"cline\s*pass|clinepass|cline")],
decide="默认官方"),
_sku("opencode_go", "OpenCode Go", "subscription", tier="p0",
msrp=10.0, msrp_note="首月 $5 → $10/mo集群主采 PlatiGoogle 号+卖家代付 Stripe",
msrp_url="https://opencode.ai/go",
probes=[
{"channel_id": "official-msrp", "channel": "官方 Go", "kind": "msrp",
"url": "https://opencode.ai/go", "parser": "official_go"},
# 自动比价GGSel人工主源 Platiparser 仅 plati_item 需具体商品 URL搜索页另议
_ggsel("opencode go", r"opencode|go\s*\$|open.?code"),
],
decide="主采 Plati号+代付)→ prep/post-pay → opencode-ccGGSel 仅交叉价;见 opencode-go-fleet-runbook §10"),
_sku("ollama_cloud", "Ollama Cloud Pro", "subscription", tier="p0",
msrp=20.0, msrp_note="Pro $20 · Max $100", msrp_url="https://ollama.com/pricing",
probes=[{"channel_id": "official-msrp", "channel": "官方 Pricing", "kind": "msrp",
"url": "https://ollama.com/pricing", "parser": "official_ollama"},
_ggsel("ollama cloud", r"ollama")],
decide="Pro 自开更稳"),
# —— P1 AI 相邻 ——
_sku("newapi_relay", "中转站/NewAPI 额度", "relay", tier="p1",
msrp=None, msrp_note="各站充值无统一 MSRPtoken 指数见 community", msrp_url="",
unit="credit",
probes=[
_ggsel("chatgpt api", r"api|gpt|claude|中转|newapi|oneapi|key|token"),
_ggsel("claude api", r"claude|api|key|anthropic"),
],
decide="看延迟/纯血/风控;小额试充;价表见 awesome-ai-api-proxy"),
_sku("groq", "Groq 额度", "api_credit", tier="p1",
msrp=None, msrp_note="按量", msrp_url="https://groq.com/pricing/",
unit="credit", query="groq api", title_filter=r"groq",
decide="官方免费档+付费"),
_sku("together", "Together / Fireworks", "api_credit", tier="p1",
msrp=None, msrp_note="按量", msrp_url="https://www.together.ai/pricing",
unit="credit", query="together ai", title_filter=r"together|fireworks",
decide="开源权重托管走官方"),
_sku("hf_pro", "HuggingFace Pro", "subscription", tier="p1",
msrp=9.0, msrp_note="Pro $9/mo 量级", msrp_url="https://huggingface.co/pricing",
query="huggingface pro", title_filter=r"hugging\s*face|hf\s*pro",
decide="需要私有/加速再开"),
_sku("replicate", "Replicate / fal.ai", "api_credit", tier="p1",
msrp=None, msrp_note="按量", msrp_url="https://replicate.com/pricing",
unit="credit", query="replicate api", title_filter=r"replicate|fal\.ai|falai",
decide="生图视频 API 官方充值"),
_sku("elevenlabs", "ElevenLabs", "subscription", tier="p1",
msrp=5.0, msrp_note="Starter 档起", msrp_url="https://elevenlabs.io/pricing",
query="elevenlabs", title_filter=r"eleven|11labs|elevenlabs",
decide="语音合成;共享号风险"),
_sku("suno", "Suno / Udio", "subscription", tier="p1",
msrp=10.0, msrp_note="Pro 档量级", msrp_url="https://suno.com/",
query="suno ai", title_filter=r"suno|udio",
decide="音乐生成订阅"),
_sku("firefly", "Adobe Firefly / Canva AI", "subscription", tier="p1",
msrp=10.0, msrp_note="捆绑 Creative/Canva 套餐", msrp_url="https://www.adobe.com/products/firefly.html",
query="adobe firefly", title_filter=r"firefly|adobe|canva",
decide="设计侧捆绑账号"),
_sku("gpu_cloud", "GPU 云时 (RunPod/Vast/AutoDL)", "compute", tier="p1",
msrp=0.4, msrp_note="≈$0.20.8/h 消费卡量级(对照)", msrp_url="https://www.runpod.io/pricing",
unit="hour", query="runpod", title_filter=r"runpod|vast\.ai|autodl|gpu",
decide="比小时价+网络;勿买来路不明面板"),
# —— P2 注册基建 ——
_sku("sms_otp", "接码 OTP (5sim 类)", "infra", tier="p2",
msrp=0.15, msrp_note="≈$0.050.5/条 国别差异大", msrp_url="https://5sim.net/",
unit="sms", query="sms receive", title_filter=r"sms|otp|接码|virtual\s*number|5sim",
decide="按国别比单价;拒 exit-scam 平台"),
_sku("bulk_mail", "成品邮箱/老号", "infra", tier="p2",
msrp=0.5, msrp_note="≈$0.12/个 视平台", msrp_url="",
unit="account", query="gmail account", title_filter=r"gmail|outlook|email|mail\s*account|hotmail",
decide="注册前置;优先自产注册机"),
_sku("residential_proxy", "住宅代理", "infra", tier="p2",
msrp=5.0, msrp_note="≈$/GB 或月套餐量级", msrp_url="",
unit="gb", query="residential proxy", title_filter=r"residential|proxy|socks|isp",
decide="看出口纯净度;小额试"),
_sku("captcha_solve", "打码 (2Captcha 类)", "infra", tier="p2",
msrp=1.0, msrp_note="≈$/千次量级", msrp_url="https://2captcha.com/",
unit="1k", query="2captcha", title_filter=r"captcha|2captcha|anticaptcha|rucaptcha",
decide="注册基建成本;官方 API"),
_sku("antidetect", "指纹浏览器席位", "infra", tier="p2",
msrp=10.0, msrp_note="≈$530/席/月", msrp_url="",
unit="mo", query="antidetect browser", title_filter=r"antidetect|gologin|multilogin|ads\s*power|dolphin",
decide="按席位月费;能少开少开"),
_sku("giftcard_us", "美区 App Store / iTunes 卡", "infra", tier="p2",
msrp=100.0, msrp_note="面值 $100 对照折扣率", msrp_url="",
unit="face", query="itunes gift card usa",
# 必须 gift card + 美区/USD排除 TR/账号
title_filter=r"(gift\s*card|iTunes).{0,40}(USA|US\b|USD)|Apple iTunes Gift Card.{0,20}USD",
decide="开 Plus/Claude 支付;看折扣率(条目价≠面值时看标题面额)"),
# —— 包 A支付开号高杠杆——
_sku("giftcard_tr", "土区 Apple / Google 礼品卡", "pay", tier="pay",
msrp=100.0, msrp_note="面值对照(常用于低价开 AI", msrp_url="",
unit="face", query="turkey itunes gift",
title_filter=r"(gift\s*card|iTunes|Google\s*Play).{0,40}(Turkey|Türkiye|TRY|TL\b)|Turkey.{0,20}(gift|iTunes)",
decide="区服开订阅常用;算折扣+封区风险"),
_sku("giftcard_hk", "港区 Apple / Google 礼品卡", "pay", tier="pay",
msrp=100.0, msrp_note="面值对照", msrp_url="",
unit="face", query="hong kong itunes gift card",
title_filter=r"(gift\s*card|iTunes|Google\s*Play).{0,40}(Hong\s*Kong|HK\b)|Hong\s*Kong.{0,20}(gift|iTunes)",
decide="港区支付通道;对照美区折扣"),
_sku("virtual_card", "虚拟卡开卡/充值损耗", "pay", tier="pay",
msrp=5.0, msrp_note="开卡费+充值损耗量级 $/次或月", msrp_url="",
unit="mo",
probes=[
_ggsel("virtual debit card", r"virtual\s*(debit|credit)?\s*card|prepaid\s*card|virtual\s*visa"),
_ggsel("prepaid visa card", r"prepaid|virtual\s*visa|virtual\s*mastercard|debit\s*card"),
],
decide="决定自开是否划算;监控费率勿囤来路不明卡"),
_sku("sms_us", "接码 · 美国号", "pay", tier="pay",
msrp=0.3, msrp_note="≈$/条 US", msrp_url="https://5sim.net/",
unit="sms", query="usa sms receive", title_filter=r"usa|us\b|united\s*states|sms|otp",
decide="OpenAI/Claude 注册常用;比成功率"),
_sku("sms_nl", "接码 · 荷兰号", "pay", tier="pay",
msrp=0.25, msrp_note="≈$/条 NL", msrp_url="https://5sim.net/",
unit="sms", query="netherlands sms", title_filter=r"netherlands|nl\b|dutch|sms",
decide="欧区注册备选"),
_sku("sms_id", "接码 · 印尼号", "pay", tier="pay",
msrp=0.08, msrp_note="≈$/条 ID 便宜但拒收多", msrp_url="https://5sim.net/",
unit="sms", query="indonesia sms", title_filter=r"indonesia|id\b|sms",
decide="便宜≠能过;小额试成功率"),
_sku("edu_perk", "教育认证 / 学生通道", "pay", tier="pay",
msrp=0.0, msrp_note="官方学生免费/折扣Copilot/Cursor 等)", msrp_url="https://education.github.com/",
unit="mo", query="student email edu", title_filter=r"edu|student|university|教育",
decide="有资格走官方教育;灰市 .edu 高风险"),
# —— 包 B调用/算力专线 ——
_sku("codex_quota", "Codex / ChatGPT 编程额度", "api_credit", tier="call",
msrp=20.0, msrp_note="与 Plus 拆开的编程额度对照", msrp_url="https://openai.com/chatgpt/pricing/",
unit="mo",
probes=[
_ggsel("chatgpt codex", r"codex|chatgpt|o1|o3|programming|plus"),
_ggsel("chatgpt plus", r"chatgpt|plus|openai|gpt"),
_ggsel_path("chatgpt", r"chatgpt|codex|plus|openai"),
_funpay_lot("3559"),
_funpay_lot("1356"), # Other / API 向
],
decide="号池 dead 时优先看此项;共享号易封"),
_sku("chatgpt_pro", "ChatGPT Pro 高档", "account", tier="call",
msrp=200.0, msrp_note="Pro 高档量级对照(若在售)", msrp_url="https://openai.com/chatgpt/pricing/",
unit="mo",
probes=[
_ggsel("chatgpt pro", r"chatgpt.?pro|gpt.?pro|pro.?200|\$200"),
_ggsel("chatgpt plus", r"chatgpt|plus|pro|openai"),
_funpay_lot("3559"),
],
decide="重度才考虑;灰市高仿多"),
_sku("claude_max", "Claude Max 高档", "account", tier="call",
msrp=200.0, msrp_note="Max 高档量级对照", msrp_url="https://claude.ai/pricing",
unit="mo",
probes=[
_ggsel("claude max", r"claude.?max|anthropic.?max|claude"),
_ggsel_path("claude", r"claude|max|pro|anthropic"),
_funpay_lot("3173"),
],
decide="编码重度;优先官方"),
_sku("supergrok_heavy", "SuperGrok Heavy", "account", tier="call",
msrp=300.0, msrp_note="SuperGrok Heavy 高档量级对照", msrp_url="https://x.ai/pricing",
unit="mo", query="super grok heavy", title_filter=r"super\s*grok\s*heavy|grok\s*heavy|heavy",
decide="最高档锚;灰市假多,优先官方"),
_sku("google_ai_ultra", "Google AI Ultra", "account", tier="call",
msrp=249.99, msrp_note="Google AI Ultra 高档量级对照", msrp_url="https://one.google.com/ai",
unit="mo", query="google ai ultra", title_filter=r"google\s*ai\s*ultra|ai\s*ultra|gemini\s*ultra",
decide="Google One AI Ultra 锚;区服价差大"),
_sku("deepseek", "DeepSeek 官方/渠道额度", "api_credit", tier="call",
msrp=None, msrp_note="按量 RMB/USD", msrp_url="https://platform.deepseek.com/",
unit="credit", query="deepseek api", title_filter=r"deepseek|深度求索",
decide="国内主路径;官方充值优先"),
_sku("siliconflow", "硅基流动额度", "api_credit", tier="call",
msrp=None, msrp_note="按量充值", msrp_url="https://siliconflow.cn/",
unit="credit", query="siliconflow", title_filter=r"silicon|硅基|siliconflow",
decide="开源权重托管;官方/代理差价"),
_sku("zhipu", "智谱 GLM 额度", "api_credit", tier="call",
msrp=None, msrp_note="按量", msrp_url="https://open.bigmodel.cn/",
unit="credit",
probes=[
_ggsel("zhipu glm api", r"zhipu|智谱|glm|bigmodel"),
_funpay_lot("4400"), # GLM Accounts
],
decide="国内 API发票场景走官方"),
_sku("relay_premium", "中转站·高并发/纯血档", "relay", tier="call",
msrp=None, msrp_note="各站无统一价token 指数见 community-prices", msrp_url="",
unit="credit",
probes=[
_ggsel("chatgpt api premium", r"api|gpt|claude|premium|pure|中转|key"),
_ggsel("openai api", r"openai|api|key|gpt"),
],
decide="小额试延迟/风控;与 OpenRouter + awesome 价表对照"),
_sku("gpu_4090", "GPU 4090 云时", "compute", tier="call",
msrp=0.35, msrp_note="≈$0.20.5/h 对照", msrp_url="https://www.runpod.io/pricing",
unit="hour", query="4090 gpu cloud", title_filter=r"4090|rtx",
decide="训推性价比首选消费卡"),
_sku("gpu_h100", "GPU H100 云时", "compute", tier="call",
msrp=2.5, msrp_note="≈$1.54/h 对照", msrp_url="https://www.runpod.io/pricing",
unit="hour", query="h100 gpu", title_filter=r"h100|hopper",
decide="大模型才上;对比区域与磁盘"),
]
def _now() -> str:
return datetime.now(timezone.utc).isoformat()
def _fetch(url: str, timeout: int = FETCH_TIMEOUT) -> tuple[int, str]:
if not url:
return 0, ""
req = urllib.request.Request(url, headers={"User-Agent": UA, "Accept-Language": "en-US,en;q=0.9"})
try:
with urllib.request.urlopen(req, timeout=timeout) as r:
return r.status, r.read().decode("utf-8", "replace")
except urllib.error.HTTPError as e:
try:
body = e.read().decode("utf-8", "replace")
except Exception:
body = ""
return e.code, body
except Exception as e:
return 0, str(e)[:200]
def _parse_money_list(text: str) -> list[float]:
out = []
for m in re.finditer(r"(\d+)[,.](\d{1,2})\s*\$", text):
try:
v = float(f"{m.group(1)}.{m.group(2)}")
except ValueError:
continue
if 0.01 <= v <= 500:
out.append(v)
for m in re.finditer(r"\$\s*(\d+)(?:\.(\d{1,2}))?", text):
try:
v = float(m.group(1) + ("." + m.group(2) if m.group(2) else ""))
except ValueError:
continue
if 0.01 <= v <= 500:
out.append(v)
return out
def parse_face_from_title(title: str) -> dict[str, Any]:
"""从礼品卡标题抽面值区间。返回 face_min/max/currency/kind=fixed|range。"""
s = title or ""
out: dict[str, Any] = {"face_min": None, "face_max": None, "face_currency": None, "face_kind": None}
m = re.search(r"(\d+)\s*[-~到至]\s*(\d+)\s*(USD|US\$|\$)", s, re.I)
if m:
a, b = float(m.group(1)), float(m.group(2))
out.update(face_min=min(a, b), face_max=max(a, b), face_currency="USD",
face_kind="range" if a != b else "fixed")
return out
m = re.search(r"(?:USD|US\$|\$)\s*(\d+)(?:\s*[-~]\s*(\d+))?", s, re.I)
if m:
a = float(m.group(1))
b = float(m.group(2)) if m.group(2) else a
out.update(face_min=min(a, b), face_max=max(a, b), face_currency="USD",
face_kind="range" if a != b else "fixed")
return out
m = re.search(r"(\d+)\s*(USD|US\$)\b", s, re.I)
if m:
a = float(m.group(1))
out.update(face_min=a, face_max=a, face_currency="USD", face_kind="fixed")
return out
m = re.search(r"(\d+)\s*[-~]\s*(\d+)\s*(TRY|TL)\b", s, re.I)
if m:
a, b = float(m.group(1)), float(m.group(2))
out.update(face_min=min(a, b), face_max=max(a, b), face_currency="TRY",
face_kind="range" if a != b else "fixed")
return out
m = re.search(r"(\d+)\s*(TRY|TL)\b", s, re.I)
if m:
a = float(m.group(1))
out.update(face_min=a, face_max=a, face_currency="TRY", face_kind="fixed")
return out
m = re.search(r"(\d+)\s*(HKD|HK\$)", s, re.I)
if m:
a = float(m.group(1))
out.update(face_min=a, face_max=a, face_currency="HKD", face_kind="fixed")
return out
return out
# 粗汇率:仅用于折扣估计(非财务)
_FACE_FX = {"USD": 1.0, "TRY": 0.029, "TL": 0.029, "HKD": 0.128}
def face_discount_fields(title: str, price_usd: float) -> dict[str, Any]:
"""listing 价 + 标题面值 → 折扣估计诚实range 给 at_min/at_max"""
face = parse_face_from_title(title)
out = dict(face)
out["listing_usd"] = price_usd
out["discount"] = None
out["discount_at_min"] = None
out["discount_at_max"] = None
out["discount_note"] = None
ccy = face.get("face_currency")
fx = _FACE_FX.get(str(ccy or "").upper()) or _FACE_FX.get(str(ccy or ""))
if not fx or not face.get("face_min") or price_usd is None or price_usd <= 0:
out["discount_note"] = "no_face"
return out
fmin = float(face["face_min"]) * fx
fmax = float(face["face_max"]) * fx
if fmin <= 0 or fmax <= 0:
out["discount_note"] = "bad_face"
return out
dmin = round(price_usd / fmin, 4)
dmax = round(price_usd / fmax, 4)
out["discount_at_min"] = dmin
out["discount_at_max"] = dmax
if face.get("face_kind") == "fixed":
out["discount"] = dmin
out["discount_note"] = "fixed_face"
else:
out["discount"] = None
out["discount_note"] = "multi_denom_listing_min_price"
return out
def _ggsel_title_ok(blob: str, title_filter: str | None) -> bool:
"""弱匹配硬化filter 命中,且拒绝纯礼品卡/游戏 noise除非 filter 明确要卡)。"""
if not title_filter:
return True
if not re.search(title_filter, blob, re.I):
return False
# 若 filter 不是礼品卡向,丢掉明显礼品卡/充值卡误伤
if not re.search(r"gift|itunes|card|礼品|itunes", title_filter, re.I):
if re.search(r"gift\s*card|itunes|steam\s*wallet|roblox|pubg|valorant", blob, re.I):
# 仍允许 blob 同时含目标品牌
if not re.search(r"chatgpt|claude|cursor|gemini|openai|grok|copilot|midjourney", blob, re.I):
return False
return True
def parse_ggsel_catalog(html: str, title_filter: str | None = None) -> list[dict]:
"""从 GGSel 目录 HTML 抽 product + 价(启发式)。
模式 CSSR 内嵌 id_goods JSON 的 price_wme礼品卡页几乎无 $ 明文,必须走此路)。
模式 A/B旧 product slug + 邻近 $。
"""
quotes = []
gift_mode = bool(title_filter and re.search(
r"gift|itunes|card|礼品|app\s*store|virtual\s*card|debit|prepaid|visa",
title_filter, re.I,
))
price_lo = 0.15 if gift_mode else 0.5
# 模式 C嵌入式商品 JSONname + price_wme ≈ USD
# 结构:{"id_goods":N, ... "name":"...", ... "price_wme":"1.81"...}
for m in re.finditer(r'\{"id_goods":(\d+),', html):
chunk = html[m.start(): m.start() + 2800]
nm = re.search(r'"name":"((?:[^"\\]|\\.){5,180})"', chunk)
pr = re.search(r'"price_wme":"?([0-9.]+)"?', chunk)
ur = re.search(r'"url":"([^"]+)"', chunk)
if not (nm and pr):
continue
try:
# unescape minimal
title = bytes(nm.group(1), "utf-8").decode("unicode_escape", errors="replace")
except Exception:
title = nm.group(1)
title = title.replace("\\/", "/").replace('\\"', '"')
try:
price = float(pr.group(1))
except ValueError:
continue
blob = f"{title} {ur.group(1) if ur else ''}"
if not _ggsel_title_ok(blob, title_filter):
continue
if not (price_lo <= price <= 500):
continue
# 礼品卡:必须标题含 gift card / iTunes 码;拒 Apple ID 账号与游戏礼包
if gift_mode and not re.search(r"virtual\s*card|debit|prepaid|visa|mastercard", title_filter or "", re.I):
if not re.search(r"gift\s*card|iTunes\s*Gift|Google\s*Play\s*Gift", title, re.I):
continue
if re.search(r"\bApple ID\b|personal account|steam gift|roblox|psn account|Nexus Mods", title, re.I):
continue
slug = (ur.group(1) if ur else "").lstrip("/")
pid = m.group(1)
if slug.startswith("http"):
url = slug
elif slug:
url = f"https://ggsel.net/en/catalog/product/{slug}"
else:
url = f"https://ggsel.net/en/catalog/product/{pid}"
quotes.append({
"title": title[:100],
"price": price,
"currency": "USD",
"url": url,
"product_id": pid,
"confidence": 0.72,
"price_field": "price_wme",
})
# 模式 Aproduct slug + 邻近美元
for m in re.finditer(
r'/en/catalog/product/([a-z0-9-]+)-(\d+)[^$]{0,500}?(\d+)[,.](\d+)\s*\$',
html, re.I | re.S,
):
slug, pid, a, b = m.group(1), m.group(2), m.group(3), m.group(4)
title = slug.replace("-", " ")
blob = f"{title} {slug}"
if not _ggsel_title_ok(blob, title_filter):
continue
price = float(f"{a}.{b}")
if not (price_lo <= price <= 500):
continue
quotes.append({
"title": title[:80],
"price": price,
"currency": "USD",
"url": f"https://ggsel.net/en/catalog/product/{slug}-{pid}",
"product_id": pid,
"confidence": 0.75,
})
# 模式 B仅 slug后扫价— 扩大召回,仍强制 title_filter
if len(quotes) < 3 and title_filter:
for m in re.finditer(r'/en/catalog/product/([a-z0-9-]+)-(\d+)', html, re.I):
slug, pid = m.group(1), m.group(2)
title = slug.replace("-", " ")
blob = f"{title} {slug}"
if not _ggsel_title_ok(blob, title_filter):
continue
# 在匹配位置后 800 字符内找价
start = m.end()
window = html[start:start + 800]
moneys = _parse_money_list(window)
moneys = [p for p in moneys if price_lo <= p <= 500]
# 也扫 price_wme 在邻近
for wm in re.finditer(r'"price_wme":"?([0-9.]+)"?', window):
try:
moneys.append(float(wm.group(1)))
except ValueError:
pass
moneys = [p for p in moneys if price_lo <= p <= 500]
if not moneys:
continue
price = min(moneys)
quotes.append({
"title": title[:80],
"price": price,
"currency": "USD",
"url": f"https://ggsel.net/en/catalog/product/{slug}-{pid}",
"product_id": pid,
"confidence": 0.55,
})
# 无 title 命中时:绝不拿整页最低价(会污染成 $0.25 万能价)
# 仅在未设 filter 时允许 catalog 全局最低
if not quotes and not title_filter:
for price in sorted(set(_parse_money_list(html)))[:5]:
if price < 0.5:
continue
quotes.append({
"title": "catalog_min", "price": price, "currency": "USD",
"url": None, "product_id": None, "confidence": 0.3,
})
out, seen = [], set()
for q in sorted(quotes, key=lambda x: x["price"]):
k = (q.get("product_id") or q["price"], q["price"])
if k in seen:
continue
seen.add(k)
try:
fd = face_discount_fields(str(q.get("title") or ""), float(q["price"]))
q.update({k: v for k, v in fd.items() if v is not None or k.startswith("discount")})
except Exception:
pass
out.append(q)
return out[:12]
def parse_plati_item(html: str, url: str) -> list[dict]:
m = re.search(r'itemprop=["\']price["\'][^>]*content=["\']([\d.]+)["\']', html, re.I)
if not m:
m = re.search(r'content=["\']([\d.]+)["\'][^>]*itemprop=["\']price["\']', html, re.I)
if m:
return [{"title": "plati_item", "price": float(m.group(1)), "currency": "USD", "url": url, "product_id": None}]
moneys = _parse_money_list(html)
if moneys:
return [{"title": "plati_min", "price": min(moneys), "currency": "USD", "url": url, "product_id": None}]
return []
# 粗略 EUR→USD面板统一 USD精确汇率非目标
_EUR_USD = 1.08
def parse_funpay_lots(html: str, url: str) -> list[dict]:
"""FunPay lot 列表页:抽 € / $ 标价,转 USD 后取合理最低。"""
quotes: list[dict] = []
# € prices
for m in re.finditer(
r'<div>(\d+)[.,](\d{2})\s*<span class="unit">€</span>',
html, re.I,
):
try:
eur = float(f"{m.group(1)}.{m.group(2)}")
except ValueError:
continue
if not (1.0 <= eur <= 400):
continue
usd = round(eur * _EUR_USD, 2)
quotes.append({
"title": "funpay_eur",
"price": usd,
"currency": "USD",
"price_orig": eur,
"currency_orig": "EUR",
"url": url,
"product_id": None,
"confidence": 0.7,
})
# $ prices
for m in re.finditer(
r'<div>(\d+)[.,](\d{2})\s*<span class="unit">\$</span>',
html, re.I,
):
try:
usd = float(f"{m.group(1)}.{m.group(2)}")
except ValueError:
continue
if not (1.0 <= usd <= 400):
continue
quotes.append({
"title": "funpay_usd",
"price": usd,
"currency": "USD",
"url": url,
"product_id": None,
"confidence": 0.75,
})
# 丢掉邀请试用类极低价(< $3 且同页有更高价时)
if not quotes:
return []
quotes.sort(key=lambda x: x["price"])
# 若最低 < 3 且存在 ≥8 的价,跳过试用噪声
solid = [q for q in quotes if q["price"] >= 8.0]
if solid and quotes[0]["price"] < 3.0:
quotes = solid
out, seen = [], set()
for q in quotes:
k = q["price"]
if k in seen:
continue
seen.add(k)
out.append(q)
return out[:12]
def parse_official_go(html: str, url: str) -> list[dict]:
found = _parse_money_list(html.replace("/month", " $").replace("/mo", " $"))
for m in re.finditer(r"\$\s*(\d+)\s*/\s*month", html, re.I):
found.append(float(m.group(1)))
for m in re.finditer(r"\$\s*(\d+)\s+for your first month", html, re.I):
found.append(float(m.group(1)))
if not found:
return [{"title": "official", "price": 10.0, "currency": "USD", "url": url, "product_id": None, "note": "fallback"}]
price = 10.0 if 10.0 in found else min(x for x in found if x >= 5)
return [{"title": "official_go", "price": price, "currency": "USD", "url": url, "unit": "mo", "product_id": None}]
def parse_official_ollama(html: str, url: str) -> list[dict]:
out = []
if re.search(r"Pro[\s\S]{0,80}\$\s*20", html, re.I) or "$20" in html:
out.append({"title": "Pro", "price": 20.0, "currency": "USD", "url": url, "unit": "mo", "product_id": None})
if re.search(r"Max[\s\S]{0,80}\$\s*100", html, re.I) or "$100" in html:
out.append({"title": "Max", "price": 100.0, "currency": "USD", "url": url, "unit": "mo", "product_id": None})
if not out:
out = [{"title": "Pro", "price": 20.0, "currency": "USD", "url": url, "unit": "mo", "product_id": None, "note": "fallback"}]
return out
def parse_official_clinepass(html: str, url: str) -> list[dict]:
found = _parse_money_list(html)
for m in re.finditer(r"\$\s*(\d+(?:\.\d{1,2})?)\s*/\s*month", html, re.I):
try:
found.append(float(m.group(1)))
except ValueError:
pass
recurring = [x for x in found if 8.0 <= x <= 15.0]
price = min(recurring) if recurring else (min(found) if found else 9.99)
return [{"title": "clinepass", "price": price, "currency": "USD", "url": url, "unit": "mo", "product_id": None}]
PARSERS = {
"ggsel_catalog": lambda html, url, **kw: parse_ggsel_catalog(html, kw.get("title_filter")),
"plati_item": lambda html, url, **kw: parse_plati_item(html, url),
"funpay_lots": lambda html, url, **kw: parse_funpay_lots(html, url),
"official_go": lambda html, url, **kw: parse_official_go(html, url),
"official_msrp": lambda html, url, **kw: [],
"official_ollama": lambda html, url, **kw: parse_official_ollama(html, url),
"official_clinepass": lambda html, url, **kw: parse_official_clinepass(html, url),
}
def _load_cached() -> dict | None:
if not OUT.is_file():
return None
try:
return json.loads(OUT.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
return None
def _cache_age_s(data: dict) -> float | None:
ts = data.get("ts") or ""
if not ts:
return None
try:
# 2026-07-10T19:38:23.190232+00:00
dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
return max(0.0, (datetime.now(timezone.utc) - dt).total_seconds())
except ValueError:
return None
def _history_best_entry(s: dict) -> dict:
"""单 SKU history 行:价 + 渠道 + availabilityunknown≠sold_out"""
best = s.get("best") if isinstance(s.get("best"), dict) else {}
bm = s.get("best_market") if isinstance(s.get("best_market"), dict) else {}
price = best.get("price") if best.get("price") is not None else bm.get("price")
ch = best.get("channel") or bm.get("channel")
av = (
s.get("availability")
or best.get("availability")
or best.get("av")
or s.get("av")
)
entry: dict[str, Any] = {
"p": price,
"ch": ch,
"tier": s.get("tier"),
}
if av is not None:
entry["av"] = av
elif price is not None:
entry["av"] = "in_stock"
else:
sources = s.get("sources") or []
if sources and all(
isinstance(x, dict) and (x.get("ok") is False or x.get("error"))
for x in sources
):
entry["av"] = "unknown"
entry["probe_ok"] = False
else:
entry["av"] = "unknown"
seller = best.get("seller_id") or s.get("seller_id")
if seller:
entry["seller"] = seller
return entry
def _append_history(result: dict) -> None:
"""每次探针写一行摘要,供趋势/审计(自己渠道,不上云)。"""
try:
HISTORY.mkdir(parents=True, exist_ok=True)
day = datetime.now(timezone.utc).strftime("%Y%m%d")
path = HISTORY / f"{day}.jsonl"
best_map: dict[str, Any] = {}
for s in result.get("skus") or []:
if not isinstance(s, dict) or not s.get("id"):
continue
best_map[str(s.get("id"))] = _history_best_entry(s)
row = {
"ts": result.get("ts"),
"n": len(result.get("skus") or []),
"counts": result.get("counts"),
"best": best_map,
}
with path.open("a", encoding="utf-8") as f:
f.write(json.dumps(row, ensure_ascii=False) + "\n")
except OSError:
pass
def probe_all(force: bool = False, merge: bool = True, subset: bool = False) -> dict:
"""拉取 SKUS可能是子集默认与本地缓存按 id 合并,保证轮询不全量抹掉。"""
CACHE_DIR.mkdir(parents=True, exist_ok=True)
prev = _load_cached() or {}
# 全量且缓存新鲜:直接复用(非 force
if not force and not subset and prev.get("skus"):
age = _cache_age_s(prev)
if age is not None and age < TTL_S:
out = dict(prev)
out["cache_hit"] = True
out["cache_age_s"] = int(age)
return out
skus_out = []
for spec in SKUS:
sources = []
for pr in spec.get("probes") or []:
parser = pr.get("parser") or ""
url = pr.get("url") or ""
# 无 URL 的静态 MSRP 探针
if parser == "official_msrp" and not url:
msrp0 = (spec.get("official_msrp") or {})
if msrp0.get("price") is not None:
sources.append({
"channel_id": "official-msrp",
"channel": "官方",
"kind": "msrp",
"url": msrp0.get("url") or "",
"http": 200,
"ok": True,
"quotes": [],
"price": msrp0["price"],
"currency": msrp0.get("currency") or "USD",
"title": "msrp",
"link": msrp0.get("url"),
"error": None,
})
continue
code, body = _fetch(url)
# official 页允许短 body
min_len = 200 if str(parser).startswith("official") else 500
rec = {
"channel_id": pr.get("channel_id"),
"channel": pr.get("channel"),
"kind": pr.get("kind"),
"url": url,
"http": code,
"ok": 200 <= int(code) < 400 and len(body) > min_len,
"quotes": [],
"error": None,
}
if not rec["ok"]:
rec["error"] = f"http={code} len={len(body)}"
sources.append(rec)
time.sleep(SLEEP_S)
continue
fn = PARSERS.get(parser)
try:
quotes = fn(body, url, title_filter=pr.get("title_filter")) if fn else []
except Exception as e:
quotes = []
rec["error"] = str(e)[:80]
rec["quotes"] = quotes
if quotes:
rec["price"] = quotes[0]["price"]
rec["currency"] = quotes[0].get("currency") or "USD"
rec["title"] = quotes[0].get("title")
rec["link"] = quotes[0].get("url") or url
for fk in ("face_min", "face_max", "face_currency", "face_kind",
"discount", "discount_at_min", "discount_at_max", "discount_note"):
if quotes[0].get(fk) is not None:
rec[fk] = quotes[0].get(fk)
sources.append(rec)
time.sleep(SLEEP_S)
msrp = dict(spec.get("official_msrp") or {}) if spec.get("official_msrp") else {}
msrp_src = [s for s in sources if s.get("kind") == "msrp" and s.get("price") is not None]
if msrp_src and (not msrp.get("price") or msrp.get("price") == 0):
msrp["price"] = msrp_src[0]["price"]
msrp["currency"] = msrp_src[0].get("currency") or "USD"
if msrp_src:
msrp.setdefault("url", msrp_src[0].get("url"))
def _market_sane(s: dict) -> bool:
"""丢掉离谱灰市价(整页污染 / 试用价误匹配)。"""
if s.get("kind") == "msrp" or s.get("price") is None:
return False
# relay_index 不是「账号货架价」,单独标记,不进 best_market 账号比较
if s.get("kind") == "relay_index":
return False
p = float(s["price"])
if p <= 0:
return False
unit = (msrp.get("unit") if msrp else None) or "mo"
mprice = msrp.get("price") if msrp else None
conf = s.get("confidence")
if conf is not None:
try:
if float(conf) < 0.45:
return False
except (TypeError, ValueError):
pass
# 订阅类:丢掉整页污染的几毛钱价;真·灰市号可到 $1如 SuperGrok
if unit in ("mo",) and mprice and float(mprice) >= 5:
if p < 0.5:
return False
# < 官方 2% 且 < $1基本是误匹配
if p < float(mprice) * 0.02 and p < 1.0:
return False
# 超高档(≥$100 MSRP灰市价 < $5 几乎必假
if float(mprice) >= 100 and p < 5.0:
return False
# 礼品卡面值对照:灰市常是「多面值条目最低价」或区服 TL 卡,可远低于 face MSRP
if unit == "face":
if p < 0.15:
return False
# 置信度太低不进 best
if conf is not None:
try:
if float(conf) < 0.5:
return False
except (TypeError, ValueError):
pass
return True
# 非短信/按量:拒绝纯噪声
if unit not in ("sms", "1k", "hour", "gb", "account", "credit", "face") and p < 0.2:
return False
return True
market_prices = [s for s in sources if _market_sane(s)]
best_m = min(market_prices, key=lambda x: float(x["price"])) if market_prices else None
best = best_m
if not best and msrp.get("price") is not None:
best = {
"channel": "官方",
"channel_id": "official-msrp",
"price": msrp.get("price"),
"currency": msrp.get("currency") or "USD",
"link": msrp.get("url"),
"url": msrp.get("url"),
"title": "msrp",
"kind": "msrp",
}
# 无 MSRP 且无灰市best 空UI 显示「—」
quotes_ready = bool(market_prices) or bool(msrp.get("price") is not None)
skus_out.append({
"id": spec["id"],
"label": spec["label"],
"category": spec["category"],
"tier": spec.get("tier") or "p0",
"official_msrp": msrp or None,
"sources": sources,
"source_n": len(sources),
"quotes_ready": quotes_ready,
"best": {
"channel": best.get("channel"),
"channel_id": best.get("channel_id"),
"price": best.get("price"),
"currency": best.get("currency") or "USD",
"url": best.get("link") or best.get("url"),
"title": best.get("title"),
"kind": best.get("kind"),
"face_min": best.get("face_min"),
"face_max": best.get("face_max"),
"face_currency": best.get("face_currency"),
"face_kind": best.get("face_kind"),
"discount": best.get("discount"),
"discount_at_min": best.get("discount_at_min"),
"discount_at_max": best.get("discount_at_max"),
"discount_note": best.get("discount_note"),
} if best else None,
"best_market": {
"channel": best_m.get("channel"),
"price": best_m.get("price"),
"currency": best_m.get("currency") or "USD",
"url": best_m.get("link") or best_m.get("url"),
"face_min": best_m.get("face_min"),
"face_max": best_m.get("face_max"),
"face_currency": best_m.get("face_currency"),
"discount": best_m.get("discount"),
"discount_at_min": best_m.get("discount_at_min"),
"discount_at_max": best_m.get("discount_at_max"),
"discount_note": best_m.get("discount_note"),
} if best_m else None,
"spread": (
round(float(best_m["price"]) / float(msrp["price"]), 3)
if best_m and msrp and msrp.get("price")
else None
),
"monitor": spec.get("monitor"),
"decide": spec.get("decide"),
"hot": True,
"demand_link": [],
})
# 灰市价 ≫ MSRP可能多月/捆绑/假货,勿当「单月更贵」误读
row = skus_out[-1]
try:
bm_p = (row.get("best_market") or {}).get("price")
ms_p = (row.get("official_msrp") or {}).get("price")
if bm_p is not None and ms_p is not None and float(ms_p) > 0:
ratio = float(bm_p) / float(ms_p)
row["spread"] = round(ratio, 3)
if ratio >= 1.5:
row["price_flag"] = "possible_bundle"
row["price_flag_note"] = (
f"灰市 {bm_p} ≥ MSRP×1.5{ms_p})→ 可能多月/捆绑/席位包,勿当单月溢价"
)
elif ratio <= 0.15 and float(ms_p) >= 10:
row["price_flag"] = "suspiciously_cheap"
row["price_flag_note"] = (
f"灰市 {bm_p} ≤ MSRP×0.15 → 高仿/试用/误匹配风险"
)
except (TypeError, ValueError):
pass
# 与旧缓存合并:子集探针不丢其它 tier
if merge and prev.get("skus"):
by_id = {s.get("id"): s for s in prev["skus"] if isinstance(s, dict) and s.get("id")}
for s in skus_out:
by_id[s["id"]] = s
# 稳定顺序:按当前全量定义序,未知的挂尾
order = []
try:
# 重新读模块级全表顺序困难;按 prev 序 + 新 id
seen = set()
for s in prev["skus"]:
i = s.get("id")
if i in by_id and i not in seen:
order.append(by_id[i]); seen.add(i)
for s in skus_out:
i = s.get("id")
if i not in seen:
order.append(s); seen.add(i)
skus_out = order
except Exception:
skus_out = list(by_id.values())
# 合并 community-prices overlay5sim / awesome 中转指数 / openrouter
_apply_community_overlay(skus_out)
by_tier: dict[str, int] = {}
market_n = 0
for s in skus_out:
t = s.get("tier") or "p0"
by_tier[t] = by_tier.get(t, 0) + 1
if (s.get("best_market") or {}).get("price") is not None:
market_n += 1
result = {
"ts": _now(),
"ok": True,
"skus": skus_out,
"strategy": "sku_probe_local_cache+community",
"cache_hit": False,
"probed_n": len(SKUS),
"counts": {
"skus": len(skus_out),
"market_n": market_n,
**{f"tier_{k}": v for k, v in by_tier.items()},
},
"note": "本地缓存探针P0/P1/P2+pay+callGGSel+community(5sim/awesome-proxy/OR)history jsonl",
"paths": {
"quotes": str(OUT),
"community": str(COMMUNITY),
"history": str(HISTORY),
},
}
OUT.write_text(json.dumps(result, ensure_ascii=False, indent=2))
_append_history(result)
return result
def _apply_community_overlay(skus_out: list[dict[str, Any]]) -> None:
"""把 community-prices.json 的 sku_overlay 并入 SKU不覆盖已有 marketplace 实价)。"""
if not COMMUNITY.is_file():
return
try:
data = json.loads(COMMUNITY.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
return
overlay = data.get("sku_overlay") if isinstance(data, dict) else None
if not isinstance(overlay, dict):
return
by_id = {s.get("id"): s for s in skus_out if isinstance(s, dict)}
for sid, o in overlay.items():
if not isinstance(o, dict) or o.get("price") is None:
continue
s = by_id.get(sid)
if not s:
continue
s["community"] = o
kind = o.get("kind") or "community"
# sms 等 marketplace 类:可作 best_market
if kind == "marketplace" and not (s.get("best_market") or {}).get("price"):
s["best_market"] = {
"channel": o.get("channel"),
"price": o.get("price"),
"currency": o.get("currency") or "USD",
"url": o.get("url"),
"kind": kind,
"unit": o.get("unit"),
"source": "community",
}
if not s.get("best") or (s.get("best") or {}).get("kind") == "msrp":
s["best"] = {
"channel": o.get("channel"),
"channel_id": o.get("channel_id"),
"price": o.get("price"),
"currency": o.get("currency") or "USD",
"url": o.get("url"),
"title": o.get("title"),
"kind": kind,
}
s["quotes_ready"] = True
# relay_index挂 side 字段,不假装成账号店价
if kind == "relay_index":
s["relay_index"] = o
if not s.get("best") and o.get("price") is not None:
s["best"] = {
"channel": o.get("channel"),
"channel_id": o.get("channel_id"),
"price": o.get("price"),
"currency": o.get("currency") or "USD",
"url": o.get("url"),
"title": o.get("title") or "relay_index",
"kind": "relay_index",
"unit": o.get("unit"),
}
s["quotes_ready"] = True
# 供应链 demand 相关 SKU轮询时每次必打合并进子集
DEMAND_SKU_IDS: set[str] = {
"codex_quota", "openai", "chatgpt_pro", "chatgpt_team", "claude", "claude_max",
"cursor", "relay_premium", "newapi_relay", "openrouter", "opencode_go",
"sms_otp", "sms_us", "sms_nl", "sms_id", "bulk_mail", "captcha_solve",
"supergrok", "giftcard_us", "giftcard_tr", "giftcard_hk", "virtual_card",
"residential_proxy",
}
_DEMAND_REQ = Path.home() / ".cache" / "manhuang" / "quotes" / ".demand-probe.request"
def _gap_skus_from_request() -> set[str]:
"""读 demand-first 请求旗上的 gap_skuscollect 写入)。"""
if not _DEMAND_REQ.is_file():
return set()
try:
data = json.loads(_DEMAND_REQ.read_text(encoding="utf-8"))
out: set[str] = set()
for sid in data.get("gap_skus") or []:
if sid:
out.add(str(sid))
# 也并入 priced 相关 id保证刷新已有价
for p in data.get("priced_samples") or []:
if isinstance(p, dict) and p.get("id"):
out.add(str(p["id"]))
return out
except (OSError, json.JSONDecodeError, TypeError):
return set()
def main() -> int:
import argparse
ap = argparse.ArgumentParser(description="蛮荒比价探针 → ~/.cache/manhuang/quotes/")
ap.add_argument("--force", action="store_true", help="忽略 TTL强制重拉")
ap.add_argument("--tier", action="append", help="only p0/p1/p2/pay/call (repeatable)")
ap.add_argument("--rotate", action="store_true",
help="按时段轮询:偶时 p0+pay奇时 p1+p2+call始终并入 demand SKU")
ap.add_argument("--demand-first", action="store_true",
help="只探针 DEMAND_SKU_IDS + request 旗 gap_skus最快适合危机时")
ap.add_argument("--ids", action="append", help="只探针指定 id可重复")
ap.add_argument("--no-merge", action="store_true", help="不与旧缓存合并(全量覆盖)")
args = ap.parse_args()
global SKUS
full = list(SKUS)
if args.demand_first:
allow = set(DEMAND_SKU_IDS) | _gap_skus_from_request()
# --ids 可再收窄或追加
if args.ids:
allow |= set(args.ids)
SKUS = [s for s in full if s.get("id") in allow]
subset = True
print(f"demand-first allow={len(allow)} match={len(SKUS)} ids={sorted(allow)[:20]}")
elif args.ids:
allow_ids = set(args.ids)
SKUS = [s for s in full if s.get("id") in allow_ids]
subset = True
else:
if args.rotate and not args.tier:
h = datetime.now(timezone.utc).hour
if h % 2 == 0:
args.tier = ["p0", "pay"]
else:
args.tier = ["p1", "p2", "call"]
subset = False
if args.tier:
allow = {t.lower() for t in args.tier}
base = [s for s in full if (s.get("tier") or "p0") in allow]
# demand SKU 始终并入本轮(供应链优先)
seen = {s.get("id") for s in base}
for s in full:
if s.get("id") in DEMAND_SKU_IDS and s.get("id") not in seen:
base.append(s)
seen.add(s.get("id"))
SKUS = base
subset = True
r = probe_all(force=args.force, merge=not args.no_merge, subset=subset)
hit = "CACHE" if r.get("cache_hit") else "FRESH"
print(
f"wrote {OUT} n={len(r.get('skus') or [])} probed={r.get('probed_n')} "
f"[{hit}] tiers={r.get('counts')}"
)
# 只打印本轮探针的 idsubset 时用 SKUS 列表)
probed_ids = {s.get("id") for s in SKUS}
for s in r.get("skus") or []:
if subset and s.get("id") not in probed_ids:
continue
b = s.get("best") or {}
m = s.get("official_msrp") or {}
print(
f"- [{s.get('tier')}] {s['id']}: best={b.get('price')} @{b.get('channel')} "
f"| msrp={m.get('price')} | mkt_ok={sum(1 for x in s.get('sources') or [] if x.get('kind')!='msrp' and x.get('price') is not None)}"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())