有一段时间,我每天花将近一小时刷 B 站做选题。
划啊划,看到不错的视频就截图,评论区里有意思的讨论就复制粘贴,扔进备忘录。第二天打开,一堆乱截图,完全没法用——想找某个视频的数据,翻半天找不到,好不容易记起有个爆款,忘了是哪个关键词搜到的。
后来我搭了一套自动化,这件事就变成:每天早上打开飞书多维表格,昨日发布的相关视频按播放量排好,评论也拉下来,10 分钟搞定选题调研。说说这套系统怎么搭的。
这个流程,我用到了n8n、多维表格和tikhub,先说三个工具各是做什么的,毕竟不是每个人都接触过 n8n 和 TikHub。
1.n8n 是一个开源的流程自动化工具,可以理解成”到点自动做一串事”——定时触发、调 API、写数据,不需要人守着。
2.TikHub 是一个三方接口平台,专门给开发者用的第三方数据服务,提供 B 站、抖音等平台的公开数据接口,当然这个是要付费的,因为要去爬虫的话,封号的风险很高,所以用这第三方平台的接口,就安全可靠一些。
我用它按关键词搜视频,获取数据、评论和字幕。
3.飞书多维表格就不多说了,n8n 把筛好的视频推进去,打开表格就能看,不用手动操作。
n8n(调度)+ TikHub(B 站数据)+ 飞书多维表格(存数据)。
每天 22:00 → n8n 触发 → 按关键词搜 B 站 → 只留昨天发布的 → 去掉已记录的 → 新视频写进飞书表格。
跑一遍大概 2-3 分钟,不需要人在场。

二、飞书多维表格怎么建
先把表格建好,n8n 才知道往哪里写数据。我们建一张选题监控表,可以用如下字段:
| 字段名 | 字段类型 | 说明 |
|---|---|---|
| 标题 | 文本 | |
| 作者 | 文本 | |
| bvid | 文本 | 视频唯一 ID,去重用 |
| 视频url | 文本 | |
| 视频时长 | 文本 | |
| 视频描述 | 文本 | |
| 关键词 | 文本 | 哪个关键词搜到的 |
| 视频播放次数 | 数字 | |
| 视频收藏次数 | 数字 | |
| 视频发布时间 | 文本 | |
| 视频字幕 | 文本 | |
| 视频评论 | 文本 | 热门评论,自动采集 |
也就是我们需要把想获取到的B站视频的信息的字段维护到多维表格里,后面把数据同步这里面就好

建好之后,记下多维表格链接里的两个参数,后面 n8n 配置要用到:
- App Token:URL 里
/base/后面那段 - Table ID:URL 里
table=后面那段
三、n8n 工作流:节点配置
一共 7 个节点。节点 1-2 负责触发和配置,节点 3 单独向飞书换 Token(避免把认证逻辑塞进主代码节点),节点 4 把 Token 和配置打包传下去,节点 5 是核心——搜索、去重、评论、字幕通过tikhub的接口一气都拿到,节点 6 是条件判断(有新视频才写,没有直接结束,不报错),节点 7 写入飞书。

下面我们详细说说这几个节点的配置。
节点 1:定时触发
类型:Schedule Trigger
类似设个闹钟,到时间自动开跑,不用手动点。

