overseas-trending-search
海外跨平台热文搜索 — 输入任意关键词(中文/英文),一次搜索 X / TikTok / YouTube 三平台热门内容,每平台 Top N(默认5)统一列表(平台/标题/作者/播放/点赞/评论/发布时间/链接),终端分组表格 + CSV 导出 + 交互式 HTML 报告(卡片/表格双视图)。按关键词语言智能优先同语言内容(中文词→中文优先,英文词→英文优先,其余语言兜底)。当用户需要搜索海外平台热点、跨平台内容对比、话题/竞品舆情监控、选题素材采集时使用。
Install
npx skills add https://github.com/redfox-data/redfox-community/tree/main/skills/overseas-trending-search
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install redfox-data-redfox-community@llmmart
git clone https://github.com/redfox-data/redfox-community.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole redfox-data/redfox-community collection as a plugin from our marketplace. Git is the plain clone.
README
海外跨平台热文搜索 / overseas-trending-search
简介
海外跨平台热文搜索是一款跨平台热点洞察工具,输入任意关键词(中文/英文),即可一键搜索 X、TikTok、YouTube 三大海外平台的热门内容,智能识别关键词语言并优先展示同语言结果,输出统一字段列表与交互式 HTML 报告。
核心价值
- 🔍 三平台覆盖:一键搜索 X / TikTok / YouTube 三大海外平台的热门内容
- 🌐 智能语言优先:中文关键词优先中文内容,英文关键词优先英文内容,解决跨语言歧义
- 📊 多格式输出:终端分组表格 + CSV 数据导出 + 交互式 HTML 报告(卡片/表格双视图)
- ⚡ 灵活排序:支持按播放数/点赞数/评论数/发布时间自由排序,每平台独立取 Top N
适用对象
- ✍️ 内容创作者 — 每日热点追踪,获取选题灵感与素材
- 📈 市场运营 — 跨平台内容对比,了解不同平台内容形态差异
- 🏢 品牌方 — 竞品舆情监控,查看海外用户真实讨论
- 📊 数据分析师 — 话题事件追踪,量化跨平台传播效果
功能特性
核心功能
- 🔍 三平台搜索:覆盖 X、TikTok、YouTube 三大海外平台的关键词搜索
- 🌐 语言智能优先:中文关键词各平台优先展示中文内容,英文关键词优先英文内容,其余语言兜底
- 📋 统一字段列表:平台、标题、作者、播放数、点赞数、评论数、发布时间、链接,跨平台一致
- 📊 多维度排序:支持按播放数/点赞数/评论数/发布时间排序,每平台独立取 Top N
- 📄 多格式输出:终端分组表格展示、CSV 数据导出、交互式 HTML 报告(卡片/表格双视图)
- 🔗 多关键词支持:英文逗号分隔多个关键词,一次搜索同时命中
- 🔄 错误容忍:某平台异常时自动跳过,不影响其他平台结果
使用指南
直接用自然语言描述需求,无需记忆命令。
常用说法速查
| 意图 | 示例话术 | 效果 |
|---|---|---|
| 热点搜索 | 「搜索 AI 在海外平台的热门内容」 | 三平台搜索 + 终端表格 + CSV + HTML |
| 指定平台 | 「帮我看 TikTok 上关于 AI 的热门视频」 | 仅搜索指定平台 |
| 跨平台对比 | 「对比 X 和 YouTube 上 iPhone 16 的热度」 | 指定平台对比分析 |
| 多关键词 | 「搜索 人工智能,AI agent 的海外热文」 | 同时搜索多个关键词 |
| 竞品监控 | 「搜一下 Tesla 在海外的讨论」 | 品牌竞品舆情追踪 |
| 素材采集 | 「导出 AI 相关的海外热文 CSV」 | 导出 CSV 做选题库 |
输出示例
搜索完成后,你将依次收到:
终端分组表格 → 按平台分组展示 Top N 结果,含标题、作者、播放/点赞/评论/发布时间、链接 CSV 导出 → 结构化数据导出,便于数据分析与存档 HTML 报告 → 交互式报告,支持卡片/表格双视图切换,可离线保存
使用场景
| 场景 | 角色 | 示例问法 | 收益 |
|---|---|---|---|
| 每日热点追踪 | 内容创作者 | 「搜索 AI 今天的热门内容」 | 掌握三平台当日最热内容 |
| 跨平台对比 | 市场运营 | 「对比 X 和 YouTube 上 Vision Pro 的热度」 | 了解不同平台内容形态差异 |
| 话题事件追踪 | 品牌方 | 「搜 新能源,EV 近 7 天传播情况」 | 追踪特定话题跨平台传播 |
| 竞品舆情监控 | 数据分析师 | 「搜一下 Tesla 在海外的真实讨论」 | 看海外用户真实反馈 |
| 素材灵感采集 | 自媒体运营 | 「导出 AI 相关海外热文 CSV」 | 导出数据做选题库 |
Skill manifest
海外跨平台热文搜索
输入任意关键词(中文/英文),一键搜索 X / TikTok / YouTube 三平台热门内容,每平台按点赞/播放/评论/发布时间取 Top N(默认 5),输出统一字段列表:平台、标题、作者、播放数、点赞数、评论数、发布时间、链接。
API 请求均携带
ChinaTrendingDigest-RedSkill标识。需先配置 API Key,通过环境变量 REDFOX_API_KEY 或 --api-key 参数传入。 架构为平台适配器模式:新平台(如 YouTube)只需新增适配器文件,主流程零改动。
使用场景
| 场景 | 示例 |
|---|---|
| 每日热点追踪 | 每天跑一次 "AI",掌握三平台当日最热内容 |
| 跨平台对比 | 同一关键词对比 X / TikTok / YouTube 的热度与内容形态差异 |
| 话题事件追踪 | 搜 "新能源,EV" 追踪特定话题跨平台传播 |
| 竞品/舆情监控 | 搜品牌或产品名,看海外用户的真实讨论与爆款反馈 |
| 素材灵感采集 | 导出 CSV 做选题库与数据分析 |
| 账号内容挖掘 | TikTok 侧支持按作者作品列表下钻(userAwemeList) |
使用方法
# 基础用法:单关键词(默认最近 24 小时,按播放数降序)
python3 "$SKILL_PATH/scripts/digest.py" "AI"
# 中文关键词 / 多关键词(英文逗号分隔)
python3 "$SKILL_PATH/scripts/digest.py" "人工智能,AI agent"
# 放宽时间窗口到最近 3 天(TikTok 热门多为历史内容,建议放宽)
python3 "$SKILL_PATH/scripts/digest.py" "AI" --days 3
# 只跑指定平台
python3 "$SKILL_PATH/scripts/digest.py" "AI" --platforms tiktok
# 按发布时间排序
python3 "$SKILL_PATH/scripts/digest.py" "AI" --sort time
# 按点赞数排序 / 调整每平台返回条数(默认 5)
python3 "$SKILL_PATH/scripts/digest.py" "AI" --sort likes --top 10
# 仅导出 CSV / 不自动打开浏览器
python3 "$SKILL_PATH/scripts/digest.py" "AI" --csv-only --no-open
CSV / HTML 默认保存在 ~/Downloads/QoderOverseasTrending/。
返回结果展示规范
向用户展示结果时,必须包含以下字段:
| 字段 | 说明 |
|---|---|
| 平台 | X / TikTok / YouTube(带平台标识色) |
| 标题 | 推文/视频文案摘要,仅展示前 20 字(HTML 表格悬停看全文) |
| 作者 | 作者昵称 |
| 播放数 | 千/万简写(如 47.3w),展示在作者之后 |
| 点赞数 | 千/万简写(如 62.9w) |
| 评论数 | 同上 |
| 发布时间 | YYYY-MM-DD HH:mm |
| 链接 | 可点击跳转原文 |
平台状态需在结果前明示:某平台上游故障时优雅降级并在报告中标注,不影响其他平台结果。
- 条数约束:每个平台最多返回
--topN 条(默认 5),按--sort指标在平台组内降序;终端与 HTML 均按平台分组展示。 - 语言优先分层:中文关键词时各平台组内优先展示中文内容、英文关键词优先英文内容,组内中/英文内容不足时以其他语言兜底;层内仍按排序指标降序。
- HTML 报告支持「卡片 / 表格」双视图切换,表格视图含点赞/评论/播放/分享全字段。
- YouTube 点赞/评论数由 videoDetail 详情接口逐条补全(默认全量覆盖搜索结果 20 条,本地化计数串已解析为整数)。
参数说明
| 参数 | 说明 | 默认值 |
|---|---|---|
keywords |
关键词,中英文均可,多词英文逗号分隔(位置参数) | — |
--days |
时间窗口:最近 N 天(0=不限) | 1 |
--platforms |
平台列表:x,tiktok,youtube 任意组合 |
x,tiktok,youtube |
--sort |
排序:views / likes / comments / time |
views |
--top |
每平台最多返回条数(0=不限) | 5 |
--output-dir |
输出目录 | ~/Downloads/QoderOverseasTrending |
--api-key |
指定 RedFox API Key | — |
--csv-only |
仅生成 CSV,不生成 HTML | — |
--no-open |
不自动打开浏览器 | — |
API Key 配置
任选一种方式配置个人 Key:
| 方式 | 命令 |
|---|---|
| 环境变量(推荐) | export REDFOX_API_KEY=ak_你的密钥 |
| 命令行参数 | --api-key ak_你的密钥 |
| 配置文件 | echo '{"api_key":"ak_你的密钥"}' > ~/.qoder/apis/redfox.json |
注册地址:redfox.hk
架构说明
scripts/
├── digest.py # 主编排器:关键词×平台采集 → 时间过滤 → 排序 → 输出
├── config.py # RedFox 网关地址、密钥加载(三级优先级)
└── sources/ # 平台适配器(新增平台只需加一个文件并在 __init__ 登记)
├── base.py # BaseSource:统一 schema + 递增延迟重试
├── x_source.py # X:search/tweetDetail/tweetComments
├── tiktok_source.py # TikTok:searchVideo(一条请求自带点赞/评论数)
└── youtube_source.py# YouTube:searchVideo 列表 + videoDetail 补点赞/评论
统一记录 schema:platform / title / url / author / likes / comments / views / publish_ts / publish_time / keyword / shares。
平台接口状态(2026-07 实测)
| 平台 | 接口 | 状态 |
|---|---|---|
| X | search / tweetDetail / tweetComments | ✅ 全部可用;search 必填 searchType(Top/Latest),缺失会误报 3203 |
| TikTok | searchVideo / awemeDetail / userAwemeList | ✅ 全部可用,searchVideo 自带全量互动数据 |
| YouTube | searchVideo / videoDetail / videoComments | ✅ 全部可用;searchVideo 返回播放数与相对发布时间,点赞/评论由 videoDetail 补全(Top 10) |
依赖
pip3 install requests
常见问题
Q:X 搜索返回 3203 报错?
A:X search 接口的 searchType 为必填参数(Top 热门 / Latest 最新),缺失时 RedFox 会返回误导性的 3203「X能力调用失败」。本技能已内置该参数;另外过期/无效的 tweetId 调 tweetDetail 也会报 3203。
Q:TikTok 搜索需要调详情接口补数据吗? A:不需要。searchVideo 一次请求即返回点赞/评论/播放/分享数和发布时间,日常概要足够。
Q:YouTube 的点赞/评论数据从哪里来的?
A:searchVideo 列表接口只返回播放数,点赞/评论需调 videoDetail 逐条补全。技能默认对搜索结果 20 条全量补详情(youtube_source.py 中 detail_top 可调小以省积分),因此所有视频都有真实点赞/评论数;仍为 0 代表该视频确实无点赞/评论。YouTube 平台本身不公开分享数,故分享列恒为 0。
Q:每平台返回多少条?
A:默认每平台按排序指标(点赞/播放/评论/发布时间)取 Top 5,--top N 可调(0=不限)。各平台独立分组、组内降序,互不影响。
Q:为什么默认参数下看不到 TikTok 内容?
A:TikTok searchVideo 返回的是热门视频,发布时间多为数天乃至数月前,默认 --days 1(最近 24 小时)会将其整体过滤。想看 TikTok 热门请加 --days 0 或 --days 7;过滤时终端会按平台提示被排除的条数。
Q:搜中文关键词,为什么 X 上的日文内容被排到后面了? A:技能内置语言优先分层:关键词为中文时,各平台组内优先展示中文内容、其次英文,日文等其他语言兜底(解决「南海」命中日文「南海電鉄」这类歧义问题);英文关键词同理优先英文内容。语言识别基于标题字符集(汉字/假名/谚文/拉丁字母),无第三方依赖。
Q:想做每日定时运行?
A:可以让 AI 注册定时任务,每天自动执行 digest.py 并生成报告。
Files (redfox-community)
-
assets
-
report_template.html 9.7 KB · in bundle
-
-
scripts
-
sources
-
base.py 5 KB
#!/usr/bin/env python3 """ 平台适配器抽象基类 — 所有平台实现统一接口,主流程对平台无感知 新增平台(如 YouTube)只需继承 BaseSource 并实现 search(),无需改动主流程 """ import re import time from datetime import datetime, timedelta class PlatformUnavailable(Exception): """平台上游能力不可用(如 RedFox 3203 能力故障),主流程应优雅降级跳过""" pass class BaseSource: """平台适配器基类""" #: 平台标识(x / tiktok / youtube) platform = "" #: 平台展示名 display_name = "" def search(self, session, keyword): """ 按关键词搜索,返回归一化记录列表(make_record 产物)。 平台上游故障时应抛出 PlatformUnavailable,由主流程降级处理。 """ raise NotImplementedError # ─── 归一化 ───────────────────────────────────────────────────────────────── def make_record(self, title, url, author, likes, comments, views, publish_ts, keyword, extra=None): """生成统一记录 schema,主流程只认这个结构""" record = { "platform": self.platform, "platform_name": self.display_name, "title": (title or "").strip() or "无标题", "url": url or "", "author": author or "未知作者", "likes": self._to_int(likes), "comments": self._to_int(comments), "views": self._to_int(views), "publish_ts": publish_ts or 0, "publish_time": self._fmt_ts(publish_ts), "keyword": keyword or "", } if extra: record.update(extra) return record # ─── 工具 ─────────────────────────────────────────────────────────────────── @staticmethod def _to_int(v): try: return int(v or 0) except (ValueError, TypeError): return 0 @staticmethod def _fmt_ts(ts): try: ts = int(ts) if ts > 10**12: # 毫秒级时间戳兼容 ts = ts // 1000 return datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S") except (ValueError, TypeError, OSError): return "" @staticmethod def _parse_ts(value): """兼容秒/毫秒时间戳、ISO 时间字符串与相对时间(如 "1 day ago"),返回秒级 int""" if value is None: return 0 if isinstance(value, (int, float)): ts = int(value) return ts // 1000 if ts > 10**12 else ts s = str(value).strip() if s.isdigit(): ts = int(s) return ts // 1000 if ts > 10**12 else ts for fmt in ("%Y-%m-%dT%H:%M:%S%z", "%Y-%m-%d %H:%M:%S", "%a %b %d %H:%M:%S %z %Y"): try: return int(datetime.strptime(s.replace("Z", "+0000"), fmt).timestamp()) except (ValueError, TypeError): continue return BaseSource._parse_relative_ts(s) #: 相对时间单位 → 秒数(月/年为近似值,概要场景足够) _REL_UNITS = { "second": 1, "minute": 60, "hour": 3600, "day": 86400, "week": 604800, "month": 2592000, "year": 31536000, } _REL_UNITS_ZH = { "秒": 1, "分钟": 60, "小时": 3600, "天": 86400, "周": 604800, "个月": 2592000, "月": 2592000, "年": 31536000, } @staticmethod def _parse_relative_ts(s): """解析相对时间:"3 hours ago" / "Streamed 1 day ago" / "2 天前" 等""" m = re.search(r"(\d+)\s*(second|minute|hour|day|week|month|year)s?\s*ago", s, re.I) if m: secs = int(m.group(1)) * BaseSource._REL_UNITS[m.group(2).lower()] return int((datetime.now() - timedelta(seconds=secs)).timestamp()) m = re.search(r"(\d+)\s*(个月|分钟|小时|秒|天|周|月|年)前", s) if m: secs = int(m.group(1)) * BaseSource._REL_UNITS_ZH[m.group(2)] return int((datetime.now() - timedelta(seconds=secs)).timestamp()) return 0 # ─── 稳健请求(递增延迟重试)──────────────────────────────────────────────── def post_json(self, session, url, payload, max_retries=3, timeout=30): """ POST JSON,失败(网络异常/非 JSON)递增延迟重试。 返回解析后的 dict;彻底失败返回 None。 """ for attempt in range(max_retries): try: resp = session.post(url, json=payload, timeout=timeout) return resp.json() except Exception: if attempt < max_retries - 1: time.sleep(0.5 * (attempt + 1)) return None -
tiktok_source.py 2.3 KB
#!/usr/bin/env python3 """ TikTok 平台适配器 接口已实测(2026-07): searchVideo POST {"keyword": "..."} → 20 条/次,自带点赞/评论/播放/发布时间 awemeDetail POST {"awemeId": "..."} → 单条详情(日常概要无需调用) userAwemeList POST {"secUserId": "..."} → 作者作品列表(备用) """ import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from config import TIKTOK_SEARCH_API, SOURCE, SUCCESS_CODES from .base import BaseSource, PlatformUnavailable class TikTokSource(BaseSource): platform = "tiktok" display_name = "TikTok" def search(self, session, keyword): result = self.post_json(session, TIKTOK_SEARCH_API, {"keyword": keyword, "source": SOURCE}) if result is None: raise PlatformUnavailable("TikTok 搜索请求失败(网络异常)") code = result.get("code") if code not in SUCCESS_CODES: msg = result.get("msg", "") if code == 3203: raise PlatformUnavailable(f"TikTok 上游能力故障: {msg}") raise PlatformUnavailable(f"TikTok 搜索接口错误 (code {code}): {msg}") data = result.get("data") or [] if not isinstance(data, list): return [] records = [] for item in data: stats = item.get("statsData") or {} author = item.get("authorData") or {} record = self.make_record( title=item.get("content") or "", url=item.get("shareLink") or "", author=author.get("userName") or author.get("userHandle") or "", likes=stats.get("likeCount"), comments=stats.get("commentTotal"), views=stats.get("viewCount"), publish_ts=self._parse_ts(item.get("publishTime")), keyword=keyword, extra={ "shares": self._to_int(stats.get("shareTotal")), "work_id": str(item.get("workId") or ""), "area": item.get("area") or "", "cover": (item.get("videoData") or {}).get("coverImage") or "", }, ) records.append(record) return records -
x_source.py 2.7 KB
#!/usr/bin/env python3 """ X (Twitter) 平台适配器 接口已实测(2026-07): search POST {"keyword": "...", "searchType": "Top", "cursor": ""} → data.tweets[](20条/页)+ nextCursor 分页 单条自带 likeCount/replyCount/retweetCount/viewCount/createdAt tweetDetail / tweetComments:备用(评论内容分析场景) 注意:searchType 为必填参数(Top=热门 / Latest=最新),缺失会报 3203 上游错误。 """ import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from config import X_SEARCH_API, SOURCE, SUCCESS_CODES from .base import BaseSource, PlatformUnavailable class XSource(BaseSource): platform = "x" display_name = "X" #: Top=热门推文(互动量高)/ Latest=最新推文 search_type = "Top" def search(self, session, keyword): payload = { "keyword": keyword, "searchType": self.search_type, "cursor": "", "source": SOURCE, } result = self.post_json(session, X_SEARCH_API, payload) if result is None: raise PlatformUnavailable("X 搜索请求失败(网络异常)") code = result.get("code") if code not in SUCCESS_CODES: msg = result.get("msg", "") if code == 3203: raise PlatformUnavailable(f"X 上游能力故障(RedFox 侧): {msg}") raise PlatformUnavailable(f"X 搜索接口错误 (code {code}): {msg}") data = result.get("data") or {} tweets = data.get("tweets") if isinstance(data, dict) else None if not isinstance(tweets, list): return [] return [self._normalize_item(t, keyword) for t in tweets] def _normalize_item(self, item, keyword): user = item.get("user") or {} username = item.get("username") or user.get("username") or "" tweet_id = str(item.get("tweetId") or "") url = (f"https://x.com/{username}/status/{tweet_id}" if username and tweet_id else "") return self.make_record( title=item.get("text") or "", url=url, author=user.get("displayName") or username, likes=item.get("likeCount"), comments=item.get("replyCount"), views=item.get("viewCount"), publish_ts=self._parse_ts(item.get("createdAt")), keyword=keyword, extra={ "shares": self._to_int(item.get("retweetCount")), "work_id": tweet_id, "language": item.get("language") or "", "followers": self._to_int(user.get("followers")), }, ) -
youtube_source.py 6.4 KB
#!/usr/bin/env python3 """ YouTube 平台适配器 接口已实测(2026-07): searchVideo POST {"searchQuery": "..."} → data.videos[](20条/次):title / videoId / author / channelId / viewCount(原始整数)/ duration / publishedTime(相对时间,如 "1 day ago")/ thumbnails[] videoDetail POST {"videoId": "..."} → likeCount / commentCount(本地化展示串,如 "1928万"、"244万")、 date(中文日期,如 "2026年7月24日")、videoUrl / channelHandle / description videoComments POST {"videoId": "..."} → data.comments[] + continuationToken(评论内容分析场景备用) 注意:searchVideo 不含点赞/评论数,需对候选视频调 videoDetail 补全; 为控制积分消耗,默认只对 viewCount 前 detail_top 条补详情,其余回退为 0。 """ import re import sys import time from datetime import datetime from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from config import YOUTUBE_SEARCH_API, YOUTUBE_DETAIL_API, SOURCE, SUCCESS_CODES from .base import BaseSource, PlatformUnavailable class YouTubeSource(BaseSource): platform = "youtube" display_name = "YouTube" #: 对 viewCount 前 N 条调详情补点赞/评论。searchVideo 每次返回 20 条, #: 取 20 即全量补全——热文按点赞排序,漏拉详情会导致高赞视频排不上来 detail_top = 20 def search(self, session, keyword): result = self.post_json(session, YOUTUBE_SEARCH_API, {"searchQuery": keyword, "source": SOURCE}) if result is None: raise PlatformUnavailable("YouTube 搜索请求失败(网络异常)") code = result.get("code") if code not in SUCCESS_CODES: msg = result.get("msg", "") if code == 3203: raise PlatformUnavailable(f"YouTube 上游能力故障: {msg}") raise PlatformUnavailable(f"YouTube 搜索接口错误 (code {code}): {msg}") data = result.get("data") or {} videos = data.get("videos") if isinstance(data, dict) else None if not isinstance(videos, list): return [] # 按播放量排序,只对前 N 条调详情(点赞/评论数仅详情接口提供) videos = [v for v in videos if isinstance(v, dict)] videos.sort(key=lambda v: self._to_int(v.get("viewCount")), reverse=True) details = {} for v in videos[:self.detail_top]: vid = str(v.get("videoId") or "") if not vid: continue detail = self._fetch_detail(session, vid) if detail: details[vid] = detail time.sleep(0.2) return [self._normalize_item(v, keyword, details.get(str(v.get("videoId") or ""))) for v in videos] def _fetch_detail(self, session, video_id): result = self.post_json(session, YOUTUBE_DETAIL_API, {"videoId": video_id}) if result and result.get("code") in SUCCESS_CODES: data = result.get("data") if isinstance(data, dict) and data: return data return None def _normalize_item(self, item, keyword, detail=None): detail = detail or {} vid = str(item.get("videoId") or "") cover = "" thumbs = item.get("thumbnails") if isinstance(thumbs, list) and thumbs: best = max(thumbs, key=lambda t: int(t.get("width") or 0) * int(t.get("height") or 0)) cover = best.get("url") or "" # 发布时间:优先详情精确日期("2026年7月24日"),回退列表相对时间("1 day ago") publish_ts = (self._parse_cn_date(detail.get("date")) or self._parse_ts(item.get("publishedTime"))) return self.make_record( title=item.get("title") or detail.get("title") or "", url=detail.get("videoUrl") or (f"https://www.youtube.com/watch?v={vid}" if vid else ""), author=item.get("author") or detail.get("author") or "", likes=self._parse_display_count(detail.get("likeCount")), comments=self._parse_display_count(detail.get("commentCount")), views=self._to_int(item.get("viewCount")) or self._parse_display_count(detail.get("viewCount")), publish_ts=publish_ts, keyword=keyword, extra={ "shares": 0, "work_id": vid, "cover": cover, "duration": item.get("duration") or "", "channel_id": item.get("channelId") or "", "channel_handle": detail.get("channelHandle") or "", }, ) # ─── 本地化展示串解析 ──────────────────────────────────────────────────────── @staticmethod def _parse_display_count(value): """ 解析 YouTube 本地化计数字符串 → int 兼容:"1928万" / "1,797,749,387次观看" / "244万" / "1.9M" / "24K" / 原始整数 """ if value is None: return 0 if isinstance(value, (int, float)): return int(value) text = str(value).strip().replace(",", "").replace(" ", "") m = re.match(r"^([\d.]+)(.*)$", text) if not m: return 0 try: num = float(m.group(1)) except ValueError: return 0 suffix = m.group(2).upper() if "亿" in suffix: mult = 100_000_000 elif "万" in suffix: mult = 10_000 elif "千" in suffix: mult = 1_000 elif suffix.startswith("B"): mult = 1_000_000_000 elif suffix.startswith("M"): mult = 1_000_000 elif suffix.startswith("K"): mult = 1_000 else: mult = 1 return int(num * mult) @staticmethod def _parse_cn_date(value): """中文日期 "2026年7月24日" → 秒级时间戳""" if not value: return 0 m = re.match(r"^(\d{4})年(\d{1,2})月(\d{1,2})日$", str(value).strip()) if not m: return 0 try: return int(datetime(int(m.group(1)), int(m.group(2)), int(m.group(3))).timestamp()) except ValueError: return 0 -
__init__.py 304 B
from .base import BaseSource, PlatformUnavailable from .tiktok_source import TikTokSource from .x_source import XSource from .youtube_source import YouTubeSource # 平台注册表:新增平台只需在此登记 SOURCES = { "x": XSource, "tiktok": TikTokSource, "youtube": YouTubeSource, }
-
-
config.py 3.2 KB
#!/usr/bin/env python3 """ overseas-trending-search — 统一配置与密钥加载 密钥加载优先级:--api-key 参数 > REDFOX_API_KEY 环境变量 > ~/.qoder/apis/redfox.json """ import json import os from pathlib import Path # ─── RedFox 网关 ────────────────────────────────────────────────────────────────── BASE_URL = "https://redfox.hk/story/api" X_SEARCH_API = f"{BASE_URL}/x/search" X_DETAIL_API = f"{BASE_URL}/x/tweetDetail" X_COMMENTS_API = f"{BASE_URL}/x/tweetComments" TIKTOK_SEARCH_API = f"{BASE_URL}/tiktok/ability/searchVideo" TIKTOK_DETAIL_API = f"{BASE_URL}/tiktok/ability/awemeDetail" TIKTOK_USER_AWEME_API = f"{BASE_URL}/tiktok/ability/userAwemeList" # YouTube 接口(2026-07 实测):searchVideo 列表 + videoDetail 补点赞/评论 YOUTUBE_SEARCH_API = f"{BASE_URL}/youtube/searchVideo" YOUTUBE_DETAIL_API = f"{BASE_URL}/youtube/videoDetail" YOUTUBE_COMMENTS_API = f"{BASE_URL}/youtube/videoComments" # ─── 密钥与渠道 ──────────────────────────────────────────────────────────────────── CONFIG_FILE = Path.home() / ".qoder" / "apis" / "redfox.json" ENV_KEY = "REDFOX_API_KEY" SOURCE = "ChinaTrendingDigest-RedSkill" DEFAULT_OUTPUT_DIR = Path.home() / "Downloads" / "QoderOverseasTrending" SUCCESS_CODES = (200, 2000) def get_api_key(cli_key=None): """按优先级加载 RedFox API Key,找不到返回 None""" if cli_key: return cli_key env_key = os.environ.get(ENV_KEY) if env_key: return env_key if CONFIG_FILE.exists(): try: data = json.loads(CONFIG_FILE.read_text()) key = data.get("api_key") if key: return key except (json.JSONDecodeError, OSError): pass return None def make_session(api_key): """构造带认证头的 requests.Session(RedFox 网关统一 X-API-KEY 认证)""" import requests session = requests.Session() session.headers.update({ "Content-Type": "application/json", "X-API-KEY": api_key, }) return session def print_no_key_guide(): print("╔══════════════════════════════════════════════════════╗") print("║ 未配置 API Key,请通过以下方式之一配置: ║") print("║ ║") print("║ export REDFOX_API_KEY=ak_你的密钥 ║") print("║ python3 digest.py --api-key ak_你的密钥 ║") print("║ echo '{\"api_key\":\"ak_你的密钥\"}' > ~/.qoder/apis/redfox.json ║") print("║ ║") print("║ 注册获取 Key: https://redfox.hk/settings/api-keys ║") print("╚══════════════════════════════════════════════════════╝") -
digest.py 18.7 KB
#!/usr/bin/env python3 """ 海外跨平台热文搜索 — 主编排器 ============================== 按用户输入的关键词(中文/英文,逗号分隔多词),搜索 X / TikTok / YouTube 热门内容,每平台取 Top N,归一化为统一列表(平台/标题/作者/播放/点赞/评论/发布时间/链接), 输出终端分组表格 + CSV + 交互式 HTML 报告。 Usage: python3 digest.py "AI" python3 digest.py "人工智能,AI agent" --days 3 python3 digest.py "AI" --platforms tiktok --sort time python3 digest.py "AI" --sort views --top 5 python3 digest.py "AI" --csv-only --no-open """ import argparse import csv import json import os import subprocess import sys import time from datetime import datetime, timedelta from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent)) from config import (DEFAULT_OUTPUT_DIR, get_api_key, make_session, print_no_key_guide) from sources import SOURCES from sources.base import PlatformUnavailable # ─── 终端颜色 ────────────────────────────────────────────────────────────────────── GREEN = "\033[92m" YELLOW = "\033[93m" RED = "\033[91m" CYAN = "\033[96m" BOLD = "\033[1m" RESET = "\033[0m" def info(msg): print(f"{GREEN}[✓]{RESET} {msg}") def warn(msg): print(f"{YELLOW}[!]{RESET} {msg}") def error(msg): print(f"{RED}[✗]{RESET} {msg}") def step(msg): print(f"{CYAN}[→]{RESET} {msg}") def format_number(n): n = int(n or 0) if n >= 10000: return f"{n/10000:.1f}w" if n >= 1000: return f"{n/1000:.1f}k" return str(n) # ─── 语言识别与优先分层 ────────────────────────────────────────────────────────── def detect_lang(text): """轻量语言识别(无第三方依赖): zh=含汉字且无假名(简/繁中文),ja=含假名,ko=含谚文,en=含拉丁字母,other=其余。 中英混排(如「中文标题 with English」)按 zh 计。""" has_han = has_kana = has_hangul = has_latin = False for ch in text or "": o = ord(ch) if 0x4E00 <= o <= 0x9FFF or 0x3400 <= o <= 0x4DBF: has_han = True elif 0x3040 <= o <= 0x30FF: has_kana = True elif 0xAC00 <= o <= 0xD7AF: has_hangul = True elif o < 0x80 and ch.isalpha(): has_latin = True if has_han: return "ja" if has_kana else "zh" if has_kana: return "ja" if has_hangul: return "ko" if has_latin: return "en" return "other" def keyword_pref_lang(keywords): """由关键词推断优先语言:含汉字→zh,纯拉丁→en,日文/韩文词同理;无法识别→None(不分层)。""" lang = detect_lang(" ".join(keywords)) return lang if lang in ("zh", "en", "ja", "ko") else None def lang_tier(lang, pref): """语言分层:0=关键词同语言,1=中文/英文,2=其他语言。pref 为 None 时不分层。""" if not pref or lang == pref: return 0 if lang in ("zh", "en"): return 1 return 2 # ─── 采集流程 ──────────────────────────────────────────────────────────────────── def collect(session, keywords, platforms): """ 平台 × 关键词 循环采集。 返回 (records, platform_status) — platform_status: {platform: "ok"/错误信息} """ records = [] seen = set() platform_status = {} for pf in platforms: source_cls = SOURCES.get(pf) if not source_cls: warn(f"未知平台「{pf}」,已跳过(可选: {', '.join(SOURCES)})") continue source = source_cls() pf_ok = False for kw in keywords: step(f"[{source.display_name}] 搜索「{kw}」...") try: items = source.search(session, kw) except PlatformUnavailable as e: warn(f"[{source.display_name}] 不可用,已跳过: {e}") platform_status[pf] = str(e) break except Exception as e: warn(f"[{source.display_name}] 关键词「{kw}」搜索异常: {e}") continue added = 0 for r in items: dedup_key = r.get("url") or f"{pf}:{r.get('work_id')}" if dedup_key and dedup_key not in seen: seen.add(dedup_key) r["lang"] = detect_lang(r.get("title") or "") records.append(r) added += 1 step(f" +{added} 条(全局 {len(records)} 条)") pf_ok = True time.sleep(0.3) if pf_ok: platform_status[pf] = "ok" return records, platform_status def filter_by_days(records, days): """按时间窗口过滤(publish_ts 缺失或超窗的记录排除,按平台统计提示)""" if days <= 0: return records cutoff = (datetime.now() - timedelta(days=days)).timestamp() kept, dropped = [], [] for r in records: ts = int(r.get("publish_ts") or 0) if ts and ts >= cutoff: kept.append(r) else: dropped.append(r) if dropped: drop_cnt, keep_cnt = {}, {} for r in dropped: pf = r.get("platform") or "?" drop_cnt[pf] = drop_cnt.get(pf, 0) + 1 for r in kept: pf = r.get("platform") or "?" keep_cnt[pf] = keep_cnt.get(pf, 0) + 1 def _name(pf): return SOURCES[pf].display_name if pf in SOURCES else pf detail = ",".join(f"{_name(pf)} {n} 条" for pf, n in drop_cnt.items()) warn(f"{len(dropped)} 条发布时间缺失或超出最近 {days} 天窗口,已排除({detail})") for pf, n in drop_cnt.items(): if not keep_cnt.get(pf): warn(f"{_name(pf)} {n} 条均为窗口外的历史热门内容,如需查看请加 --days 0") return kept SORT_LABELS = {"likes": "点赞数", "views": "播放数", "comments": "评论数", "time": "发布时间"} def _sort_key(r, sort_by): """排序键:主指标降序,播放数/发布时间作次序兜底。 某平台主指标全为 0 时(如 YouTube 无点赞数)自动回退到播放数排序。""" views = int(r.get("views") or 0) likes = int(r.get("likes") or 0) comments = int(r.get("comments") or 0) ts = int(r.get("publish_ts") or 0) if sort_by == "time": return (ts, views, likes) if sort_by == "views": return (views, likes, ts) if sort_by == "comments": return (comments, views, likes, ts) return (likes, views, ts) # likes def sort_records(records, sort_by, pref_lang=None): """先按指标降序,再按语言分层稳定排序:关键词同语言 > 中/英文 > 其他语言。""" records.sort(key=lambda r: _sort_key(r, sort_by), reverse=True) if pref_lang: records.sort(key=lambda r: lang_tier(r.get("lang"), pref_lang)) return records def limit_per_platform(records, top, sort_by, pref_lang=None): """按平台分组 → 组内排序(语言分层 + 指标降序)→ 每组截取前 N(0=不限)→ 按平台顺序合并""" groups, order = {}, [] for r in records: pf = r.get("platform") or "?" if pf not in groups: groups[pf] = [] order.append(pf) groups[pf].append(r) merged = [] for pf in order: g = sort_records(groups[pf], sort_by, pref_lang) merged.extend(g if top <= 0 else g[:top]) return merged # ─── 终端表格 ────────────────────────────────────────────────────────────────────── def print_terminal_table(records, keywords, platform_status, sort_by, top, pref_lang=None): kw_label = " + ".join(keywords) sort_label = SORT_LABELS.get(sort_by, sort_by) top_label = f"每平台 Top {top}" if top > 0 else "不限条数" lang_label = {"zh": "中文优先", "en": "English 优先"}.get(pref_lang) lang_suffix = f" · {lang_label}" if lang_label else "" print(f"\n{BOLD}{'=' * 128}{RESET}") print(f"{BOLD} 海外跨平台热文搜索 · 「{kw_label}」· 共 {len(records)} 条 · {top_label} · 按{sort_label}降序{lang_suffix}{RESET}") print(f"{BOLD}{'=' * 128}{RESET}") status_parts = [] for pf, st in platform_status.items(): name = SOURCES[pf].display_name if pf in SOURCES else pf status_parts.append(f"{name}: {'✓' if st == 'ok' else '✗ ' + st}") if status_parts: print(f" {CYAN}平台状态:{' | '.join(status_parts)}{RESET}") if not records: print(f" {YELLOW}无结果。可尝试放宽时间窗口(--days 0 不限)或更换关键词{RESET}\n") return # 按平台分组展示(records 已按平台分组排好序) groups, order = {}, [] for r in records: pf = r.get("platform") or "?" if pf not in groups: groups[pf] = [] order.append(pf) groups[pf].append(r) header = (f" {'序号':<4}{'平台':<8}{'标题':<24}{'作者':<14}" f"{'播放':>7}{'点赞':>7}{'评论':>7}{'发布时间':<18}{'链接':<30}") idx = 0 for pf in order: g = groups[pf] name = g[0].get("platform_name") or pf print(f"\n {CYAN}{BOLD}▎{name}({len(g)} 条 · 按{sort_label}){RESET}") print(f" {YELLOW}{'─' * 110}{RESET}") print(f" {YELLOW}{header}{RESET}") print(f" {YELLOW}{'─' * 110}{RESET}") for r in g: idx += 1 pf_name = (r.get("platform_name") or r.get("platform") or "")[:6] title = r.get("title", "") title = (title[:20] + "…") if len(title) > 20 else title author = r.get("author", "") author = (author[:11] + "..") if len(author) > 13 else author views = format_number(r.get("views")) likes = format_number(r.get("likes")) if int(r.get("likes") or 0) else "-" comments = format_number(r.get("comments")) if int(r.get("comments") or 0) else "-" pub = (r.get("publish_time") or "")[:16] url = r.get("url", "") url = (url[:27] + "..") if len(url) > 29 else url print(f" {idx:<4}{pf_name:<8}{title:<24}{author:<14}" f"{views:>7}{likes:>7}{comments:>7}{pub:<18}{url:<30}") print(f" {YELLOW}{'─' * 110}{RESET}") print() # ─── CSV 导出 ────────────────────────────────────────────────────────────────────── def export_csv(records, keyword, output_dir): output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) date_str = datetime.now().strftime("%Y-%m-%d") filepath = output_dir / f"海外热文_{keyword}_{date_str}.csv" fieldnames = ["平台", "标题", "作者", "播放/阅读数", "点赞数", "评论数", "分享数", "发布时间", "链接", "命中关键词"] with open(filepath, "w", newline="", encoding="utf-8-sig") as f: writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() for r in records: writer.writerow({ "平台": r.get("platform_name", ""), "标题": r.get("title", ""), "作者": r.get("author", ""), "播放/阅读数": r.get("views", 0), "点赞数": r.get("likes", 0), "评论数": r.get("comments", 0), "分享数": r.get("shares", 0), "发布时间": r.get("publish_time", ""), "链接": r.get("url", ""), "命中关键词": r.get("keyword", ""), }) return filepath # ─── HTML 报告 ──────────────────────────────────────────────────────────────────── def generate_html(records, keyword, platform_status): template_path = Path(__file__).parent.parent / "assets" / "report_template.html" template = template_path.read_text(encoding="utf-8") status_label = " | ".join( f"{SOURCES[pf].display_name if pf in SOURCES else pf}: " f"{'正常' if st == 'ok' else st}" for pf, st in platform_status.items() ) html = template html = html.replace("{{KEYWORD}}", keyword) html = html.replace("{{DATE}}", datetime.now().strftime("%Y-%m-%d")) html = html.replace("{{TIMESTAMP}}", datetime.now().strftime("%Y-%m-%d %H:%M:%S")) html = html.replace("{{TOTAL_COUNT}}", str(len(records))) html = html.replace("{{PLATFORM_STATUS}}", status_label) html = html.replace("{{INITIAL_DATA}}", json.dumps(records, ensure_ascii=False)) return html # ─── 主流程 ──────────────────────────────────────────────────────────────────────── def main(): parser = argparse.ArgumentParser( description="海外跨平台热文搜索 — X / TikTok / YouTube", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: python3 digest.py "AI" python3 digest.py "人工智能,AI agent" --days 3 python3 digest.py "AI" --platforms tiktok --sort time python3 digest.py "AI" --sort views --top 5 python3 digest.py "AI" --csv-only --no-open """, ) parser.add_argument("keywords", nargs="?", default="", help="搜索关键词,中文/英文均可,多词用英文逗号分隔") parser.add_argument("--days", type=int, default=1, help="时间窗口:最近 N 天(默认 1,0=不限)") parser.add_argument("--platforms", default="x,tiktok,youtube", help="平台列表,逗号分隔(默认 x,tiktok,youtube)") parser.add_argument("--sort", default="views", choices=["likes", "views", "comments", "time"], help="排序: views(默认) / likes / comments / time") parser.add_argument("--top", type=int, default=5, help="每平台最多返回条数(默认 5,0=不限)") parser.add_argument("--output-dir", help="输出目录(默认 ~/Downloads/QoderChinaDigest)") parser.add_argument("--api-key", help="RedFox API Key") parser.add_argument("--csv-only", action="store_true", help="仅生成 CSV") parser.add_argument("--no-open", action="store_true", help="不自动打开浏览器") args = parser.parse_args() banner = f"""{CYAN}{BOLD} ╔══════════════════════════════════════════════╗ ║ 海外跨平台热文搜索 ║ ║ Overseas Trending Search ║ ║ X / TikTok / YouTube · 每日热文一览 ║ ╚══════════════════════════════════════════════╝{RESET} """ print(banner) try: import requests # noqa: F401 except ImportError: error("缺少 requests 库,请安装: pip3 install requests") sys.exit(1) api_key = get_api_key(cli_key=args.api_key) if not api_key: print_no_key_guide() sys.exit(1) if not args.keywords: try: args.keywords = input(f"{CYAN}请输入关键词(多词用逗号分隔): {RESET}").strip() except (EOFError, KeyboardInterrupt): print() sys.exit(0) keywords = [k.strip() for k in args.keywords.split(",") if k.strip()] if not keywords: error("关键词不能为空") sys.exit(1) platforms = [p.strip().lower() for p in args.platforms.split(",") if p.strip()] output_dir = os.path.expanduser(args.output_dir or str(DEFAULT_OUTPUT_DIR)) session = make_session(api_key) # ── 采集 ── records, platform_status = collect(session, keywords, platforms) # ── 语言优先分层(中文词→中文优先,英文词→英文优先,其余语言兜底)── pref_lang = keyword_pref_lang(keywords) # ── 时间过滤 + 每平台 Top N ── records = filter_by_days(records, args.days) records = limit_per_platform(records, args.top, args.sort, pref_lang) # ── 终端表格 ── print_terminal_table(records, keywords, platform_status, args.sort, args.top, pref_lang) if not records: sys.exit(0) # ── 统计 ── total_likes = sum(int(r.get("likes") or 0) for r in records) pf_counter = {} for r in records: pf_counter[r.get("platform_name", "?")] = pf_counter.get(r.get("platform_name", "?"), 0) + 1 pf_dist = " | ".join(f"{k} {v}" for k, v in pf_counter.items()) print(f" {BOLD}统计:{RESET} 共 {len(records)} 条 | {pf_dist} | 总点赞 {format_number(total_likes)}") # ── CSV ── main_kw = keywords[0] csv_path = export_csv(records, main_kw, output_dir) info(f"CSV 已保存: {csv_path}") # ── HTML ── if not args.csv_only: step("生成 HTML 报告 ...") html_content = generate_html(records, " + ".join(keywords), platform_status) html_path = Path(output_dir) / f"海外热文_{main_kw}_{datetime.now().strftime('%Y-%m-%d')}.html" Path(output_dir).mkdir(parents=True, exist_ok=True) html_path.write_text(html_content, encoding="utf-8") info(f"HTML 报告已保存: {html_path}") if not args.no_open: step("打开浏览器...") try: subprocess.run(["open", str(html_path)], check=True) except Exception: print(f" 请手动打开: {html_path}") print(f"\n{GREEN}╔══════════════════════════════════════════════════╗{RESET}") print(f"{GREEN}║ ✓ 热文搜索完成! ║{RESET}") print(f"{GREEN}╚══════════════════════════════════════════════════╝{RESET}\n") if __name__ == "__main__": main()
-
-
README.en.md 4 KB
# Overseas Trending Search / overseas-trending-search --- ## Overview Overseas Trending Search is a cross-platform trend discovery tool. Enter any keyword (Chinese or English) to instantly search trending content across X, TikTok, and YouTube. The tool intelligently detects the keyword language and prioritizes same-language results, delivering a unified field list and interactive HTML report. **Core Value** - 🔍 **Three-Platform Coverage**: Search trending content across X, TikTok, and YouTube in one click - 🌐 **Smart Language Prioritization**: Chinese keywords surface Chinese content first, English keywords surface English content first, resolving cross-language ambiguity - 📊 **Multi-Format Output**: Grouped terminal table + CSV export + interactive HTML report (card/table views) - ⚡ **Flexible Sorting**: Sort by views/likes/comments/publish time, with independent Top N per platform **Target Users** - ✍️ Content Creators — Daily trend tracking, topic inspiration and material collection - 📈 Marketing Operators — Cross-platform content comparison, understanding content style differences across platforms - 🏢 Brand Owners — Competitive sentiment monitoring, tracking real overseas user discussions - 📊 Data Analysts — Topic/event tracking, quantifying cross-platform reach --- ## Features ### Core Capabilities - 🔍 **Three-Platform Search**: Keyword search across X, TikTok, and YouTube - 🌐 **Smart Language Prioritization**: Chinese keywords prioritize Chinese content, English keywords prioritize English content, other languages as fallback - 📋 **Unified Field List**: Platform, title, author, views, likes, comments, publish time, link — consistent across platforms - 📊 **Multi-Dimensional Sorting**: Sort by views/likes/comments/publish time, with independent Top N per platform - 📄 **Multi-Format Output**: Grouped terminal table, CSV data export, interactive HTML report (card/table views) - 🔗 **Multi-Keyword Support**: Comma-separated keywords, search multiple terms simultaneously - 🔄 **Fault Tolerance**: Gracefully skip platforms with API errors, no impact on other platform results --- ## Usage Guide Describe your needs in natural language — no commands to memorize. ### Quick Reference | Intent | Example | Result | |--------|---------|--------| | Trend search | "Search AI trending content on overseas platforms" | 3-platform search + terminal table + CSV + HTML | | Specific platform | "Show me trending AI videos on TikTok" | Search selected platform only | | Cross-platform comparison | "Compare iPhone 16 buzz on X and YouTube" | Targeted platform comparison | | Multiple keywords | "Search AI, AI agent overseas trending" | Search multiple keywords simultaneously | | Competitor monitoring | "Search Tesla discussions overseas" | Brand competitive sentiment tracking | | Content collection | "Export AI overseas trending CSV" | Export CSV for content planning | ### Output Example After the search completes, you will receive: > **Terminal Grouped Table** → Top N results grouped by platform, with title, author, views/likes/comments/publish time, and links > **CSV Export** → Structured data export for analysis and archival > **HTML Report** → Interactive report with card/table view toggle, savable offline --- ## Use Cases | Scenario | Role | Example Query | Benefit | |----------|------|---------------|---------| | Daily Trend Tracking | Content Creator | "Search AI trending today" | Master today's hottest content across 3 platforms | | Cross-Platform Comparison | Marketing Operator | "Compare Vision Pro buzz on X and YouTube" | Understand content style differences across platforms | | Topic Tracking | Brand Owner | "Search EV, new energy trends for 7 days" | Track cross-platform topic propagation | | Competitor Monitoring | Data Analyst | "Search real Tesla discussions overseas" | See authentic overseas user feedback | | Content Collection | Social Media Operator | "Export AI overseas trending CSV" | Export data for content planning | -
README.md 3.7 KB
# 海外跨平台热文搜索 / overseas-trending-search --- ## 简介 海外跨平台热文搜索是一款跨平台热点洞察工具,输入任意关键词(中文/英文),即可一键搜索 X、TikTok、YouTube 三大海外平台的热门内容,智能识别关键词语言并优先展示同语言结果,输出统一字段列表与交互式 HTML 报告。 **核心价值** - 🔍 **三平台覆盖**:一键搜索 X / TikTok / YouTube 三大海外平台的热门内容 - 🌐 **智能语言优先**:中文关键词优先中文内容,英文关键词优先英文内容,解决跨语言歧义 - 📊 **多格式输出**:终端分组表格 + CSV 数据导出 + 交互式 HTML 报告(卡片/表格双视图) - ⚡ **灵活排序**:支持按播放数/点赞数/评论数/发布时间自由排序,每平台独立取 Top N **适用对象** - ✍️ 内容创作者 — 每日热点追踪,获取选题灵感与素材 - 📈 市场运营 — 跨平台内容对比,了解不同平台内容形态差异 - 🏢 品牌方 — 竞品舆情监控,查看海外用户真实讨论 - 📊 数据分析师 — 话题事件追踪,量化跨平台传播效果 --- ## 功能特性 ### 核心功能 - 🔍 **三平台搜索**:覆盖 X、TikTok、YouTube 三大海外平台的关键词搜索 - 🌐 **语言智能优先**:中文关键词各平台优先展示中文内容,英文关键词优先英文内容,其余语言兜底 - 📋 **统一字段列表**:平台、标题、作者、播放数、点赞数、评论数、发布时间、链接,跨平台一致 - 📊 **多维度排序**:支持按播放数/点赞数/评论数/发布时间排序,每平台独立取 Top N - 📄 **多格式输出**:终端分组表格展示、CSV 数据导出、交互式 HTML 报告(卡片/表格双视图) - 🔗 **多关键词支持**:英文逗号分隔多个关键词,一次搜索同时命中 - 🔄 **错误容忍**:某平台异常时自动跳过,不影响其他平台结果 --- ## 使用指南 直接用自然语言描述需求,无需记忆命令。 ### 常用说法速查 | 意图 | 示例话术 | 效果 | |------|---------|------| | 热点搜索 | 「搜索 AI 在海外平台的热门内容」 | 三平台搜索 + 终端表格 + CSV + HTML | | 指定平台 | 「帮我看 TikTok 上关于 AI 的热门视频」 | 仅搜索指定平台 | | 跨平台对比 | 「对比 X 和 YouTube 上 iPhone 16 的热度」 | 指定平台对比分析 | | 多关键词 | 「搜索 人工智能,AI agent 的海外热文」 | 同时搜索多个关键词 | | 竞品监控 | 「搜一下 Tesla 在海外的讨论」 | 品牌竞品舆情追踪 | | 素材采集 | 「导出 AI 相关的海外热文 CSV」 | 导出 CSV 做选题库 | ### 输出示例 搜索完成后,你将依次收到: > **终端分组表格** → 按平台分组展示 Top N 结果,含标题、作者、播放/点赞/评论/发布时间、链接 > **CSV 导出** → 结构化数据导出,便于数据分析与存档 > **HTML 报告** → 交互式报告,支持卡片/表格双视图切换,可离线保存 --- ## 使用场景 | 场景 | 角色 | 示例问法 | 收益 | |------|------|---------|------| | 每日热点追踪 | 内容创作者 | 「搜索 AI 今天的热门内容」 | 掌握三平台当日最热内容 | | 跨平台对比 | 市场运营 | 「对比 X 和 YouTube 上 Vision Pro 的热度」 | 了解不同平台内容形态差异 | | 话题事件追踪 | 品牌方 | 「搜 新能源,EV 近 7 天传播情况」 | 追踪特定话题跨平台传播 | | 竞品舆情监控 | 数据分析师 | 「搜一下 Tesla 在海外的真实讨论」 | 看海外用户真实反馈 | | 素材灵感采集 | 自媒体运营 | 「导出 AI 相关海外热文 CSV」 | 导出数据做选题库 | -
SKILL.md 9.4 KB
--- name: overseas-trending-search display_name: 海外跨平台热文搜索 display_name_en: Overseas Trending Search description: 海外跨平台热文搜索 — 输入任意关键词(中文/英文),一次搜索 X / TikTok / YouTube 三平台热门内容,每平台 Top N(默认5)统一列表(平台/标题/作者/播放/点赞/评论/发布时间/链接),终端分组表格 + CSV 导出 + 交互式 HTML 报告(卡片/表格双视图)。按关键词语言智能优先同语言内容(中文词→中文优先,英文词→英文优先,其余语言兜底)。当用户需要搜索海外平台热点、跨平台内容对比、话题/竞品舆情监控、选题素材采集时使用。 description_zh: 输入关键词(中英文均可),一次搜索 X / TikTok / YouTube 三平台热门内容,每平台 Top N 统一字段列表(平台/标题/作者/播放/点赞/评论/发布时间),中文关键词优先中文内容、英文关键词优先英文内容,终端分组表格 + CSV + 交互式 HTML 报告(卡片/表格双视图),适合热点追踪、跨平台对比与素材采集。 description_en: "Search any keyword (Chinese or English) across X / TikTok / YouTube and get each platform's Top N trending posts in one unified list (platform/title/author/views/likes/comments/date). Language-aware ranking: a Chinese keyword surfaces Chinese content first, an English keyword surfaces English content first, other languages as fallback. Grouped terminal table + CSV export + interactive HTML report (card/table views) — ideal for trend tracking, cross-platform comparison and content research." category: data-analysis version: 1.3.0 author: 红狐数据 permissions: - network - filesystem-write --- # 海外跨平台热文搜索 输入任意关键词(中文/英文),一键搜索 X / TikTok / YouTube 三平台热门内容,每平台按点赞/播放/评论/发布时间取 Top N(默认 5),输出统一字段列表:平台、标题、作者、播放数、点赞数、评论数、发布时间、链接。 > API 请求均携带 `ChinaTrendingDigest-RedSkill` 标识。需先配置 API Key,通过环境变量 REDFOX_API_KEY 或 --api-key 参数传入。 > 架构为平台适配器模式:新平台(如 YouTube)只需新增适配器文件,主流程零改动。 --- ## 使用场景 | 场景 | 示例 | |------|------| | **每日热点追踪** | 每天跑一次 "AI",掌握三平台当日最热内容 | | **跨平台对比** | 同一关键词对比 X / TikTok / YouTube 的热度与内容形态差异 | | **话题事件追踪** | 搜 "新能源,EV" 追踪特定话题跨平台传播 | | **竞品/舆情监控** | 搜品牌或产品名,看海外用户的真实讨论与爆款反馈 | | **素材灵感采集** | 导出 CSV 做选题库与数据分析 | | **账号内容挖掘** | TikTok 侧支持按作者作品列表下钻(userAwemeList) | --- ## 使用方法 ```bash # 基础用法:单关键词(默认最近 24 小时,按播放数降序) python3 "$SKILL_PATH/scripts/digest.py" "AI" # 中文关键词 / 多关键词(英文逗号分隔) python3 "$SKILL_PATH/scripts/digest.py" "人工智能,AI agent" # 放宽时间窗口到最近 3 天(TikTok 热门多为历史内容,建议放宽) python3 "$SKILL_PATH/scripts/digest.py" "AI" --days 3 # 只跑指定平台 python3 "$SKILL_PATH/scripts/digest.py" "AI" --platforms tiktok # 按发布时间排序 python3 "$SKILL_PATH/scripts/digest.py" "AI" --sort time # 按点赞数排序 / 调整每平台返回条数(默认 5) python3 "$SKILL_PATH/scripts/digest.py" "AI" --sort likes --top 10 # 仅导出 CSV / 不自动打开浏览器 python3 "$SKILL_PATH/scripts/digest.py" "AI" --csv-only --no-open ``` CSV / HTML 默认保存在 `~/Downloads/QoderOverseasTrending/`。 --- ## 返回结果展示规范 向用户展示结果时,**必须**包含以下字段: | 字段 | 说明 | |------|------| | 平台 | X / TikTok / YouTube(带平台标识色) | | 标题 | 推文/视频文案摘要,仅展示前 20 字(HTML 表格悬停看全文) | | 作者 | 作者昵称 | | 播放数 | 千/万简写(如 47.3w),展示在作者之后 | | 点赞数 | 千/万简写(如 62.9w) | | 评论数 | 同上 | | 发布时间 | `YYYY-MM-DD HH:mm` | | 链接 | 可点击跳转原文 | 平台状态需在结果前明示:某平台上游故障时优雅降级并在报告中标注,不影响其他平台结果。 - 条数约束:每个平台最多返回 `--top` N 条(默认 5),按 `--sort` 指标在平台组内降序;终端与 HTML 均按平台分组展示。 - 语言优先分层:中文关键词时各平台组内优先展示中文内容、英文关键词优先英文内容,组内中/英文内容不足时以其他语言兜底;层内仍按排序指标降序。 - HTML 报告支持「卡片 / 表格」双视图切换,表格视图含点赞/评论/播放/分享全字段。 - YouTube 点赞/评论数由 videoDetail 详情接口逐条补全(默认全量覆盖搜索结果 20 条,本地化计数串已解析为整数)。 --- ## 参数说明 | 参数 | 说明 | 默认值 | |------|------|--------| | `keywords` | 关键词,中英文均可,多词英文逗号分隔(位置参数) | — | | `--days` | 时间窗口:最近 N 天(0=不限) | `1` | | `--platforms` | 平台列表:`x,tiktok,youtube` 任意组合 | `x,tiktok,youtube` | | `--sort` | 排序:`views` / `likes` / `comments` / `time` | `views` | | `--top` | 每平台最多返回条数(0=不限) | `5` | | `--output-dir` | 输出目录 | `~/Downloads/QoderOverseasTrending` | | `--api-key` | 指定 RedFox API Key | — | | `--csv-only` | 仅生成 CSV,不生成 HTML | — | | `--no-open` | 不自动打开浏览器 | — | --- ## API Key 配置 任选一种方式配置个人 Key: | 方式 | 命令 | |------|------| | 环境变量(推荐) | `export REDFOX_API_KEY=ak_你的密钥` | | 命令行参数 | `--api-key ak_你的密钥` | | 配置文件 | `echo '{"api_key":"ak_你的密钥"}' > ~/.qoder/apis/redfox.json` | 注册地址:[redfox.hk](https://redfox.hk/settings/api-keys?source=redskill) --- ## 架构说明 ``` scripts/ ├── digest.py # 主编排器:关键词×平台采集 → 时间过滤 → 排序 → 输出 ├── config.py # RedFox 网关地址、密钥加载(三级优先级) └── sources/ # 平台适配器(新增平台只需加一个文件并在 __init__ 登记) ├── base.py # BaseSource:统一 schema + 递增延迟重试 ├── x_source.py # X:search/tweetDetail/tweetComments ├── tiktok_source.py # TikTok:searchVideo(一条请求自带点赞/评论数) └── youtube_source.py# YouTube:searchVideo 列表 + videoDetail 补点赞/评论 ``` 统一记录 schema:`platform / title / url / author / likes / comments / views / publish_ts / publish_time / keyword / shares`。 ### 平台接口状态(2026-07 实测) | 平台 | 接口 | 状态 | |------|------|------| | X | search / tweetDetail / tweetComments | ✅ 全部可用;search 必填 `searchType`(Top/Latest),缺失会误报 3203 | | TikTok | searchVideo / awemeDetail / userAwemeList | ✅ 全部可用,searchVideo 自带全量互动数据 | | YouTube | searchVideo / videoDetail / videoComments | ✅ 全部可用;searchVideo 返回播放数与相对发布时间,点赞/评论由 videoDetail 补全(Top 10) | --- ## 依赖 ```bash pip3 install requests ``` --- ## 常见问题 **Q:X 搜索返回 3203 报错?** A:X search 接口的 `searchType` 为必填参数(`Top` 热门 / `Latest` 最新),缺失时 RedFox 会返回误导性的 3203「X能力调用失败」。本技能已内置该参数;另外过期/无效的 tweetId 调 tweetDetail 也会报 3203。 **Q:TikTok 搜索需要调详情接口补数据吗?** A:不需要。searchVideo 一次请求即返回点赞/评论/播放/分享数和发布时间,日常概要足够。 **Q:YouTube 的点赞/评论数据从哪里来的?** A:searchVideo 列表接口只返回播放数,点赞/评论需调 videoDetail 逐条补全。技能默认对搜索结果 20 条全量补详情(`youtube_source.py` 中 `detail_top` 可调小以省积分),因此所有视频都有真实点赞/评论数;仍为 0 代表该视频确实无点赞/评论。YouTube 平台本身不公开分享数,故分享列恒为 0。 **Q:每平台返回多少条?** A:默认每平台按排序指标(点赞/播放/评论/发布时间)取 Top 5,`--top N` 可调(0=不限)。各平台独立分组、组内降序,互不影响。 **Q:为什么默认参数下看不到 TikTok 内容?** A:TikTok searchVideo 返回的是热门视频,发布时间多为数天乃至数月前,默认 `--days 1`(最近 24 小时)会将其整体过滤。想看 TikTok 热门请加 `--days 0` 或 `--days 7`;过滤时终端会按平台提示被排除的条数。 **Q:搜中文关键词,为什么 X 上的日文内容被排到后面了?** A:技能内置语言优先分层:关键词为中文时,各平台组内优先展示中文内容、其次英文,日文等其他语言兜底(解决「南海」命中日文「南海電鉄」这类歧义问题);英文关键词同理优先英文内容。语言识别基于标题字符集(汉字/假名/谚文/拉丁字母),无第三方依赖。 **Q:想做每日定时运行?** A:可以让 AI 注册定时任务,每天自动执行 `digest.py` 并生成报告。
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.