前言
在做微信机器人或自动化运营系统时,纯文本消息往往满足不了业务需求。图片通知、合同文件、语音播报、短视频推送——这些富媒体场景在客服机器人、内容分发、B端审批流中极为常见。然而,不少开发者在对接时会遇到几个共同困惑:图片怎么传、文件大小有没有限制、转发和上传有什么区别、批量发送时怎么防频控触发封号?
本文以 HTTP REST 接口为技术路径,覆盖图片、文件、视频、语音四类媒体消息的完整发送流程,包含 Python 示例代码、常见报错排查和批量发送的安全策略,帮助开发者快速打通多媒体消息能力。
一、媒体消息发送的整体架构
与纯文本消息不同,多媒体消息的发送通常有两种路径:
| 路径 | 适用场景 | 特点 |
|---|---|---|
| 直接上传路径 | 本地文件首次发送 | 携带文件内容上传,服务端返回 msgId |
| 转发路径 | 同一文件二次发送 | 使用已有 msgId 转发,不重复上传流量 |
转发接口(forwardImage / forwardFile 等)本质上是"复用已上传的媒体资源",对批量场景非常友好:上传一次、转发 N 次,既省带宽又降低接口调用频率。
整体流程如下:
本地文件
↓
postImage / postFile / postVideo / postVoice(首次发送)
↓ 返回 msgId
forwardImage / forwardFile(转发给其他用户)
所有接口统一采用 HTTP POST + JSON body,鉴权 token 放在请求头,格式如下:
pythonBASE = "https://你的接口域名" # 注册后在官方文档获取
TOKEN = "你的Token"
APPID = "你的appId" # 扫码登录后获得的设备ID
HEADERS = {"token": TOKEN} # 鉴权字段名以官方文档为准
注:以下所有代码均为示例,具体接口路径、字段名称以官方文档为准。
二、发送图片消息
2.1 接口说明
发送图片使用 /message/postImage 接口,支持 URL 图片和 Base64 两种传入方式,推荐 URL 方式,传输更轻量。
pythonimport requests
def send_image(to_wxid: str, img_url: str) -> dict:
"""
发送图片消息
:param to_wxid: 接收方微信ID(个人wxid或群chatroom ID)
:param img_url: 图片的公网可访问 URL
"""
url = f"{BASE}/message/postImage"
payload = {
"appId": APPID,
"toWxid": to_wxid,
"imgUrl": img_url # 字段名以官方文档为准
}
resp = requests.post(url, json=payload, headers=HEADERS, timeout=30)
return resp.json()
result = send_image("wxid_xxxxxxxx", "https://cdn.example.com/banner.jpg")
if result.get("ret") == 200:
print("图片发送成功,msgId:", result["data"]["msgId"])
else:
print("发送失败:", result.get("msg"))
2.2 Base64 方式
当图片在本地、无法提供公网 URL 时,可转为 Base64 发送:
pythonimport base64
def send_image_b64(to_wxid: str, local_path: str) -> dict:
with open(local_path, "rb") as f:
b64_str = base64.b64encode(f.read()).decode()
url = f"{BASE}/message/postImage"
payload = {
"appId": APPID,
"toWxid": to_wxid,
"imgBase64": b64_str # 字段名以官方文档为准
}
return requests.post(url, json=payload, headers=HEADERS, timeout=30).json()
注意事项:Base64 编码会使数据体积增大约 33%,图片较大时建议先压缩再编码,避免请求体过大导致接口拒绝或超时。生产环境推荐将图片上传到 CDN,使用 URL 方式发送更为稳定。
2.3 转发已收到的图片
如果消息回调中接收到一张图片并需要转发给另一个用户,使用转发接口可以避免重新下载再上传:
pythondef forward_image(to_wxid: str, msg_id: str, from_wxid: str) -> dict:
url = f"{BASE}/message/forwardImage"
payload = {
"appId": APPID,
"toWxid": to_wxid,
"msgId": msg_id,
"fromWxid": from_wxid # 原消息来源ID,字段以文档为准
}
return requests.post(url, json=payload, headers=HEADERS, timeout=30).json()
转发时需确保 msgId 与 fromWxid 的对应关系正确,否则会返回资源无效错误。若图片来自群聊,fromWxid 通常填群的 chatroom ID,具体以文档为准。
三、发送文件消息
3.1 接口说明
发送文件(PDF、Excel、Word、压缩包等)使用 /message/postFile 接口。与图片类似,支持 URL 和 Base64,此外还需传入文件名,否则接收端显示的文件名可能不正确。
pythondef send_file(to_wxid: str, file_url: str, file_name: str) -> dict:
"""
发送文件消息
:param to_wxid: 接收方ID
:param file_url: 文件的公网可访问下载地址
:param file_name: 文件名(含扩展名,如 contract.pdf)
"""
url = f"{BASE}/message/postFile"
payload = {
"appId": APPID,
"toWxid": to_wxid,
"fileUrl": file_url,
"fileName": file_name # 字段名以官方文档为准
}
resp = requests.post(url, json=payload, headers=HEADERS, timeout=60)
return resp.json()
result = send_file(
"wxid_xxxxxxxx",
"https://cdn.example.com/reports/Q2.pdf",
"Q2业绩报告.pdf"
)
文件名编码:fileName 字段务必使用 UTF-8 编码的字符串,若出现乱码,检查 Python 源文件编码声明及 requests 库的请求头 Content-Type 是否正确设置为 application/json; charset=utf-8。
3.2 批量发文件的安全策略
批量给多人发同一份文件时,推荐"上传一次、转发多次"模式:
pythonimport time
import random
def batch_send_file(wxid_list: list, file_url: str, file_name: str):
"""
批量发文件:第一条正常发,后续转发
"""
first_msg_id = None
first_from_wxid = None
for i, wxid in enumerate(wxid_list):
if i == 0:
# 第一次正常上传发送
res = send_file(wxid, file_url, file_name)
if res.get("ret") == 200:
first_msg_id = res["data"].get("msgId")
first_from_wxid = APPID # 自己发出的,以文档为准
else:
# 后续使用转发接口
if first_msg_id:
url = f"{BASE}/message/forwardFile"
payload = {
"appId": APPID,
"toWxid": wxid,
"msgId": first_msg_id,
"fromWxid": first_from_wxid
}
requests.post(url, json=payload, headers=HEADERS, timeout=30)
# 每条之间随机间隔,避免频控
time.sleep(random.uniform(3, 10))
四、发送视频消息
视频消息使用 /message/postVideo 接口。视频文件体积通常较大,建议优先使用 URL 方式,并在服务器侧确保下载链接稳定可访问。
pythondef send_video(to_wxid: str, video_url: str, thumb_url: str = "") -> dict:
"""
发送视频消息
:param to_wxid: 接收方ID
:param video_url: 视频公网地址(mp4 格式)
:param thumb_url: 视频封面图 URL(可选,字段以文档为准)
"""
url = f"{BASE}/message/postVideo"
payload = {
"appId": APPID,
"toWxid": to_wxid,
"videoUrl": video_url
}
if thumb_url:
payload["thumbUrl"] = thumb_url
resp = requests.post(url, json=payload, headers=HEADERS, timeout=120)
return resp.json()
视频发送注意事项
- 格式:微信对视频格式有要求,建议提前转码为 H.264 编码的 mp4 文件。非标准格式可能导致接收方显示异常或无法播放,转码可使用 ffmpeg 在服务端自动处理。
- 大小:视频体积过大可能导致接口超时,建议单个视频控制在合理范围内(具体限制以文档为准)。超大视频可考虑先压缩分辨率或降低码率后再上传。
- timeout:视频上传耗时长,客户端超时设置建议不低于 90 秒。若使用异步任务队列,应将超时监控放在 worker 侧而非 HTTP 层。
- 封面图:传入
thumbUrl可为视频指定封面,改善接收方的预览体验。封面图建议与视频宽高比一致,避免显示变形。 - 队列:批量发视频务必走队列,每条间隔 5-15 秒,禁止并发调用。
五、发送语音消息
语音消息使用 /message/postVoice 接口。微信语音格式要求较为严格,通常需要 SILK 或 AMR 格式,其他格式(如 MP3)需在服务端转码后再发送。
pythondef send_voice(to_wxid: str, voice_url: str, duration: int) -> dict:
"""
发送语音消息
:param to_wxid: 接收方ID
:param voice_url: 语音文件 URL(silk/amr 格式,以文档为准)
:param duration: 语音时长(毫秒,字段名以文档为准)
"""
url = f"{BASE}/message/postVoice"
payload = {
"appId": APPID,
"toWxid": to_wxid,
"voiceUrl": voice_url,
"voiceDuration": duration # 字段名以官方文档为准
}
resp = requests.post(url, json=payload, headers=HEADERS, timeout=30)
return resp.json()
# 示例:发送一段 8 秒的语音
send_voice("wxid_xxxxxxxx", "https://cdn.example.com/notice.silk", 8000)
语音格式转换参考
如果原始文件是 MP3,可通过 ffmpeg 转为 AMR:
bash# 安装 ffmpeg 后执行(仅供参考,具体参数视实际需求调整)
ffmpeg -i input.mp3 -ar 8000 -ac 1 -ab 12.2k output.amr
SILK 格式转换可借助开源工具 kn007/silk-v3-decoder(纯文字提及,不作链接),在本地完成转码后再上传。
语音时长字段:voiceDuration 单位通常为毫秒,填写不准确会导致接收方播放进度条显示异常(时长偏长或偏短),应根据实际音频时长填写,而非估算值。可用 ffprobe 提取精确时长:
bashffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 output.amr
六、托管 HTTP API 的使用方案
如果不想自己搭建微信协议层,可以选择使用托管的 REST API 服务直接调用。该类平台提供扫码登录、消息收发、好友与群管理等 REST 接口,HTTP 调用即可,无需关心底层协议细节,适合快速验证或中小规模业务,例如 WechatApi。
对接流程大致如下:
- 注册账号,调用
getLoginQrCode获取二维码; - 用微信扫码,通过
checkLogin轮询确认登录状态,获得appId; - 调用
setCallback设置回调地址,接收消息推送; - 使用上述
postImage/postFile/postVideo/postVoice等接口发送媒体消息。
鉴权方式、接口域名、字段名称均以官方文档为准,不同版本可能存在差异。
七、频控与安全建议
微信对批量行为有严格的频控机制,媒体消息的发送同样需要注意节奏控制:
| 行为 | 建议策略 |
|---|---|
| 批量发图/文件 | 每条间隔 3-10 秒,随机化间隔 |
| 同一文件发多人 | 首次 postFile,后续 forwardFile,避免重复上传 |
| 视频批量发送 | 间隔不低于 10 秒,建议走任务队列 |
| 新登录设备 | 建议在线稳定 3 天后再发起批量操作 |
| 异步处理回调 | 收到回调立即返回 200,下载/转发在异步队列中处理 |
消息回调中收到媒体消息时,不要在回调函数内同步下载文件,这会阻塞回调响应,可能导致回调平台认为超时而重试,产生重复处理。正确做法是把 msgId 入队列,在异步 worker 中调用 downloadImage / downloadFile 等接口。
此外,新注册设备或长期离线重新上线的账号,建议先保持正常收发聊天若干天,再逐步开启自动化操作。频控触发后通常有冷却期,期间继续高频调用只会延长封控时间,应立即停止并等待恢复。
八、常见报错排查
| 错误现象 | 可能原因 | 排查方向 |
|---|---|---|
| ret != 200,msg 提示"操作失败" | 频率过高触发频控 | 降低调用频率,增加随机间隔 |
| 图片/视频发出但对方收不到 | 设备不在线 | 调用 checkOnline 确认登录状态 |
| 文件名乱码 | 未传 fileName 或编码问题 | 检查 fileName 字段是否为 UTF-8 |
| 语音消息发出但无声音 | 格式不对 | 确认使用 SILK/AMR,时长字段单位是否正确 |
| 视频接口超时 | 视频文件过大或网络慢 | 增大 timeout,压缩视频后重试 |
| 转发失败,提示 msgId 无效 | msgId 来自其他 appId | 确保 fromWxid 和 msgId 对应关系正确 |
| Base64 上传返回体积超限 | 图片/文件过大 | 压缩后再编码,或改用 URL 方式 |
| 语音时长显示异常 | duration 字段填写有误 | 用 ffprobe 获取精确毫秒时长后填写 |
总结
本文系统梳理了通过 REST 接口发送图片、文件、视频、语音四类媒体消息的完整方案。核心要点包括:优先使用 URL 方式传递媒体资源、通过"首次上传+后续转发"降低带宽和调用频率、语音和视频需提前确认格式和编码规范、批量操作必须引入随机间隔与异步队列以规避频控风险。掌握这些要点,可以在保持账号安全的前提下,高效构建微信富媒体自动化系统。