节点 2:配置初始化
类型:Code(JavaScript)
我们直接用代码节点,把关键词、认证信息、日期范围一次准备好,交给后面的节点用。
代码如下:
const config = {
timezone: 'Asia/Shanghai',
tikhub: {
baseUrl: 'https://api.tikhub.io',
apiKey: 'XXXX',
},
feishu: {
appId: 'XXXX',
appSecret: 'XXXX',
bitableAppToken: 'XXXX',
bitableTableId: 'XXXX',
},
keywords: [
'飞书多维表格',
'n8n',
'AI工作流',
'自动化工作流',
'多维表格教程',
'AI Agent',
'企业数字化',
'低代码工具',
],
// 每个关键词最多写入播放量 TOP N 的视频
// 已入库的不占此配额(例如今天 TOP10 里有 3 个已入库,则额外再取后面 3 个新视频)
topPerKeyword: 10,
};
// 计算「昨天」的北京时间范围(UTC+8)
// 例:6月15日22:00 BJT 运行 → 取 6月14日 00:00:00 ~ 23:59:59 BJT
const BJT_OFFSET_MS = 8 * 60 * 60 * 1000;
const nowBJT_ms = Date.now() + BJT_OFFSET_MS;
const yesterdayBJT = new Date(nowBJT_ms - 24 * 60 * 60 * 1000);
const y = yesterdayBJT.getUTCFullYear();
const mo = yesterdayBJT.getUTCMonth();
const d = yesterdayBJT.getUTCDate();
// 昨天 00:00:00 BJT → UTC Unix 秒
const beginSec = Math.floor((Date.UTC(y, mo, d, 0, 0, 0) - BJT_OFFSET_MS) / 1000);
// 昨天 23:59:59 BJT → UTC Unix 秒
const endSec = Math.floor((Date.UTC(y, mo, d, 23, 59, 59) - BJT_OFFSET_MS) / 1000);
return [{ json: { config, range: { pubtime_begin_s: beginSec, pubtime_end_s: endSec } } }];
这里面有几个逻辑重点说明一下:
① 关键词
就是要你要让B站去搜的关键词,根据你自己的需求,来维护这里面的关键词
const keywords = [
'飞书多维表格', 'n8n', 'AI工作流',
'自动化工作流', '多维表格教程',
'AI Agent', '企业数字化', '低代码工具'
];
② 认证信息
TikHub API Key(注册后在个人中心拿),飞书 Access Token(用 app_id + app_secret 换取,参考飞书开放平台文档)。
把XXXX替换成自己的key和飞书内容。
tikhub: {
baseUrl: 'https://api.tikhub.io',
apiKey: 'XXXX',
},
feishu: {
appId: 'XXXX',
appSecret: 'XXXX',
bitableAppToken: 'XXXX',
bitableTableId: 'XXXX',
},
③ 昨日北京时间换算
n8n 服务器通常跑在 UTC,需要手动换算,不做这步数据范围会乱:
const BJT_OFFSET_MS = 8 * 60 * 60 * 1000;
const nowBJT_ms = Date.now() + BJT_OFFSET_MS;
const yesterdayBJT = new Date(nowBJT_ms - 24 * 60 * 60 * 1000);
const y = yesterdayBJT.getUTCFullYear();
const mo = yesterdayBJT.getUTCMonth();
const d = yesterdayBJT.getUTCDate();
// 昨日 00:00:00 北京时间对应的 Unix 时间戳(秒)
const beginSec = Math.floor((Date.UTC(y, mo, d, 0, 0, 0) - BJT_OFFSET_MS) / 1000);
const endSec = Math.floor((Date.UTC(y, mo, d, 23, 59, 59) - BJT_OFFSET_MS) / 1000);
节点 3/4:获取飞书token和打包配置信息
分别用http节点来获取飞书的token,然后用code节点来把 Token 和配置信息打包
1.获取token,我们用http节点来获取

