一个做知识类视频的UP主去年接了三个平台的同步更新任务——B站、抖音、YouTube,每个平台每周发5条,一个月60条视频。最让他崩溃的不是做视频,是上传。B站一个视频从打开创作中心、拖文件、等转码、填标题标签简介、选分区、设封面、勾选原创声明、点发布,完整走完一套流程8分钟。60条视频光上传就要8个小时,还经常因为填错分区被退回重传。
后来他用了B站开放平台的投稿API,写了个Python脚本批量上传。视频文件、标题、标签、简介、封面、分区、定时发布时间全放在Excel里,脚本逐行读取→调用API→自动上传。第一次搭环境花了半天,但之后每周60条视频的上传时间从8小时压缩到30分钟以内。
B站批量上传和手动上传的区别不在速度,在这五个维度
| 1 | 信息录入效率 — 手动填表每项逐一敲字 vs 批量从Excel/CSV读取一行数据自动填入所有字段 |
| 2 | 错误率 — 手动填100个视频有5-8个分区选错或标签漏填 vs 脚本从规范数据源读取,填错率为零 |
| 3 | 定时发布 — 手动只能发布时选时间 vs 脚本可以提前批量预设未来30天的发布时间点 |
| 4 | 多账号管理 — 手动要反复登录登出 vs 脚本维护多组access_token自动切换账号 |
| 5 | 状态追踪 — 手动要一个个进创作中心看审核状态 vs 脚本调用稿件状态API批量拉取所有视频的审核结果 |
一、B站投稿有两条路:网页端和开放平台API,能力差了一个量级
很多人以为B站只有网页端创作中心这一条上传路径。实际上B站开放平台提供了完整的投稿API,能做的事情比网页端多得多——不只是上传视频,还能管理稿件状态、修改已发布视频的信息、批量查询视频数据。
| 对比维度 | 网页端创作中心 | 开放平台投稿API |
|---|---|---|
| 上传方式 | 拖拽或选择文件,一次一个 | 程序化上传,支持并发,理论无上限 |
| 信息填写 | 手动逐项填写 | 从Excel/CSV/数据库自动读取 |
| 分P上传 | 支持,手动添加分P | 支持,API参数指定分P信息 |
| 定时发布 | 发布时可设定时 | 支持,可批量预设未来任意时间 |
| 封面设置 | 手动上传图片或选视频帧 | API上传封面图片文件 |
| 稿件管理 | 在创作中心逐条操作 | API批量查询、修改、删除稿件 |
| 多账号 | 反复登录登出切换 | 维护多组access_token,脚本自动切换 |
| 审核状态查询 | 逐个点进稿件查看 | API批量查询,一次拉取所有稿件状态 |
| 门槛 | 零门槛,有账号就行 | 需要注册开发者、创建应用、获取access_token、写代码 |
B站开放平台的投稿API目前支持的完整参数包括:标题(title)、视频文件(videos)、封面图片(cover)、简介(desc)、分区ID(tid)、标签列表(tag)、定时发布时间(dtime)、来源(source)、原创/转载声明(copyright)、是否开启商业声明(no_reprint)等。基本上网页端能设的参数,API都能设,除了互动投票和弹幕设置这两项API暂不支持。
二、怎么拿到B站投稿API的权限,五步走
API好用,但获取权限的门槛劝退了很多人。整个过程分五步,核心难点在第二步的身份认证——需要完成个人或企业实名认证。
第一步:注册B站开放平台开发者
访问 openhome.bilibili.com → 用B站账号登录 → 进入开放平台 → 点击"加入开放平台" → 填写开发者信息(个人或企业)。个人开发者需要实名认证(身份证+人脸识别),企业需要营业执照。审核通常1-3个工作日。
第二步:创建应用获取client_id和client_secret
开发者后台 → 我的应用 → 创建应用 → 选择"投稿工具"类型 → 填写应用名称和描述 → 审核通过后获得client_id(App Key)和client_secret(App Secret)。这两个值相当于你应用的账号密码,绝对不能泄露。

