| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597 |
- """
- API 结构探针脚本 v2
- 目标:用最低接口成本测通 小红书(JustOne PGY) 和 抖音(TikHub 代理) 创作者搜索。
- 注意:TikHub 使用代理地址 https://server.fmode.cn/thapi 规避跨域问题。
- 每个测试只调用 1 次,count/pageSize=2,节省 API 成本。
- """
- import urllib.request
- import urllib.parse
- import json
- import sys
- import time
- # Windows GBK 终端强制 UTF-8 输出
- if hasattr(sys.stdout, 'reconfigure'):
- sys.stdout.reconfigure(encoding='utf-8', errors='replace')
- if hasattr(sys.stderr, 'reconfigure'):
- sys.stderr.reconfigure(encoding='utf-8', errors='replace')
- # ─────────────── API Keys & 地址 ───────────────
- JUSTONE_TOKEN = "wT0w78sxrwlrpJrQ"
- TIKHUB_TOKEN = "oL4nMZwrhXCro1+TQYye0BL8LASyaphtx0LUmGuRCfqnm1CeBFfbdmLAGQ=="
- JUSTONE_BASE = "https://api.justoneapi.com"
- TIKHUB_PROXY = "https://server.fmode.cn/thapi" # TikHub 代理,规避跨域
- KEYWORD = "护肤"
- # ─────────────── 工具函数 ───────────────
- def get(url: str, headers: dict | None = None, retry301: int = 3) -> dict:
- for attempt in range(1, retry301 + 2):
- req = urllib.request.Request(url, headers=headers or {})
- try:
- with urllib.request.urlopen(req, timeout=20) as resp:
- body = resp.read().decode("utf-8")
- data = json.loads(body)
- if data.get("code") == 301 and attempt <= retry301:
- print(f" [301] RETRY ({attempt}/{retry301})...")
- time.sleep(3)
- continue
- return data
- except urllib.error.HTTPError as e:
- body = e.read().decode("utf-8")
- print(f" [HTTP {e.code}] {body[:300]}")
- return {}
- except Exception as ex:
- print(f" [ERROR] {ex}")
- return {}
- return {}
- def post(url: str, body: dict, headers: dict | None = None) -> dict:
- """POST JSON body 请求"""
- payload = json.dumps(body).encode("utf-8")
- h = dict(headers or {})
- h["Content-Type"] = "application/json"
- req = urllib.request.Request(url, data=payload, headers=h, method="POST")
- try:
- with urllib.request.urlopen(req, timeout=20) as resp:
- return json.loads(resp.read().decode("utf-8"))
- except urllib.error.HTTPError as e:
- body_resp = e.read().decode("utf-8")
- print(f" [HTTP {e.code}] {body_resp[:300]}")
- return {}
- except Exception as ex:
- print(f" [ERROR] {ex}")
- return {}
- def print_section(title: str):
- print("\n" + "=" * 60)
- print(f" {title}")
- print("=" * 60)
- def describe_keys(obj: dict, indent: int = 0, max_depth: int = 3):
- """递归打印对象的键和值类型(不打印完整值,节省屏幕空间)"""
- prefix = " " * indent
- for k, v in obj.items():
- if isinstance(v, dict):
- print(f"{prefix}[dict] {k}:")
- if indent < max_depth:
- describe_keys(v, indent + 1, max_depth)
- elif isinstance(v, list):
- print(f"{prefix}[list({len(v)})] {k}")
- if v and isinstance(v[0], dict) and indent < max_depth:
- print(f"{prefix} └─ 第一个元素 keys:")
- describe_keys(v[0], indent + 2, max_depth)
- else:
- display = repr(v)[:80]
- print(f"{prefix}[{type(v).__name__}] {k} = {display}")
- # ─────────────── 1. 小红书 JustOne PGY 创作者搜索 ───────────────
- def test_xhs_justone():
- print_section("1. 小红书 — JustOne 蒲公英 Creator Search")
- params = urllib.parse.urlencode({
- "token": JUSTONE_TOKEN,
- "keyword": KEYWORD,
- "page": 1,
- "searchType": "NOTE",
- "fansNumberLower": 20000,
- "fansNumberUpper": 500000,
- "gender": "ALL",
- })
- url = f"{JUSTONE_BASE}/api/xiaohongshu-pgy/api/solar/cooperator/blogger/v2/v1?{params}"
- print(f" 请求 URL: {url.replace(JUSTONE_TOKEN, '***')}\n")
- data = get(url)
- if not data:
- print(" ⚠ 无响应")
- return
- print(f" 顶层 code : {data.get('code')}")
- print(f" 顶层 message: {data.get('message')}")
- print(f"\n 完整响应结构:")
- describe_keys(data)
- # 定位列表
- d = data.get("data", {})
- if d:
- for list_key in ("kols", "bloggerList", "list", "items", "result", "authors"):
- lst = d.get(list_key)
- if lst and isinstance(lst, list):
- print(f"\n [OK] 找到列表字段: data.data.{list_key},共 {len(lst)} 条")
- if lst:
- print(f"\n 第一条完整数据:")
- print(json.dumps(lst[0], ensure_ascii=False, indent=2)[:2000])
- return
- print(f"\n [WARN] 未在 data.data 中找到已知列表字段,data.data keys: {list(d.keys())}")
- # ─────────────── 2. 抖音 JustOne 星图 创作者搜索(多接口尝试)───────────────
- DOUYIN_JUSTONE_ENDPOINTS = [
- # 当前在用(已知返回 authors 但为空)
- ("/api/douyin-xingtu/gw/api/gsearch/search_for_author_square/v1",
- {"keyword": KEYWORD, "page": 1, "searchType": "CONTENT"},
- "Xingtu search_for_author_square (当前)"),
- # JustOne 文档 Douyin Xingtu KOL Keyword Search V1
- ("/api/douyin-xingtu/gw/api/gsearch/search_kol/v1",
- {"keyword": KEYWORD, "page": 1},
- "Xingtu search_kol v1"),
- # JustOne 文档 Douyin User Search V2
- ("/api/douyin/aweme/v1/search/user/",
- {"keyword": KEYWORD, "count": 2, "offset": 0},
- "Douyin user search v1 (通用)"),
- # 另一种可能路径
- ("/api/douyin-xingtu/gw/api/search/author/v1",
- {"keyword": KEYWORD, "page": 1},
- "Xingtu search author v1"),
- ]
- def test_douyin_justone():
- print_section("2. 抖音 — JustOne 星图 KOL 搜索(逐一探测可用接口)")
- for endpoint, extra_params, label in DOUYIN_JUSTONE_ENDPOINTS:
- print(f"\n ── {label} ──")
- params = urllib.parse.urlencode({
- "token": JUSTONE_TOKEN,
- **extra_params,
- })
- url = f"{JUSTONE_BASE}{endpoint}?{params}"
- print(f" URL: {url.replace(JUSTONE_TOKEN, '***')}")
- data = get(url)
- if not data:
- print(" ⚠ 无响应,跳过")
- continue
- code = data.get("code")
- msg = data.get("message", "")
- print(f" code={code} message={msg}")
- if code != 0:
- print(f" [SKIP] 接口返回错误 code={code}")
- continue
- # 成功,打印完整结构
- print(f"\n [OK] 接口 [{label}] 返回成功!")
- print(f" 完整响应结构:")
- describe_keys(data)
- # 定位列表
- d = data.get("data", {})
- for list_key in ("authors", "kols", "kol_list", "list", "items", "result", "userList"):
- lst = d.get(list_key) if isinstance(d, dict) else None
- if lst is None and isinstance(d, dict):
- d2 = d.get("data", {})
- lst = d2.get(list_key) if isinstance(d2, dict) else None
- if lst and isinstance(lst, list):
- print(f"\n [LIST] 字段: {list_key},共 {len(lst)} 条")
- if lst:
- print(f"\n 第一条完整数据:")
- print(json.dumps(lst[0], ensure_ascii=False, indent=2)[:2000])
- return
- print(f" [WARN] 未找到列表字段,data keys = {list(data.get('data', {}).keys()) if isinstance(data.get('data'), dict) else data.keys()}")
- return
- print("\n [FAIL] 所有 JustOne 抖音接口均无有效响应")
- # ─────────────── 3. 抖音 — TikHub 星图 KOL 搜索(代理地址)───────────────
- def test_douyin_tikhub_xingtu():
- """
- 通过代理 https://server.fmode.cn/thapi 调用 TikHub 星图 KOL 搜索。
- 代理规避浏览器跨域,Python 直连同样可用。
- 只请求 count=2,节省成本。
- """
- print_section("3. 抖音 — TikHub 星图 KOL 搜索(代理)")
- headers = {"Authorization": f"Bearer {TIKHUB_TOKEN}"}
- # 按优先级逐一尝试,成功则停止
- # 每个条目: (url, body_dict, label, use_post)
- candidates = [
- (
- f"{TIKHUB_PROXY}/api/v1/douyin/xingtu/fetch_kol_search_v1",
- {"keyword": KEYWORD, "page": 1, "count": 2},
- "Xingtu KOL 搜索 V1 [GET]",
- False
- ),
- (
- f"{TIKHUB_PROXY}/api/v1/douyin/xingtu/fetch_kol_search_v1",
- {"keyword": KEYWORD, "page": 1, "count": 2},
- "Xingtu KOL 搜索 V1 [POST]",
- True
- ),
- (
- f"{TIKHUB_PROXY}/api/v1/douyin/xingtu/fetch_kol_advanced_search_v2",
- {"keyword": KEYWORD, "page": 1, "count": 2},
- "Xingtu KOL 高级搜索 V2 [POST]",
- True
- ),
- (
- f"{TIKHUB_PROXY}/api/v1/douyin/xingtu_v2/get_author_market_fields",
- {},
- "Xingtu V2 达人广场筛选字段 [GET]",
- False
- ),
- ]
- for url_base, body, label, use_post in candidates:
- print(f"\n ── {label} ──")
- if use_post:
- print(f" POST {url_base} body={body}")
- data = post(url_base, body, headers)
- else:
- params = urllib.parse.urlencode(body) if body else ""
- url = f"{url_base}?{params}" if params else url_base
- print(f" GET {url}")
- data = get(url, headers)
- if not data:
- print(" [SKIP] 无响应")
- continue
- code = data.get("code")
- msg = data.get("message", "")
- print(f" code={code} message={str(msg)[:100]}")
- if code not in (200, 0):
- print(f" [SKIP] 非成功状态")
- continue
- print(f"\n [OK] {label} 通过!")
- print(" 响应结构:")
- describe_keys(data)
- _find_and_print_list(data, label)
- return
- print("\n [FAIL] TikHub 星图接口均失败,改用 Douyin 用户搜索")
- test_douyin_tikhub_user_search()
- def test_douyin_tikhub_user_search():
- """
- 备用:通过代理调用 TikHub Douyin 用户搜索。
- docs 中 UserSearchRequest / UserSearchRequestV2 是 POST body schema,
- 故改为 POST + JSON body(之前用 GET 是错误的)。
- """
- print_section("3b. 抖音备用 — TikHub Douyin 用户搜索(POST,代理)")
- headers = {"Authorization": f"Bearer {TIKHUB_TOKEN}"}
- # (url, body, label, use_post)
- candidates = [
- (
- f"{TIKHUB_PROXY}/api/v1/douyin/search/fetch_user_search_v2",
- {"keyword": KEYWORD, "offset": 0, "count": 2},
- "Douyin Search 用户搜索 V2 [POST]",
- True
- ),
- (
- f"{TIKHUB_PROXY}/api/v1/douyin/search/fetch_user_search",
- {"keyword": KEYWORD, "offset": 0, "count": 2},
- "Douyin Search 用户搜索 V1 [POST]",
- True
- ),
- (
- f"{TIKHUB_PROXY}/api/v1/douyin/web/fetch_user_search",
- {"keyword": KEYWORD, "offset": 0, "count": 2},
- "Douyin Web 用户搜索 [GET]",
- False
- ),
- (
- f"{TIKHUB_PROXY}/api/v1/douyin/web/fetch_user_search",
- {"keyword": KEYWORD, "offset": 0, "count": 2},
- "Douyin Web 用户搜索 [POST]",
- True
- ),
- ]
- for url_base, body, label, use_post in candidates:
- print(f"\n ── {label} ──")
- if use_post:
- print(f" POST {url_base} body={body}")
- data = post(url_base, body, headers)
- else:
- params = urllib.parse.urlencode(body)
- url = f"{url_base}?{params}"
- print(f" GET {url}")
- data = get(url, headers)
- if not data:
- print(" [SKIP] 无响应")
- continue
- code = data.get("code")
- msg = data.get("message", "")
- print(f" code={code} message={str(msg)[:100]}")
- if code not in (200, 0):
- print(f" [SKIP] 非成功状态")
- continue
- print(f"\n [OK] {label} 通过!")
- print(" 响应结构:")
- describe_keys(data)
- _find_and_print_list(data, label)
- return
- print(" [FAIL] 所有 TikHub 抖音用户搜索接口均失败")
- def test_douyin_tikhub_billboard():
- """
- 测试 TikHub Douyin-Billboard-API 账号搜索。
- 搜索用户名或抖音号 — 可能不需要高级订阅。
- docs 中 Billboard 端点带有 _post 后缀,故先试 POST 再试 GET。
- """
- print_section("3c. 抖音 — TikHub Billboard 账号搜索")
- headers = {"Authorization": f"Bearer {TIKHUB_TOKEN}"}
- url = f"{TIKHUB_PROXY}/api/v1/douyin/billboard/fetch_account_search_list"
- body = {"keyword": KEYWORD, "count": 2}
- for label, use_post in [("[POST]", True), ("[GET]", False)]:
- print(f"\n ── Billboard 账号搜索 {label} ──")
- if use_post:
- print(f" POST {url} body={body}")
- data = post(url, body, headers)
- else:
- params = urllib.parse.urlencode(body)
- print(f" GET {url}?{params}")
- data = get(f"{url}?{params}", headers)
- if not data:
- print(" [SKIP] 无响应")
- continue
- code = data.get("code")
- msg = data.get("message", "")
- print(f" code={code} message={str(msg)[:100]}")
- if code not in (200, 0):
- continue
- print(f"\n [OK] Billboard 账号搜索 {label} 通过!")
- describe_keys(data)
- _find_and_print_list(data, "Billboard")
- return
- print(" [FAIL] Billboard 账号搜索均失败")
- def _find_and_print_list(data: dict, label: str):
- """在任意层级自动定位列表字段并打印第一条"""
- LIST_KEYS = ("kol_list", "user_list", "authors", "kols", "list",
- "items", "result", "data", "user_info")
- def search(obj, depth=0):
- if depth > 4 or not isinstance(obj, dict):
- return False
- for k, v in obj.items():
- if isinstance(v, list) and v and k in LIST_KEYS:
- print(f"\n [LIST] 路径 .{k},共 {len(v)} 条")
- print(f" 第一条完整数据:")
- print(json.dumps(v[0], ensure_ascii=False, indent=2)[:2500])
- return True
- for k, v in obj.items():
- if isinstance(v, dict):
- if search(v, depth + 1):
- return True
- return False
- if not search(data):
- print(f" [WARN] 未在响应中找到已知列表字段,完整 keys: {list(data.keys())}")
- # ─────────────── 4. 字段解析验证(纯 Python 模拟 TS 解析逻辑)───────────────
- def parse_xhs_creator(item: dict) -> dict:
- """模拟 justone.service.ts 中 parseXhsCreator 的解析逻辑"""
- raw_content = item.get("contentTags", [])
- content_tags = []
- for t in raw_content:
- if isinstance(t, str):
- content_tags.append(t)
- elif isinstance(t, dict):
- tag = t.get("taxonomy1Tag")
- if isinstance(tag, str):
- content_tags.append(tag)
- feature_tags = [v for v in item.get("featureTags", []) if isinstance(v, str)]
- fans = item.get("fansNum") or item.get("fansCount") or 0
- image_price = item.get("picturePrice") or item.get("imagePrice") or 0
- video_price = item.get("videoPrice") or 0
- min_price = item.get("lowerPrice") or image_price or 0
- cooperate = "active" if item.get("cooperateState") == 1 else ""
- gender_map = {"女": "female", "男": "male", "FEMALE": "female", "MALE": "male"}
- gender = gender_map.get(str(item.get("gender", "")), str(item.get("gender", "")))
- return {
- "userId": item.get("userId") or item.get("user_id") or "",
- "nickname": item.get("name") or item.get("nickname") or "",
- "redId": item.get("redId") or "",
- "location": item.get("location") or item.get("city") or "",
- "fansCount": int(fans),
- "imagePrice": float(image_price),
- "videoPrice": float(video_price),
- "minPrice": float(min_price),
- "cooperationStatus": cooperate,
- "personalTags": feature_tags,
- "contentTags": content_tags,
- "gender": gender,
- }
- def parse_douyin_xingtu_creator(item: dict) -> dict:
- """模拟 justone.service.ts 中 parseDouyinXingtuCreator 的解析逻辑"""
- attr = item.get("attribute_datas") or {}
- fans = int(attr.get("follower") or attr.get("fans_count") or 0)
- gender_map = {"1": "male", "2": "female", "0": ""}
- gender = gender_map.get(str(attr.get("gender", "0")), "")
- # 从 last_10_items 提取视频标题中的 hashtag 作为 contentTags
- content_tags = []
- try:
- last_items = json.loads(attr.get("last_10_items") or "[]")
- for it in last_items[:5]:
- title = it.get("item_title", "")
- tags = [t.lstrip("#") for t in title.split() if t.startswith("#")]
- content_tags.extend(tags)
- content_tags = list(dict.fromkeys(content_tags))[:10] # 去重限10个
- except Exception:
- pass
- # keyword_association 作为 personalTags
- personal_tags = []
- try:
- kw_map = json.loads(attr.get("author_thin_mid_word_association_index") or "{}")
- personal_tags = list(kw_map.keys())
- except Exception:
- pass
- return {
- "userId": str(item.get("star_id") or attr.get("id") or attr.get("core_user_id") or ""),
- "nickname": str(attr.get("nickname") or attr.get("name") or ""),
- "location": str(attr.get("city") or attr.get("province") or ""),
- "fansCount": fans,
- "imagePrice": 0,
- "videoPrice": 0,
- "minPrice": 0,
- "cooperationStatus": "active" if attr.get("author_status") == "1" else "",
- "personalTags": personal_tags,
- "contentTags": content_tags,
- "gender": gender,
- }
- def test_parse_validation():
- print_section("4. 字段解析验证(Python 模拟解析,不调用接口)")
- # ── XHS 验证 ──
- print("\n [XHS] 模拟解析第一条 kol 数据...")
- xhs_sample = {
- "userId": "5f869dc2000000000101db7c",
- "name": "imtender.",
- "redId": "1011627787",
- "location": "山东 潍坊 潍城区",
- "fansCount": 0, # 始终为 0,正确字段是 fansNum
- "fansNum": 20022,
- "picturePrice": 0.0,
- "videoPrice": 4000.0,
- "lowerPrice": 4000.0,
- "cooperateState": 1,
- "gender": "女",
- "contentTags": [
- {"taxonomy1Tag": "护肤", "taxonomy2Tags": ["护肤合集"]},
- {"taxonomy1Tag": "个人护理", "taxonomy2Tags": ["身体护理", "头发产品"]},
- ],
- "featureTags": ["沉浸式", "vlog", "氛围感", "抗老", "控油", "开箱"],
- }
- parsed_xhs = parse_xhs_creator(xhs_sample)
- print(f" nickname = {parsed_xhs['nickname']!r} (期望: 'imtender.')")
- print(f" fansCount = {parsed_xhs['fansCount']} (期望: 20022)")
- print(f" imagePrice = {parsed_xhs['imagePrice']} (期望: 0.0)")
- print(f" videoPrice = {parsed_xhs['videoPrice']} (期望: 4000.0)")
- print(f" minPrice = {parsed_xhs['minPrice']} (期望: 4000.0)")
- print(f" contentTags = {parsed_xhs['contentTags']} (期望: ['护肤', '个人护理'])")
- print(f" personalTags = {parsed_xhs['personalTags'][:3]} (期望: ['沉浸式', 'vlog', '氛围感']...)")
- print(f" gender = {parsed_xhs['gender']!r} (期望: 'female')")
- print(f" cooperationStatus = {parsed_xhs['cooperationStatus']!r} (期望: 'active')")
- xhs_ok = (
- parsed_xhs["nickname"] == "imtender."
- and parsed_xhs["fansCount"] == 20022
- and parsed_xhs["videoPrice"] == 4000.0
- and "护肤" in parsed_xhs["contentTags"]
- and parsed_xhs["gender"] == "female"
- and parsed_xhs["cooperationStatus"] == "active"
- )
- print(f"\n XHS 解析{'[PASS]' if xhs_ok else '[FAIL]'}")
- # ── Douyin 验证 ──
- print("\n [Douyin] 模拟解析第一条 author 数据...")
- dy_sample = {
- "star_id": "7174756942059929657",
- "attribute_datas": {
- "follower": "2161358",
- "gender": "1",
- "city": "湖州",
- "author_status": "1",
- "author_thin_mid_word_association_index": '{"服帖":0.33,"清爽":0.15}',
- "last_10_items": json.dumps([
- {"item_title": "愿我们都能在梦幻的世界里被童话治愈 #艾莎 #沉浸式护肤 #解压", "like_cnt": "2158"},
- {"item_title": "给男友挑战黄色护肤 #沉浸式护肤 #情侣日常", "like_cnt": "3837"},
- ]),
- },
- }
- parsed_dy = parse_douyin_xingtu_creator(dy_sample)
- print(f" userId = {parsed_dy['userId']!r} (期望: '7174756942059929657')")
- print(f" fansCount = {parsed_dy['fansCount']} (期望: 2161358)")
- print(f" gender = {parsed_dy['gender']!r} (期望: 'male')")
- print(f" location = {parsed_dy['location']!r} (期望: '湖州')")
- print(f" contentTags = {parsed_dy['contentTags']} (期望含: '沉浸式护肤')")
- print(f" personalTags = {parsed_dy['personalTags']} (期望: ['服帖', '清爽'])")
- print(f" cooperationStatus = {parsed_dy['cooperationStatus']!r} (期望: 'active')")
- dy_ok = (
- parsed_dy["userId"] == "7174756942059929657"
- and parsed_dy["fansCount"] == 2161358
- and parsed_dy["gender"] == "male"
- and any("护肤" in t for t in parsed_dy["contentTags"])
- and "服帖" in parsed_dy["personalTags"]
- and parsed_dy["cooperationStatus"] == "active"
- )
- print(f"\n Douyin 解析{'[PASS]' if dy_ok else '[FAIL]'}")
- if xhs_ok and dy_ok:
- print("\n [PASS] 所有字段解析验证通过,可以更新 justone.service.ts")
- else:
- print("\n [FAIL] 解析有误,需先修复 Python 逻辑再对应修改 TS 服务")
- # ─────────────── 主程序 ───────────────
- if __name__ == "__main__":
- print("=" * 60)
- print(" API 结构探针 — 关键词:", KEYWORD)
- print(" 每平台只调用 1 次,节省成本")
- print("=" * 60)
- test_xhs_justone() # 小红书:JustOne 蒲公英
- test_douyin_justone() # 抖音:JustOne 星图(301→重试→成功)
- test_douyin_tikhub_xingtu() # 抖音:TikHub 星图(GET+POST,修正方法)
- test_douyin_tikhub_billboard() # 抖音:TikHub Billboard 账号搜索(新增)
- test_parse_validation() # 纯 Python 字段解析验证(不调接口)
- print("\n" + "=" * 60)
- print(" 探针完成。根据结果决定抖音数据源:")
- print(" - TikHub Xingtu POST 通:使用 TikHub(有定价),成本低")
- print(" - TikHub Billboard 通:可搜索用户,但无定价")
- print(" - 均失败:继续用 JustOne 星图(需 301 重试,有定价)")
- print("=" * 60)
|