前言
在私域运营和客服自动化场景中,通过接口向微信好友或群组发送视频是高频需求。然而微信对视频文件的大小、格式、上传流程均有严格限制,普通 HTTP 接口直接推视频往往遭遇上传失败、发送超时或被平台拦截等问题。本文围绕微信发送视频消息接口的完整链路展开,重点解决大文件分片、断点续传与调用时序等实操难点,并给出可直接参考的代码示例。
一、微信视频消息的底层机制
微信并不像普通 REST API 那样接受一个文件 URL 然后自行下载——它的媒体消息走的是先上传、再引用的两段式流程:
- 上传阶段:客户端将视频文件二进制流推送到微信服务器,获得一个临时的
mediaId(或称MsgMediaId)。 - 发送阶段:用
mediaId构造消息体,调用发消息接口将视频发往目标联系人或群组。
这套机制在公众号/企业微信平台有官方 SDK 支持,但在个人微信侧从未对外开放——官方不存在个人号的视频发送接口。要实现个人号自动化发视频,必须借助基于 微信 iPad 协议 的接入方案。iPad 协议通过模拟真实 iPad 客户端的底层通信来完成登录、消息收发等操作,在合规边界内实现了个人号的接口化调用,WechatApi 正是基于这一协议构建的 个人微信 API 平台。
理解两段式流程的意义在于:大文件处理的难点集中在上传阶段,发送阶段只是一次轻量 JSON 请求。
二、视频文件的格式与大小限制
在动手写代码之前,先把微信客户端侧的硬性约束摸清楚,否则即便接口调通也会被拒。
| 约束项 | 限制值 | 说明 |
|---|---|---|
| 支持格式 | mp4、mov、avi(建议 mp4) | 其他格式客户端可能无法播放 |
| 单文件上限 | 约 100 MB(移动端体验上限更低) | 超限直接上传失败 |
| 视频时长 | 个人会话不限;群发有平台限制 | 实测超过 5 分钟建议分段 |
| 分辨率 | 最高 4K,但建议 1080p 以下 | 过高分辨率会触发客户端转码 |
| 帧率 | ≤ 60fps | 高帧率文件需预处理 |
| 码率 | 建议视频码率 ≤ 4 Mbps | 过高导致上传超时 |
对于超过 20 MB 的视频,强烈建议在上传前用 FFmpeg 做一次转码压缩:
bash# 将视频压缩到 H.264 + AAC,控制码率在 2 Mbps 以内
ffmpeg -i input.mp4 \
-vcodec libx264 -b:v 2000k \
-acodec aac -b:a 128k \
-movflags +faststart \
output_compressed.mp4
-movflags +faststart 将视频元数据移到文件头部,让微信客户端在下载完成前即可开始播放("快速开始"体验),这个参数在实测中显著减少接收端的缓冲等待。
三、大文件分片上传流程详解
WechatApi 对大于阈值(通常 5 MB)的视频文件采用分片上传策略,整体流程分为三步:初始化 → 逐块上传 → 合并确认。
3.1 初始化上传任务
向上传初始化接口发送请求,声明文件的总大小、文件名和 MIME 类型,服务端返回一个 uploadId 用于后续分块标识。
pythonimport requests
import math
import os
API_BASE = "https://api.wechatapi.net" # 示意域名,非真实 endpoint
TOKEN = "YOUR_VIDEOS_API_TOKEN" # 控制台获取,勿硬编码
APP_ID = "YOUR_DEVICE_APP_ID" # 设备 ID,登录后获得
headers = {
"VideosApi-token": TOKEN,
"Content-Type": "application/json"
}
video_path = "output_compressed.mp4"
file_size = os.path.getsize(video_path)
file_name = os.path.basename(video_path)
# 1. 初始化分片上传
init_payload = {
"appId": APP_ID,
"fileName": file_name,
"fileSize": file_size,
"mimeType": "video/mp4"
}
resp = requests.post(f"{API_BASE}/v1/media/upload/init", json=init_payload, headers=headers)
result = resp.json()
# 期望返回: {"ret": 200, "msg": "ok", "data": {"uploadId": "xxx", "chunkSize": 2097152}}
upload_id = result["data"]["uploadId"]
chunk_size = result["data"]["chunkSize"] # 服务端建议的分块大小,单位字节
注意:appId 是设备 ID 而非公众号 AppID,每台登录设备对应一个唯一值,可在 控制台 查看。
3.2 逐块上传
将文件按 chunkSize 切分,每块携带 uploadId、块序号 chunkIndex 和块的 MD5 校验值依次上传。上传失败的块可单独重试,已上传的块无需重传——这正是分片断点续传的核心价值。
pythonimport hashlib
import base64
total_chunks = math.ceil(file_size / chunk_size)
with open(video_path, "rb") as f:
for idx in range(total_chunks):
chunk_data = f.read(chunk_size)
chunk_md5 = hashlib.md5(chunk_data).hexdigest()
chunk_payload = {
"appId": APP_ID,
"uploadId": upload_id,
"chunkIndex": idx,
"totalChunks": total_chunks,
"chunkMd5": chunk_md5,
"chunkData": base64.b64encode(chunk_data).decode() # Base64 编码后放 JSON
}
chunk_resp = requests.post(
f"{API_BASE}/v1/media/upload/chunk",
json=chunk_payload,
headers=headers,
timeout=60
)
chunk_result = chunk_resp.json()
if chunk_result["ret"] != 200:
print(f"块 {idx} 上传失败,重试中…")
# 生产代码应加指数退避重试逻辑
continue
print(f"[{idx+1}/{total_chunks}] 上传成功")
关于 Base64 vs multipart/form-data:WechatApi 的分片接口统一采用 JSON + Base64,避免了 multipart 边界解析的复杂性,但会带来约 33% 的体积膨胀。对于 2 MB 的分块,实际传输约 2.67 MB,在百兆宽带下影响可忽略不计;若带宽受限,可咨询平台是否支持二进制流模式。
3.3 合并确认并获取 mediaId
所有块上传完毕后,调用合并接口,服务端校验所有块的完整性并生成最终媒体文件,返回 mediaId。
json// POST /v1/media/upload/complete 请求体
{
"appId": "YOUR_DEVICE_APP_ID",
"uploadId": "xxx",
"totalChunks": 15,
"fileMd5": "整个文件的 MD5(可选但建议传)"
}
// 成功响应
{
"ret": 200,
"msg": "ok",
"data": {
"mediaId": "v2_xxxxxxxxxxxxxxxx@chatroom",
"expireAt": 1718000000
}
}
expireAt 是 mediaId 的过期时间戳,通常有效期为 3 天。务必在过期前完成发送,否则需重新上传。
四、发送视频消息接口调用
拿到 mediaId 后,发送就是一次简单的 POST 请求。
python# 向指定微信号发送视频消息
send_payload = {
"appId": APP_ID,
"toWxId": "target_wechat_id", # 对方微信号
"mediaId": media_id,
"videoTitle": "产品演示视频 2024", # 可选,部分客户端会显示
"thumbUrl": "" # 可选,视频封面图 URL
}
send_resp = requests.post(
f"{API_BASE}/v1/message/sendVideo",
json=send_payload,
headers=headers,
timeout=30
)
send_result = send_resp.json()
# {"ret": 200, "msg": "发送成功", "data": {"msgId": "xxxxxxxx"}}
print(send_result)
发送群消息只需将 toWxId 换成群的 roomId(格式通常为 xxxxxxxxx@chatroom),其余参数不变。WechatApi 的 微信群管理机器人 文档中有群 ID 的获取方法。
五、关键注意事项与常见报错处理
5.1 发送频率控制
微信账号侧对短时间内大量媒体消息有行为检测,建议:
- 单账号每分钟视频发送不超过 5 条;
- 批量发送时在每条消息之间随机插入 3~10 秒的等待;
- 避免在凌晨 0~8 点批量操作(账号活跃画像异常)。
5.2 网络超时与重试策略
分片上传每块建议设置 60 秒超时,整体上传任务可设置最大重试次数(建议每块最多重试 3 次,采用指数退避:1s、2s、4s)。若网络中断导致上传任务中止,可凭 uploadId 查询已上传的块列表,只补传缺失的块,无需从头开始。
5.3 常见错误码速查
| 错误码 | 含义 | 处理建议 |
|---|---|---|
| 400 | 参数缺失或格式错误 | 检查 JSON 字段名与类型 |
| 401 | Token 无效或已过期 | 前往控制台刷新 Token |
| 403 | appId 未授权 | 确认设备已登录且 appId 正确 |
| 413 | 块大小超出服务端限制 | 按服务端返回的 chunkSize 切分 |
| 429 | 请求频率超限 | 降低并发,加入限速逻辑 |
| 500 | 服务端内部错误 | 等待后重试,持续则联系支持 |
5.4 视频封面(缩略图)处理
微信视频消息展示时会显示一帧封面图。若不传 thumbUrl,平台会自动截取首帧;若首帧是黑屏或片头 LOGO,建议手动提取第 3~5 秒的某帧作为封面,可用 FFmpeg 一行完成:
bash# 提取第 4 秒的帧作为封面图
ffmpeg -i output_compressed.mp4 -ss 00:00:04 -vframes 1 thumb.jpg
将 thumb.jpg 上传到 CDN 后,把 URL 填入 thumbUrl 参数即可。
六、视频群发与 SCRM 场景的进阶用法
在私域营销和客服场景中,视频消息往往不是"发一条"而是"发给一批人"。WechatApi 的 微信 SCRM 能力层在基础发送接口之上提供了以下增强:
批量任务队列:提交一个包含多个 toWxId 的任务列表,平台自动排队、限速、重试,无需业务侧自己维护并发控制逻辑。
媒体复用:同一 mediaId 可在有效期内重复发给不同联系人,避免为每个接收方重复上传视频文件,大幅节省带宽和时间。
发送状态回调:配置 Webhook 地址后,每条消息的送达状态(成功/失败/对方已读)会实时推送到业务系统,方便后续触达追踪。
模板消息 + 视频组合:先发文字消息做话题铺垫,再发视频,比直接发视频的打开率更高。WechatApi 支持在一个任务中串联多种消息类型,按顺序定时发出。
对于需要在微信内提供售后服务的团队,微信客服机器人 场景下的视频发送也是常见需求——当用户咨询某产品安装步骤时,机器人自动回复操作演示视频,相比图文说明效果提升显著。
七、本地测试与调试技巧
在开发阶段,建议用以下方式快速验证链路:
- 用小文件先跑通:取一个 1 MB 以内的 mp4,单块上传(不走分片),确认
mediaId正常返回后再测试分片流程。
- 抓包确认请求格式:用 mitmproxy 或 Charles 代理本地请求,检查实际发出的 JSON 结构是否与文档一致,尤其注意 Base64 编码是否有换行符(标准 Base64 无换行,部分语言库默认添加,需显式去除)。
- Mock 服务端:用 FastAPI 或 Flask 在本地搭一个简单的 Mock,模拟
/upload/init、/upload/chunk、/upload/complete三个端点,可以在不消耗真实 API 配额的情况下反复测试分片逻辑。
- 日志记录:每个分块的上传耗时、重试次数、最终状态都应落日志,便于生产环境排查偶发失败。在带宽波动的云主机上,分块耗时方差很大,日志是唯一的诊断依据。
关于接入方式选型,如果你的业务还在评估阶段,可以先阅读 微信二次开发 的整体方案说明,了解个人号、企业号、公众号三条路线的差异,再决定是否采用 WechatApi 的 iPad 协议方案接入。
小结
微信视频消息接口的核心难点不在"发送"本身,而在上传链路的大文件处理:格式预处理(FFmpeg 压缩 + faststart)、分片上传(初始化 → 逐块 → 合并)、断点续传(凭 uploadId 补传缺失块)、以及频率控制与错误重试。WechatApi 基于 iPad 协议将上述能力封装为标准 HTTP 接口,配合 VideosApi-token 鉴权和 appId 设备标识,开发者无需关心底层协议细节,可以将精力集中在业务逻辑上。如有接入需求,可前往 控制台 注册试用,或查阅 开发文档 获取完整接口参考。