第三步:OAuth2.0授权获取access_token
这是最关键的一步。用client_id引导用户跳转到B站授权页面 → 用户确认授权 → B站回调你的服务器 → 用授权码换取access_token和refresh_token。access_token有效期30天,过期后用refresh_token续期。一个access_token对应一个B站账号,多账号需要分别授权获取多个token。
第四步:申请投稿API权限(关键!)
拿到access_token不代表能调投稿API。需要在开发者后台→API权限管理→找到"视频投稿"相关接口→单独申请开通。个人开发者可能遇到投稿权限审核不通过的情况,特别是新注册的开发者账号。建议先用其他不需要审核的API(如用户信息查询)测试token是否有效,确认整个流程走通后再申请投稿权限。
第五步:处理access_token过期
access_token有效期只有30天,批量上传脚本运行到一半token过期是常见的翻车场景。解决方案:每次调API前检查token剩余有效时间,不足24小时自动用refresh_token刷新。refresh_token有效期较长(通常180天),但过期后需要用户重新授权——所以多账号场景下,每个账号的授权流程要留好记录,方便token过期后通知对应账号重新授权。
三、完整的Python批量上传脚本,从Excel读取到API发布
下面是一个可运行的完整脚本框架。核心流程:读取Excel中每行视频信息→调用B站分片上传API上传视频文件→上传封面图→调用投稿API提交完整稿件→记录返回的稿件ID→循环下一行。
import requestsimport pandas as pdimport osimport timeimport jsonfrom datetime import datetime# ========== 配置区 ==========ACCESS_TOKEN = "your_access_token" # 从OAuth2.0流程获取CLIENT_ID = "your_client_id"UPLOAD_CHUNK_SIZE = 10 * 1024 * 1024 # 分片上传每片10MB# ========== 第一步:读取Excel中的视频信息 ==========df = pd.read_excel("videos_batch.xlsx")# Excel列:文件名、标题、标签(逗号分隔)、简介、分区ID、定时发布时间、封面文件名required_cols = ['文件名', '标题', '标签', '简介', '分区ID']for col in required_cols:if col not in df.columns:raise ValueError(f"Excel缺少必需列: {col}")# ========== 第二步:分片上传视频文件 ==========def upload_video_chunked(filepath, access_token):"""B站投稿API的视频上传接口,支持分片上传"""filename = os.path.basename(filepath)file_size = os.path.getsize(filepath)# 2.1 预上传——获取upload_url和upload_idpre_url = "https://member.bilibili.com/x/vupre/web/upload/pre"headers = {"Authorization": f"Bearer {access_token}"}pre_data = {"name": filename,"size": file_size,"r": "upos","profile": "ugcupos/bup","ssl": 0,"version": "2.14.0","build": "2140000","upcdn": "bldsa",}resp = requests.post(pre_url, headers=headers, data=pre_data).json()if resp.get("code") != 0:raise Exception(f"预上传失败: {resp}")upload_url = resp["data"]["url"]upload_id = resp["data"]["upload_id"]auth = resp["data"]["auth"]# 2.2 分片上传with open(filepath, 'rb') as f:chunk_num = 0while True:chunk = f.read(UPLOAD_CHUNK_SIZE)if not chunk:breakchunk_num += 1chunk_headers = {"Authorization": auth,"Content-Type": "application/octet-stream",}chunk_url = f"{upload_url}?partNumber={chunk_num}&uploadId={upload_id}&chunk={chunk_num}&chunks={ (file_size + UPLOAD_CHUNK_SIZE - 1) // UPLOAD_CHUNK_SIZE}&size={len(chunk)}&start={ (chunk_num - 1) * UPLOAD_CHUNK_SIZE}&end={min(chunk_num * UPLOAD_CHUNK_SIZE, file_size) - 1}&total={file_size}"resp = requests.put(chunk_url, headers=chunk_headers, data=chunk)if resp.status_code != 200:raise Exception(f"分片{chunk_num}上传失败")print(f" 分片 {chunk_num} 上传完成")return filename, upload_id# ========== 第三步:上传封面图 ==========def upload_cover(filepath, access_token):"""上传封面图片,返回封面URL"""url = "https://member.bilibili.com/x/vupre/web/cover/up"headers = {"Authorization": f"Bearer {access_token}"}with open(filepath, 'rb') as f:files = {"file": (os.path.basename(filepath), f, "image/png")}resp = requests.post(url, headers=headers, files=files).json()if resp.get("code") != 0:raise Exception(f"封面上传失败: {resp}")return resp["data"]["url"]# ========== 第四步:提交投稿 ==========def submit_video(filename, upload_id, title, tags, desc, tid,cover_url=None, dtime=None, access_token=None):"""调用投稿API提交完整稿件"""url = "https://member.bilibili.com/x/vupre/web/article/post"headers = {"Authorization": f"Bearer {access_token}","Content-Type": "application/json",}data = {"title": title,"tid": int(tid), # 分区ID,如知识区是36"tag": tags, # 逗号分隔的标签字符串"desc": desc, # 简介"copyright": 2, # 1=自制 2=转载"source": "", # 转载来源,自制留空"no_reprint": 0, # 0=允许转载 1=禁止转载"videos": [{"title": title,"filename": filename,"desc": "",}],"cover": cover_url or "","dynamic": "", # 发布动态文案,留空不发布}# 定时发布if dtime and not pd.isna(dtime):if isinstance(dtime, str):dt = datetime.strptime(dtime, "%Y-%m-%d %H:%M:%S")else:dt = dtimedata["dtime"] = int(dt.timestamp())resp = requests.post(url, headers=headers, json=data).json()return resp# ========== 第五步:批量处理主循环 ==========results = []video_dir = "./videos" # 视频文件目录cover_dir = "./covers" # 封面文件目录for idx, row in df.iterrows():print(f"\n[{idx+1}/{len(df)}] 处理: {row['标题']}")try:# 上传视频文件video_path = os.path.join(video_dir, row['文件名'])if not os.path.exists(video_path):raise FileNotFoundError(f"视频文件不存在: {video_path}")filename, upload_id = upload_video_chunked(video_path, ACCESS_TOKEN)print(f" 视频上传完成: {filename}")# 上传封面(如果有)cover_url = Noneif '封面文件名' in row and not pd.isna(row['封面文件名']):cover_path = os.path.join(cover_dir, row['封面文件名'])if os.path.exists(cover_path):cover_url = upload_cover(cover_path, ACCESS_TOKEN)print(f" 封面上传完成")# 提交投稿dtime = row.get('定时发布时间', None)resp = submit_video(filename=filename,upload_id=upload_id,title=row['标题'],tags=row['标签'],desc=row['简介'],tid=row['分区ID'],cover_url=cover_url,dtime=dtime,access_token=ACCESS_TOKEN)if resp.get("code") == 0:results.append({"标题": row['标题'],"状态": "成功","稿件ID": resp.get("data", {}).get("aid", ""),"BVID": resp.get("data", {}).get("bvid", ""),"时间": datetime.now().strftime("%Y-%m-%d %H:%M:%S")})print(f" ✅ 投稿成功! aid={resp.get('data', {}).get('aid')}")else:results.append({"标题": row['标题'], "状态": f"失败: {resp.get('message')}"})print(f" ❌ 投稿失败: {resp.get('message')}")except Exception as e:results.append({"标题": row['标题'], "状态": f"异常: {str(e)}"})print(f" ❌ 异常: {e}")time.sleep(3) # 控制频率,避免触发限流# 保存结果pd.DataFrame(results).to_excel("upload_results.xlsx", index=False)print(f"\n批量上传完成! 成功{sum(1 for r in results if '成功' in str(r['状态']))}/{len(results)}")脚本运行前必须确认的三件事
· access_token有效且未过期:运行前手动调一次用户信息查询API(不需要额外权限),确认token能正常返回用户数据。
· 投稿API权限已开通:在开发者后台确认"视频投稿"相关接口显示为"已开通",未开通的话上传步骤会报403权限错误。
· 分区ID正确:B站分区ID是一个数字(如知识区=36、科技区=188、生活区=160),不要在Excel里填分区名称。分区ID列表可以在B站开放平台文档中查到。
四、现成的批量上传工具,不想写代码的选择
不是每个UP主都想或者能写代码。市面上有一些现成的B站批量上传工具,但质量和安全性参差不齐。
B站创作中心(官方多稿件管理)
· B站官方创作中心最近更新了多稿件管理功能
· 支持同时上传多个视频文件排队转码
· 但每个视频的信息还是要手动填写,不算真正的"批量"
· 优点:官方工具无安全风险,不需要任何开发
· 适合:每次上传3-5个视频,不需要从Excel导入信息
第三方投稿助手(桌面客户端)
· 如"B站投稿助手""UP主工具箱"等第三方桌面软件

