Bigjpg API 教程:用 Python 批量放大图片
一份能直接跑的脚本:上传图片、创建放大任务、轮询结果、下载成品,外加并发控制和错误处理,让 500 张的批量任务不会跑到一半就断掉。
处理几张图,网页界面就够了。要面对一个五百张的文件夹,还是用 API 省事:四个接口、不用开浏览器、脚本随时能重跑。下面这一版就是我们平时会直接丢给同事的代码——上传、创建、轮询、下载,再加上实际会遇上的失败处理。
开始之前
- API 只对付费账号开放。登录后带上有效套餐,密钥就在 API 参考页 上。
- Python 3,装好
requests,别的都不用。 - 放大在我们的 worker 上异步跑,所以脚本是「先建任务、再轮询」。五百张的任务,耗时取决于你所在队列里最慢的那张,而不是把每张的时间加起来。
脚本
import json
import os
import time
import requests
API = "https://bigjpg.com"
KEY = "YOUR_API_KEY"
STYLE = "art" # "art" 用于插画,"photo" 用于照片
NOISE = "1" # -1 不降噪,0 低,1 中,2 高,3 最高
X2 = "2" # 1 = 2x,2 = 4x,3 = 8x,4 = 16x
POLL_SECONDS = 8
MAX_RETRY = 1
HEADERS = {"X-API-KEY": KEY}
def upload(path):
"""Get a presigned URL, PUT the file, return its public URL."""
name = path.split("/")[-1]
r = requests.post(
API + "/api/img_upload",
headers=HEADERS,
data={"filename": name, "filetype": "image/jpeg"},
timeout=60,
)
info = r.json()
if info.get("status") != "ok":
raise RuntimeError("upload failed: " + str(info))
with open(path, "rb") as image:
r = requests.put(
info["url"],
data=image,
headers={"Content-Type": "image/jpeg"},
timeout=300,
)
if r.status_code != 200:
raise RuntimeError("upload failed with HTTP %s" % r.status_code)
return info["fileurl"]
def create(image_url):
"""Create an enlargement task, return its task id."""
payload = {
"style": STYLE,
"noise": NOISE,
"x2": X2,
"input": image_url,
}
r = requests.post(
API + "/api/task/",
headers=HEADERS,
data=json.dumps(payload),
timeout=60,
)
data = r.json()
if "tid" not in data:
raise RuntimeError("task failed: " + str(data))
return data["tid"]
def poll(tids):
"""Query up to 100 task ids at once. Returns task id -> result dict."""
r = requests.get(API + "/api/task/" + ",".join(tids), timeout=60)
return r.json()
def download(url, out):
"""Download a finished image, checking the transfer really succeeded."""
r = requests.get(url, timeout=300)
if r.status_code != 200:
raise RuntimeError("download failed with HTTP %s" % r.status_code)
with open(out, "wb") as f:
f.write(r.content)
return out
def main(paths):
pending = {}
attempts = {}
for path in paths:
try:
tid = create(upload(path))
except Exception as e:
print("skipped", path, e)
continue
pending[tid] = path
attempts[tid] = 0
print("queued", path, tid)
while pending:
for tid, info in poll(list(pending)[:100]).items():
if info.get("status") == "success":
out = os.path.splitext(pending[tid])[0] + "_bigjpg.jpg"
try:
download(info["url"], out)
except Exception as e:
print("download failed", pending[tid], e)
pending.pop(tid)
continue
print("done", pending.pop(tid), "->", out)
elif info.get("status") == "error":
# 重试一次,不行就放弃这个文件。
attempts[tid] += 1
if attempts[tid] <= MAX_RETRY:
requests.post(API + "/api/task/" + tid, headers=HEADERS, timeout=60)
print("retrying", pending[tid])
else:
print("giving up on", pending.pop(tid))
if pending:
time.sleep(POLL_SECONDS)
if __name__ == "__main__":
import sys
main(sys.argv[1:])
每个接口做什么
| 调用 | 作用 | 说明 |
|---|---|---|
| POST /api/img_upload | 返回预签名的上传地址和图片公开地址 | 用你声明过的 Content-Type PUT 上传文件,然后把 fileurl 当作任务的 input |
| POST /api/task/ | 创建放大任务 | 返回 tid、预估的 minute,以及 remaining_api_calls |
| GET /api/task/{ids} | 查询一个或多个任务的状态 | id 用逗号分隔,单次最多 100 个 |
| POST /api/task/{ids} | 重试失败的任务 | 只有处于 error 状态的任务会回到队列 |
并发与礼数
- 轮询别太勤。放大不是瞬间完成的,5–10 秒查一次足够;一次问 100 个 id,请求数也压得住。
- 限制上传并发。单台机器同时跑两到四个上传比较合适。想要更高吞吐就多开几个实例,别在一个实例里加线程。
- 退避重试。网络错误不等于任务失败。HTTP 调用最多重试三次,间隔一次比一次长,再判定文件真的坏了。
- 盯着
remaining_api_calls。每个创建成功的任务都会带上它;设一个低位阈值就停下,别等报错刷屏才发现额度见底。 - 把对应关系记下来。用一份文件记下
tid、输入文件名、参数和输出文件名。任务跑一整夜时,这份日志是唯一能查证的东西。
实际会撞上的错误
valid_api_key_required—— 请求头缺失或无法识别。检查头名称,并确认这个密钥所属的套餐还没过期。requires_vip—— 免费或已过期账号。API 只面向有效期内的付费套餐。month_limit—— 当月额度用完。每个任务都会返回剩余量,完全可以在这之前主动收手。param_error——style只能是 art 或 photo,noise取 -1 到 3,x2取 1 到 4。input_url_error—— 输入必须是能公开访问的 http 或 https 图片地址。预签名的私有地址会过期;先上传,再把返回的公开地址传进去。
批量要花多少
2x、4x、8x 每张算一个额度,16x 算两个。五百张按 4x 就是 500 额度,同一批走 16x 就是 1000。多数照片本来用 4x 就够,所以便宜的那个选项往往也是更好看的那个——原因见 倍率选择指南。
完整的接口文档,包括所有参数和每一种错误状态,都在 API 页面。如果脚本里有什么和你实际拿到的返回不一致,以那一页为准,我们一直在维护它。