2.配置信息,用code节点来完成
const token = $json.tenant_access_token;
const init = $('Init (Config + 7d range)').first().json;
if (!init?.config?.tikhub?.apiKey) {
throw new Error('请在 Init 节点填写 config.tikhub.apiKey(TikHub Bearer Token)');
}
if (!token) {
throw new Error('飞书 tenant_access_token 为空,请检查 appId/appSecret');
}
return [{ json: { ...init, tenant_access_token: token } }];
节点 5:搜索 + 评论 + 字幕(核心节点)
类型:Code(JavaScript)
这个节点就是核心了,我们用code来去从tikhub接口获取到B站的数据
这段代码做了这几个事:先从飞书拿全部历史 BV 号来去重(之前已经同步过的相同内容的数据就不需要再次同步了),再按关键词搜 B 站,过滤保留新视频,最后逐条拉热门评论和字幕。这是整个流程最重的部分,跑完大概 2-3 分钟。
完整代码如下:
/**
* 执行逻辑(三步):
* Step1 — 从飞书多维表格加载全部已有 bvid(分页翻完),建立去重集合
* Step2 — 对每个关键词搜索视频,每词取前 topPerKeyword 条新视频
* · 跨关键词 bvid 去重(本轮内)
* · 已入库 bvid 去重(对比 Step1 集合)
* · 已跳过的不占 topPerKeyword 配额,会继续往后取
* Step3 — 拉取热门评论 + 字幕(全部视频均拉,内容截取 8000 字符)
* 字幕流程:先调 B站公开 pagelist API 拿 cid,再调 TikHub 字幕接口
*/
function stripHtml(s) {
return String(s || '').replace(/<[^>]+>/g, '');
}
function getRepliesFromCommentResp(resp) {
const r = resp?.data?.data?.replies;
return Array.isArray(r) ? r : [];
}
function commentMessage(reply) {
const c = reply?.content;
if (c == null) return '';
if (typeof c === 'string') return c;
return c.message || c.text || '';
}
function buildNumberedComments(replies) {
const lines = [];
for (const reply of replies) {
let t = commentMessage(reply).trim().replace(/\r?\n/g, ' ').replace(/\s+/g, ' ');
if (!t) continue;
lines.push(`${lines.length + 1}.${t}`);
}
return lines.join('\n');
}
function buildQuery(params) {
const parts = [];
for (const key in params) {
if (!Object.prototype.hasOwnProperty.call(params, key)) continue;
const value = params[key];
if (value === undefined || value === null || value === '') continue;
parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(String(value)));
}
return parts.join('&');
}
// 飞书多维表格文本字段可能是字符串,也可能是 [{type,text}] 数组,统一处理
function extractText(fieldValue) {
if (!fieldValue) return '';
if (typeof fieldValue === 'string') return fieldValue;
if (Array.isArray(fieldValue)) return fieldValue.map(seg => seg.text || '').join('');
return String(fieldValue);
}
// 从 TikHub 字幕接口返回中提取纯文本
// 尝试常见格式:body 数组、嵌套 data、字符串 JSON
function extractSubtitleText(respData) {
if (!respData) return '';
// 常见格式1:直接是 body 数组({ body: [{content, from, to}, ...] })
if (respData.body && Array.isArray(respData.body)) {
return respData.body
.map(item => (item.content || item.text || '').trim())
.filter(Boolean)
.join(' ')
.slice(0, 8000);
}
// 常见格式2:嵌套 data
if (respData.data) return extractSubtitleText(respData.data);
// 常见格式3:顶层就是数组
if (Array.isArray(respData)) {
return respData
.map(item => (item.content || item.text || '').trim())
.filter(Boolean)
.join(' ')
.slice(0, 8000);
}
// 常见格式4:字符串(可能是 JSON 字符串)
if (typeof respData === 'string') {
try { return extractSubtitleText(JSON.parse(respData)); } catch { return respData.slice(0, 8000); }
}
return '';
}
const input = $input.first().json;
const cfg = input.config;
const range = input.range;
const token = input.tenant_access_token;
const apiKey = cfg.tikhub.apiKey;
const base = (cfg.tikhub.baseUrl || 'https://api.tikhub.io').replace(/\/$/, '');
const tikhubHeaders = { Authorization: `Bearer ${apiKey}` };
const feishuHeaders = { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' };
const topPerKeyword = Number(cfg.topPerKeyword) || 10;
// ── Step 1: 加载飞书多维表格中全部已有 bvid ──────────────────────────
const existingBvids = new Set();
let pageToken = undefined;
do {
const qs = { page_size: '100' };
if (pageToken) qs.page_token = pageToken;
let resp;
try {
resp = await this.helpers.httpRequest({
method: 'GET',
url: `https://open.feishu.cn/open-apis/bitable/v1/apps/${cfg.feishu.bitableAppToken}/tables/${cfg.feishu.bitableTableId}/records`,
headers: feishuHeaders,
qs,
json: true,
timeout: 30000,
});
} catch (e) {
// 获取失败时中止分页,已收集的继续用;宁可少量重复也不丢新数据
break;
}
for (const item of (resp?.data?.items || [])) {
const bvid = extractText(item?.fields?.bvid).trim();
if (bvid) existingBvids.add(bvid);
}
pageToken = resp?.data?.page_token || undefined;
} while (pageToken);
// ── Step 2: 按关键词搜索,每词取 topPerKeyword 条真正新视频 ──────────
const byBvid = new Map(); // 本轮跨关键词去重
for (const keyword of cfg.keywords || []) {
const kw = String(keyword || '').trim();
if (!kw) continue;
const qs = buildQuery({
keyword: kw,
order: 'click',
page: '1',
page_size: '20', // 搜多一些,过滤已入库后剩下的才计入配额
duration: '0',
pubtime_begin_s: String(range.pubtime_begin_s),
pubtime_end_s: String(range.pubtime_end_s),
});
const url = `${base}/api/v1/bilibili/web/fetch_general_search?${qs}`;
let searchResp;
try {
searchResp = await this.helpers.httpRequest({
method: 'GET', url, headers: tikhubHeaders, json: true, timeout: 120000,
});
} catch (e) {
continue;
}
const list = searchResp?.data?.data?.result || [];
let addedThisKeyword = 0;
for (const it of list) {
if (!it || it.type !== 'video' || !it.bvid) continue;
if (byBvid.has(it.bvid)) continue; // 本轮跨关键词去重
if (existingBvids.has(it.bvid)) continue; // 已入库去重(不占配额)
if (addedThisKeyword >= topPerKeyword) break;
byBvid.set(it.bvid, {
search_keyword: kw,
author: it.author || '',
bvid: it.bvid,
aid: it.aid || it.id || '', // 字幕接口 a_id 参数需要
arcurl: it.arcurl || `https://www.bilibili.com/video/${it.bvid}`,
duration: it.duration != null ? String(it.duration) : '',
title: stripHtml(it.title || ''),
description: String(it.description || ''),
tag: String(it.tag || ''),
pubdate: typeof it.pubdate === 'number' ? new Date(it.pubdate * 1000).toISOString() : '',
play: it.play != null ? Number(it.play) : 0,
favorites: it.favorites != null ? Number(it.favorites) : 0,
});
addedThisKeyword++;
}
}
// ── Step 3: 拉热门评论 + 字幕,组装 records ──
const videos = Array.from(byBvid.values());
const records = [];
for (const v of videos) {
// --- 3a: 热门评论 ---
const qsC = buildQuery({ bv_id: v.bvid, mode: '3', next_offset: '1' });
const cUrl = `${base}/api/v1/bilibili/app/fetch_video_comments?${qsC}`;
let cResp;
try {
cResp = await this.helpers.httpRequest({
method: 'GET', url: cUrl, headers: tikhubHeaders, json: true, timeout: 120000,
});
} catch (e) {
cResp = null;
}
const replies = cResp ? getRepliesFromCommentResp(cResp) : [];
let hot_comments = buildNumberedComments(replies);
if (!hot_comments || !hot_comments.trim()) hot_comments = '暂无评论';
// --- 3b: 字幕(所有视频均获取,内容截取 8000 字符)---
let subtitle_text = '';
if (v.aid) {
try {
// 第一步:调 B站公开 pagelist API 取 cid(不消耗 TikHub 额度)
const pagelistResp = await this.helpers.httpRequest({
method: 'GET',
url: 'https://api.bilibili.com/x/player/pagelist',
qs: { bvid: v.bvid },
json: true,
timeout: 10000,
});
const cid = pagelistResp?.data?.[0]?.cid;
if (cid) {
// 第二步:调 TikHub 字幕接口
const subtitleResp = await this.helpers.httpRequest({
method: 'GET',
url: `${base}/api/v1/bilibili/web/fetch_video_subtitle`,
headers: tikhubHeaders,
qs: { a_id: String(v.aid), c_id: String(cid) },
json: true,
timeout: 30000,
});
subtitle_text = extractSubtitleText(subtitleResp?.data) || '';
}
} catch (e) {
// 字幕获取失败不影响主流程,留空即可
subtitle_text = '';
}
}
records.push({
fields: {
视频标题: v.title,
作者: v.author,
bvid: v.bvid,
视频url: v.arcurl,
视频时长: v.duration,
视频描述: v.description,
视频标签: v.tag,
视频发布时间: v.pubdate,
视频播放次数: v.play,
视频收藏次数: v.favorites,
搜索关键词: v.search_keyword,
视频评论: hot_comments,
视频字幕: subtitle_text,
},
});
}
return [{
json: {
feishu: {
bitableAppToken: cfg.feishu.bitableAppToken,
bitableTableId: cfg.feishu.bitableTableId,
tenant_access_token: token,
},
records,
stats: {
newVideos: records.length,
existingInTable: existingBvids.size,
},
},
}];
说几个关键点:
Step 1:读取飞书历史 BV 号
飞书记录接口每页最多 100 条,超过就翻页(page_token):
const existingBvids = new Set();
let pageToken = undefined;
do {
const qs = { page_size: '100' };
if (pageToken) qs.page_token = pageToken;
const resp = await this.helpers.httpRequest({
method: 'GET',
url: `https://open.feishu.cn/open-apis/bitable/v1/apps/${APP_TOKEN}/tables/${TABLE_ID}/records`,
headers: { Authorization: `Bearer ${ACCESS_TOKEN}` },
qs, json: true
});
for (const item of (resp?.data?.items || [])) {
const bvid = item?.fields?.bvid;
if (bvid) existingBvids.add(typeof bvid === 'string' ? bvid : bvid[0]?.text);
}
pageToken = resp?.data?.page_token;
} while (pageToken);
Step 2:按关键词搜 B 站视频
const resp = await this.helpers.httpRequest({
method: 'GET',
url: 'https://api.tikhub.io/api/v1/bilibili/web/search_by_keyword',
headers: { Authorization: `Bearer ${TIKHUB_KEY}` },
qs: { keyword, page: 1, page_size: 20 },
json: true
});
// 过滤 pubdate 在 [beginSec, endSec] 内的,按 play 降序
Step 3:两层去重,每关键词取前 10
const byBvid = new Map();
let addedThisKeyword = 0;
for (const video of sortedVideos) {
if (byBvid.has(video.bvid)) continue; // 当次运行内去重
if (existingBvids.has(video.bvid)) continue; // 历史去重
if (addedThisKeyword >= 10) break;
byBvid.set(video.bvid, video);
addedThisKeyword++;
}

Step 4:拉评论和字幕
新视频挑完,对每条视频做两件事:调 TikHub 评论接口拉热门评论,再调字幕接口拿字幕。
字幕要分两步走——搜索结果里带 aid(视频 ID),但 TikHub 字幕接口同时要 cid(视频分 P 号)。cid 用 B 站自己的公开接口拿,不消耗 TikHub 额度:
// Step 1:B站公开 pagelist 接口拿 cid(免费)
const pagelistResp = await this.helpers.httpRequest({
method: 'GET',
url: 'https://api.bilibili.com/x/player/pagelist',
qs: { bvid: v.bvid },
json: true,
timeout: 10000,
});
const cid = pagelistResp?.data?.[0]?.cid;
// Step 2:TikHub 字幕接口
if (cid) {
const subtitleResp = await this.helpers.httpRequest({
method: 'GET',
url: `${base}/api/v1/bilibili/web/fetch_video_subtitle`,
headers: tikhubHeaders,
qs: { a_id: String(v.aid), c_id: String(cid) },
json: true,
timeout: 30000,
});
subtitle_text = extractSubtitleText(subtitleResp?.data) || '';
}
我们这里加了一个限制,写入多维表格的字幕文本不能超过8000字符,因为有的视频太长了,文字太多传到多维表格也会有问题。
所以拿到的字幕文本截到 8000 字符写进飞书,字幕拉取失败(比如视频没有字幕)不影响整条流程,该条置空就行。
节点 7:写入飞书多维表格
类型:HTTP Request
把上一步筛好的视频推进飞书表格。

①Method:POST
②URL:
https://open.feishu.cn/open-apis/bitable/v1/apps/{{ $json.feishu.bitableAppToken }}/tables/{{ $json.feishu.bitableTableId }}/records/batch_create
③Header:
Authorization:Bearer {{ \$json.feishu.tenant_access_token }}
Content-Type:application/json; charset=utf-8
④Body 里写好字段映射,把标题、播放量、评论等对应表格字段名。
{{ JSON.stringify({ records: $json.records }) }}
以上就是这条流程的详细配置步骤了
四、关键词怎么选
关键词决定你能监控到什么,选得太宽引来大量无关内容,选得太窄会漏掉机会。我根据我自己的内容定位选了 8 个:
- 工具类:飞书多维表格、多维表格教程、n8n、低代码工具
- 方法论类:AI 工作流、自动化工作流、AI Agent
- 受众触点类:企业数字化
每个关键词最多取播放量前 10 名,加起来最多 80 条候选视频。经过日期过滤和去重,每天实际新增 20-40 条。绝对是够用了。
你可以根据自己的实际业务需求来修改这些关键词,这些关键词也可以随时在配置节点里调整。
五、为什么是 n8n,不直接用 Claude code或 Codex?
有朋友会问,为什么我不能直接用claude或codex来做这个事情?
如果只做一次,让 Claude code直接来完全可以,他可以把这些事情都给你做了。但n8n的好处在于它每天可以自动跑、并且结果沉淀在固定地方。
而且n8n比较直观,整个过程运行到哪个节点,历史的运行日志,这些都很清晰,配置一次,后面就自动跑就行。
Claude code或 Codex也很强大,但是它有一定的学习成本,并且每次都会消耗token。
当然最后我们同步到多维表格的数据,是可以直接给到Claude code或 Codex来做分析的。
Claude 适合分析数据,n8n 适合采集和搬运。两者是分工,不是替代。
根据自己的需求来选择即可
六、我的日常是这么用的
现在每天晚上 22:00 自动触发这个n8n流程,第二天早上打开飞书,昨天发的相关视频已经按播放量排好了。扫一遍,10 分钟内找到 2-3 个值得深入的方向,选题调研就算结束。之前靠的是”今天碰到了什么”,现在所有相关内容都在表格里,只负责挑。
如果想更深入分析的话,我就直接把这个表格丢给Claude code,让它根据表格的内容、我账号的定位、我历史的文章,来给我生成可以参考的选题内容。
当然现在这套只是数据采集,如果想做的更深入的话,可以继续:
1.每日 AI 洞察:每天让 AI 看完监控数据,直接输出 3 个值得写的方向,不用自己逐条判断。
2.一键生成初稿:选好题后,结合历史文章风格,AI 出稿,自己来润色。
…..
📋 这套B站配置相关模板,我整理成模板了
跟着上面步骤搭要花点时间。想直接拿现成的改,加我微信备注「B站配置」,我发你。
🤔 卡在某一步了?
搭建过程里如果哪一步卡住了,如果你照着做没跑通,或者你们公司情况不太一样,可以找我聊聊,我免费帮你看看怎么调。不合适我也会直说。
扫码加我,备注「B站配置」领模板
想看更多这类方案,我都整理在 吨师傅工具箱 里了:客户管理、进销存、工单报修……按场景分好类,可以直接抄。