· 通常支持批量导入视频+自动读取文件名作为标题
· 部分工具支持预设模板(默认标签/简介/分区)
· 免费或几十块钱永久使用
· ⚠️ 核心风险:需要输入B站账号密码,第三方可能存储或滥用你的凭证。非开源工具建议用小号先测试。
MCN机构后台(仅限签约UP主)
· 签约MCN的UP主可以通过MCN后台批量管理视频
· MCN后台通常有批量上传和数据分析功能
· 但功能取决于MCN自己的工具开发水平,有的很好用有的很简陋
· 适合:已经签约MCN的UP主,直接用MCN提供的工具
RPA自动化(影刀/UiPath等)
· 用RPA工具模拟人工操作网页端创作中心
· 不需要API权限,模拟点击和填表即可
· 缺点:速度慢(受网页加载速度限制)、不稳定(页面改版就失效)、容易被识别为机器人
· 适合:临时应急、没有API权限但又需要批量上传少量视频
第三方工具的账号安全风险
使用任何需要输入B站账号密码的第三方工具,本质上就是把账号的完全控制权交给了对方。即使工具本身没恶意,如果开发者服务器被黑,你的账号凭证就可能泄露。更安全的做法:优先使用官方API(OAuth2.0授权,可随时在B站安全中心撤销授权),其次考虑自建脚本,实在不行再用第三方工具但用小号测试。绝对不要在来路不明的工具里输入主账号密码。
五、多账号批量上传的场景和方案
MCN机构和矩阵号运营者的核心需求是多账号批量上传。一个MCN旗下有20个UP主,每个UP主每周发3条视频,一周就是60条。如果每个UP主自己手动上传,运营根本没办法统一管理发布时间、统一检查标题标签是否符合规范。
| 方案 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
| 多组token轮换 | 每个账号独立OAuth授权→存储多组access_token→上传时按账号切换token | 实现简单,基于上一节的脚本加token管理即可 | 20个账号就要维护20组token,token过期提醒和续期管理成本高 |
| 集中管理面板 | 搭建一个内部Web管理面板,所有UP主的视频上传到统一平台,运营审核后通过API分发到各账号 | 规范化管理,运营审核→排期→发布一条龙 | 需要开发投入,小MCN可能不划算 |
| MCN官方接口 | 签约MCN后使用B站提供的MCN管理后台或接口 | 官方支持,功能最全,不需要额外开发 | 仅限签约MCN,个人或小团队用不了 |
多账号场景下最容易踩的坑是同一个IP短时间内大量上传。B站的风控系统会检测上传行为模式,如果一个IP在10分钟内上传了20条视频且来自不同账号,可能触发风控导致账号被限流或封禁投稿功能。建议的做法是:不同账号的视频上传间隔至少30秒,不要用并发上传;如果有条件,不同账号使用不同的代理IP上传。

