美团图床上传接口实测:curl 与批量脚本接入

作者 mcx 日期 2026-09-09
美团图床上传接口实测:curl 与批量脚本接入

最近给 Qx 社区插件整理市场截图时,需要把几十张图片迁移到访问更快的 CDN。本文记录 aapi.helioho.st/upload.php 的实际请求格式、返回结构、限流规则,以及一个可以断点续传的批量上传脚本。

图片上传接口与 CDN 分发手绘示意图

接口信息

项目 内容
上传地址 https://aapi.helioho.st/upload.php
请求方法 POST
请求格式 multipart/form-data
文件字段 image
成功图片域名 https://img.meituan.net/content/
单张限制 最大 20 MB
页面单次选择 最多 10 张

网页上传入口是:

1
https://695402.xyz/mt/?ref=www.549.tv

用 curl 上传一张图片

接口会检查来源。只发送文件通常会返回 HTTP 403,需要带上网页使用的 OriginReferer 和常见浏览器标识:

1
2
3
4
5
6
7
curl -fsS 'https://aapi.helioho.st/upload.php' \
-H 'Origin: https://695402.xyz' \
-H 'Referer: https://695402.xyz/mt/?ref=www.549.tv' \
-H 'User-Agent: Mozilla/5.0' \
-H 'Accept: application/json, text/javascript, */*; q=0.01' \
-F 'image=@./screenshot.jpg' \
-F 'file_id=0'

成功时返回 JSON:

1
2
3
4
5
6
7
{
"code": 200,
"msg": "success",
"data": {
"url": "https://img.meituan.net/content/xxxxxxxx.jpg"
}
}

业务代码不要只检查 HTTP 状态,还要同时检查 code === 200data.url。接口触发限流时仍可能返回一段合法 JSON,但其中 code500data 也可能从对象变成空数组。

可断点续传的批量脚本

下面的 Python 脚本读取 uploads.json。键是本地图片路径,值为空表示待上传;每成功一张就立即写回 CDN 地址,因此中途失败后可以直接重新运行。

先准备清单:

1
2
3
4
{
"images/01.jpg": "",
"images/02.png": ""
}

保存以下代码为 upload_images.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import json
import pathlib
import subprocess
import time

manifest_path = pathlib.Path("uploads.json")
uploads = json.loads(manifest_path.read_text())

for index, (filename, uploaded_url) in enumerate(uploads.items()):
if uploaded_url:
print(f"skip: {filename}")
continue

if index:
# 实测限制为一分钟最多 10 张,主动留出间隔。
time.sleep(7)

command = [
"curl", "-fsS",
"https://aapi.helioho.st/upload.php",
"-H", "Origin: https://695402.xyz",
"-H", "Referer: https://695402.xyz/mt/?ref=www.549.tv",
"-H", "User-Agent: Mozilla/5.0",
"-H", "Accept: application/json, text/javascript, */*; q=0.01",
"-F", f"image=@{filename}",
"-F", f"file_id={index}",
]

raw = subprocess.check_output(command, text=True, timeout=60)
response = json.loads(raw)
data = response.get("data")
url = data.get("url") if isinstance(data, dict) else None

if response.get("code") != 200 or not url:
raise RuntimeError(f"upload failed: {filename}: {raw}")

uploads[filename] = url
manifest_path.write_text(
json.dumps(uploads, ensure_ascii=False, indent=2) + "\n"
)
print(f"uploaded: {filename} -> {url}")

运行:

1
python3 upload_images.py

限流规则

这次实际迁移 32 张截图时遇到了两层频率限制:

  • 1 分钟最多上传 10 张。
  • 1 小时最多上传 30 张。

服务端对应的错误信息分别包含“1分钟内限制10张”和“1小时内限制30张”。批量任务应当控制速度、持久化进度,并在收到限流响应时停止或延迟重试,不能把每个失败立即连续重放。

如果终端配置了共享代理,代理出口可能和其他请求共用额度。排查时可以比较代理与直连结果;不要为了绕过限制进行高并发上传。

接入静态站点

我的处理方式是保留本地源图,同时维护一份文件名到 CDN URL 的映射:

1
2
3
4
5
{
"qxpicture": {
"qxpicture-1.jpg": "https://img.meituan.net/content/4104c2c397dc61700f974bc956233caf118800.jpg"
}
}

构建页面时优先使用经过校验的 CDN URL,映射缺失时退回本地文件:

1
2
3
4
const cdnUrl = screenshotCdn?.[pluginId]?.[filename];
const imageUrl = /^https:\/\/img\.meituan\.net\/content\//.test(cdnUrl || "")
? cdnUrl
: localUrl;

这样页面能利用 CDN,插件包或离线环境仍有本地图片可用。新增图片也不会因为忘记上传而直接显示空白。

上传后的检查

界面截图、图标和其他包含细小文字或锐利边缘的图片,建议先转为 PNG:

1
sips -s format png ./screenshot.jpg --out ./screenshot.png

实测 JPEG 上传后会再次有损压缩。Qxpicture 的一张界面截图从本地 118,800 字节变成 88,395 字节,文字边缘明显变软;同一源图转成 PNG 后再上传,CDN 文件解码后的像素与上传文件完全一致。

至少检查以下内容:

  1. 每个 URL 都能返回 HTTP 200。
  2. Content-Type 是图片类型。
  3. 远端像素宽高与本地源图一致。
  4. 页面中实际加载的是 CDN URL,而不是旧缓存或本地路径。
  5. 截图中没有令牌、账号、主机名等敏感信息。

本次最终迁移了 21 个插件图标和 32 张插件截图。53 个 CDN 文件均返回 image/png,解码后 53/53 与上传文件逐像素一致。PNG 文件的字节哈希仍可能因元数据或压缩方式不同而变化,因此应比较解码像素,而不是只比较文件哈希。

使用边界

这是第三方公开接口,域名、限流和返回格式都可能调整。生产使用时应保留本地源文件、记录返回 URL,并让构建过程具备回退路径。不要把包含访问令牌、私人页面或未遮盖账号信息的截图上传到公共图床。


实测日期:2026-09-09