六、批量上传中容易翻车的四个环节
翻车一:视频转码失败但脚本不知道
B站投稿API上传成功后,视频会进入转码队列。转码可能需要几分钟到几小时(取决于视频时长和清晰度)。转码失败的原因包括:视频编码格式不兼容(B站推荐H.264编码,部分H.265视频可能转码失败)、视频码率超出限制、音频轨道异常。脚本投稿成功不等于视频最终发布成功——需要定期调用稿件状态API检查转码是否通过。如果转码失败,稿件会被退回且不会自动发布。
翻车二:分区ID填错导致审核不通过
B站分区ID是纯数字,而且子分区和父分区是不同的ID。比如"知识"是父分区(ID=36),"科学科普""社科人文""财经商业"等是子分区(各有独立ID)。如果填了父分区ID但内容实际上属于某个子分区,审核可能不通过或者被移到错误的分区。建议在Excel里加一列"分区名称"用于人工核对,上传前用分区名称和ID做交叉验证。
翻车三:标签数量或格式不符合规则
B站视频标签有数量限制(通常最多10个),每个标签长度限制(不超过20个字符),部分特殊字符不允许出现在标签中。批量上传时如果Excel里的标签字段格式不规范——比如用了中文逗号分隔而不是英文逗号、标签超过了20个字、标签总数超过10个——API会返回错误。建议在脚本里加标签格式校验:自动用英文逗号分隔、截断超长标签、超过10个标签时取前10个并打印警告。
翻车四:定时发布时间冲突或已过期
定时发布时间必须至少在当前时间1小时后、最多90天内。如果脚本跑了2小时,Excel里第一个视频的定时发布时间可能已经过期了(设置为当前时间+30分钟但实际跑了2小时)。建议:定时发布时间统一设置为"当天+1天"起步,留足余量;或者在脚本里校验每个dtime是否在未来1小时以上,已过期的自动改为立即发布。
七、上传后的管理:稿件状态监控和数据分析
批量上传只是第一步,上传完不等于完事。稿件可能被退回、审核不通过、转码失败、或者发布后数据异常。不做后续监控,批量上传的优势全浪费在事后逐个排查上。
待审核
status=1
稿件已提交,等待人工审核
已发布
status=0
审核通过,已公开发布
已退回
status=-1
审核不通过或转码失败
定时待发
status=2
已通过审核,等待定时发布时间
B站开放平台提供了稿件状态查询API,可以用上传时返回的aid或bvid批量查询所有稿件的当前状态。建议在批量上传脚本运行完成后,自动调用状态查询API,把结果和上传记录合并输出一份完整报告——哪些成功发布、哪些待审核、哪些被退回以及退回原因。
如果多账号管理涉及站群或矩阵号的场景,把视频发布后的数据(播放量、互动率、粉丝增长)汇总到一个看板上统一监控会更高效。UC建站系统在内容管理上也是类似的思路——多站看板统一看索引量、排名和流量变化。视频矩阵的管理逻辑和网站矩阵本质上一样:批量操作省的是执行时间,统一看板省的是决策时间。
B站批量上传这件事,工具层面最优解就是开放平台API加自建脚本。第三方工具要么不安全要么功能不全,RPA太慢太脆弱。唯一需要克服的障碍是前期搭环境——注册开发者、申请权限、写脚本、测试调通——大概需要一到两天。但一旦跑通,之后每次上传就是填Excel加运行脚本,100个视频从13小时变38分钟。省下来的时间拿去打磨内容质量,比重复填表有价值得多。
