问题:在卖家上传图片之前,车源信息就需要一张图片
分类信息网站或购车目录的成败取决于其网格列表。每张卡片在车源发布的那一刻就需要一张图片,而卖家自己的照片往往姗姗来迟,甚至根本不会出现,拍摄角度随意,背景是随便哪条车道。图库照片则恰恰相反:只有热门年份的热门车型才有授权的媒体图片,角度和背景由制造商决定,其他车型一概没有。覆盖 1990 年至当前车型年份的目录会有数以万计的空缺。
搭建这类网站的团队想要的东西朴素得恰到好处:每个车型年款一张影棚图,全部采用相同的角度和相同的漆色,背景透明,无论卡片背景是什么都能自然融入,文件足够小,每天可承载上万次访问。
目录集成是什么样的
如今基于该 API 运行的车型目录构建都遵循相同的模式,无论是分类信息网站为其车源列表填充图片、购车应用渲染对比卡片,还是移动应用为每辆车展示一张主图:
- 先批量回填,再少量补充。先在几个小时内生成数千张渲染图覆盖整个目录,之后随着新车型年份和新请求的到来,每天只需生成少量。
- 一个角度、一种车漆,或少量配色。大多数会选择银色或白色的
front-3-4或side。允许用户选色的购物目录会为每辆车渲染几种预设颜色;每种只多算一张车辆图片,无需新建流程。 - 固定尺寸的 WebP。宽 512 至 1024 像素,按卡片所用的尺寸精确交付,前端无需裁剪或加黑边。
- 按车辆 ID 存储。渲染图存放在团队自己的对象存储或 CDN 上,或者由页面加载签名 URL;无论哪种方式,键都是目录中稳定的
veh_…ID,而不是品牌-车型-年份字符串。
本页其余部分将逐步介绍该流程,并附上代码。前提是您拥有具备 images:read 权限范围的 API 密钥,以及与目录规模相匹配的套餐(参见常见问题)。
第 1 步:按 id 而非拼写遍历目录
目录构建中最常见的问题不是渲染,而是拼写。如果从另一个数据库生成品牌和车型字符串,大部分请求可能都会返回 404 vehicle_not_found:虽然免费,但每次都白白浪费一次往返请求,网格里也会留下空缺。解决办法是让目录告诉您有哪些车辆。GET /api/v1/vehicles 免费且无需密钥,返回的每个车型都带有稳定的车辆 ID(veh_ 加 13 个字符),在不同部署和目录版本之间始终不变。将该 ID 存储在车源信息中,并按该 ID 渲染。
# every make with a 2024 model year (free, no key)
curl -s "https://carimage.dev/api/v1/vehicles?year=2024"
# every 2024 model of one make, each with the id you will store
curl -s "https://carimage.dev/api/v1/vehicles?year=2024&makeId=584"{
"data": {
"year": 2024,
"make_id": 584,
"models": [
{
"name": "718",
"slug": "718",
"vehicle_type": "Passenger Car",
"id": "veh_b4bf090fyb53k"
},
{
"name": "911",
"slug": "911",
"vehicle_type": "Passenger Car",
"id": "veh_78qtwrgh37bkr"
}
]
}
}?make=porsche&model=911 会列出某一车型的所有年份,每个年份对应一个 id;当卖家输入名称时,?q= 可进行自由文本搜索。同一目录也以开源包 @meterapp/vehicle-db 的形式发布,因此下拉菜单和校验可以离线对照完全相同的列表运行。无论采用哪种方式,完成这一步后您都会得到一组 id,后续环节再也不需要拼写车名。
如果车源信息已经用另一个数据库的写法命名车辆,一次调用即可完成映射:POST /api/v1/vehicles/check 会按照渲染时相同的方式读取最多 2,000 条记录,并逐条返回其车辆 ID;若目录中没有该车,则返回最接近的真实车辆;或者返回未命中,表示在所有视角和颜色中都应跳过。car-image check listings.csv --out checked.csv 会将 ID 写回文件。该功能免费,让您不必再靠一个个 404 去发现缺口。
第 2 步:所有车源使用同一种请求格式
卡片样式只需确定一次,并编码在查询字符串中。下方 vehicle= 之后的所有内容,都是您为整个目录一次性做出的构图决定:
curl --fail-with-body -H "Authorization: Bearer $CAR_IMAGE_API_KEY" \
"https://carimage.dev/api/v1/images/car?vehicle=veh_78qtwrgh37bkr&view=front-3-4&color=silver&w=800&h=600&fit=contain&trim=1&padding=8&format=webp" \
-o veh_78qtwrgh37bkr.webpw=800&h=600&fit=contain会输出精确的 800×600 图片,整车完整可见,四周为透明留白,因此每张卡片的画布都一致。fit=cover则会填满整个区域并居中裁剪。trim=1&padding=8会在适配尺寸前沿车辆轮廓裁剪,并以其长边的 8% 作为边距,这样 Suburban 和 Miata 会以相同方式填满画框,而不是悬浮在原始画面中。color=silver是默认值,也是最稳妥的中性色;15 种预设色或任意十六进制颜色计费相同,均为一张车辆图片。format=webp是体积最小的无损 Alpha 编码。当浏览器直接加载该 URL 时(签名 URL,第 4 步),format=auto会针对每个客户端协商使用 WebP 或 PNG。
响应中包含 ETag、X-Image-Source(cache 或 generated)以及 X-Vehicle-Id,即实际返回的目录车辆。请将 ETag 与文件一同保存:之后带 If-None-Match 的请求会返回 304,且不产生任何费用,夜间重新同步因此无需付费。
第 3 步:在限额内控制回填节奏
回填受三项限制约束,每一项都会通过响应头明确告知,而不会静默失败:
- 每分钟请求数。Free 和 Pro 套餐每个密钥 120 次,Business 套餐 600 次,Enterprise 套餐 1,200 次,此外还有覆盖所有密钥的账户级限制。每个经过身份验证的响应都带有
RateLimit-Remaining;429响应带有Retry-After。同时保持四到八个进行中的请求就足够了。 - 新的车辆视角。对于我们尚未准备的车辆视角,首次请求需要稍等片刻,以便我们完成准备,使其能以任意颜色展示;之后的所有请求(任意颜色)都将从缓存提供。准备新视角会占用账户在当日渲染预算中的份额(免费版 2%、Pro 版 10%、Business 版 25%、企业版 50%)。超出后,新的渲染请求将返回
429,并附带code: account_generation_cap和reset_at(UTC 午夜),不会产生任何费用,已缓存的图片仍可正常提供。大规模回填需要分几个晚上完成,而非一次完成。 - 每月车辆图片数。Free 含 100 张,Pro 2,500 张,Business 10,000 张;Enterprise 需协商。超出额度后,请求本月尚未计数的图片将返回
402(plan_image_limit),且不计数;本月已计数的图片始终正常提供,因此线上图片网格不会中断。
渲染失败(502)会自动退还对应的车辆图片额度,针对同一新变体的并发请求共享一次渲染,普通的 GET 可安全重试。SDK 会遵循 Retry-After 自动为您重试 429 和 503,但绝不会重试 402。
import { CarImageClient, CarImageError } from "@meterapp/car-image-sdk";
const client = new CarImageClient({ apiKey: process.env.CAR_IMAGE_API_KEY });
// One make, one model year: the ids are what you store and what you render by.
const { data } = await client.vehicles({ year: 2024, makeId: 584 });
const queue = data.models.flatMap((model) => (model.id ? [model.id] : []));
// Four in flight stays well inside 120 requests a minute, and a cold render
// that takes a few seconds never holds up the rest of the batch.
await Promise.all(
Array.from({ length: 4 }, async () => {
for (let id = queue.shift(); id; id = queue.shift()) {
try {
const image = await client.getImage({
vehicle: id, view: "front-3-4", color: "silver",
width: 800, height: 600, fit: "contain", trim: true, padding: 8, format: "webp",
});
await store(id, image.bytes, image.etag); // your object store or CDN, keyed by vehicle id
} catch (error) {
// 429 and 503 were already retried with Retry-After; a 404 is a vehicle to request, not a failure.
if (error instanceof CarImageError && error.status === 404) continue;
throw error;
}
}
})
);第 4 步:通过您的 CDN 或签名 URL 提供图片
有两种好方法可以将图片呈现给购车者,目录构建方案两种都会用到:
- 存储图片文件。将每张渲染图以其车辆 ID 为键写入您自己的对象存储或 CDN,并像其他静态资源一样提供。对于需要渲染数百万次的网格,这是正确的做法:每次加载都无需调用我们的服务,而且付费套餐的许可涵盖您为支持产品服务而保留的副本。
- 生成签名 URL。
POST /api/v1/image-urls每次调用最多接受五十张图片,在每个 URL 生成时将其图片计为当月的一张车辆图片,并返回浏览器可直接加载、无需密钥的 URL。ttl_seconds最长可设为七天;对于存在时间更长的列表页面,请添加renew: true(续期会在其所在月份将图片计一次,如果该图片当月已计过,则不再计数)。在控制台中将密钥限制为您网站的来源后,复制到其他地方的 URL 将返回403。
批量生成时,请为每页列表发送一个 Idempotency-Key:使用相同密钥和请求体重试的调用会重放首次响应,不会再次计数。
{
"images": [
{
"vehicle": "veh_78qtwrgh37bkr",
"view": "front-3-4",
"color": "silver",
"width": 800,
"height": 600,
"fit": "contain",
"trim": true,
"padding": 8,
"format": "auto"
},
{
"vehicle": "veh_b4bf090fyb53k",
"view": "front-3-4",
"color": "silver",
"width": 800,
"height": 600,
"fit": "contain",
"trim": true,
"padding": 8,
"format": "auto"
}
]
}Authorization 标头。一旦密钥出现在页面中,任何打开开发者工具的人都能读取它。当车辆不在目录中时
先搜索:GET /api/v1/vehicles?q= 可按名称、前缀或模糊匹配查找车型,POST /api/v1/images/resolve 可将自由文本短语转换为精确参数,并附带可供您据以决策的置信度。此后仍找不到的车型,请提交到需求看板:POST /api/v1/requests 会创建需求;若同一品牌、车型和年份已有未关闭的需求,则会为其投票,而不会重复创建。目录本身是开源的,新增内容会面向所有客户发布。
CLI、SDK 和 MCP
整个流程无需编写任何代码,通过 CLI 即可运行,API 上已有不止一个目录正是这样完成初始数据填充的:
# One file per vehicle id. Re-running skips what exists, so a stopped backfill resumes.
while read -r id; do
[ -f "cdn/$id.webp" ] || npx @meterapp/car-image get --vehicle "$id" \
--view front-3-4 --color silver --width 800 --height 600 --fit contain \
--trim --padding 8 --format webp --out "cdn/$id.webp"
done < vehicle-ids.txt
# Or fifty signed URLs in one call, for a page of listings the browser loads itself.
npx @meterapp/car-image url --batch listings.json --ttl 604800 --renew --json- SDK:使用
client.vehicles({ year, makeId })进行枚举,client.getImage({ vehicle, … })进行渲染,client.createImageUrls(…)批量生成签名 URL。所有参数请参阅图像文档。 - MCP:连接到
https://carimage.dev/api/mcp的助手可以使用相同的目录(search_vehicles、resolve_vehicle)和相同的渲染图(get_car_image、create_car_image_urls)。它适合由内容编辑手动添加车辆,但不适合数以千计的批量回填:回填请使用 CLI 或 SDK,助手只用于处理零星遗漏。设置方法请参见 MCP 页面。
常见问题
- 车源信息应该使用哪个角度?
- front-3-4 是默认视角,也是经典的主视觉角度,车头朝左。车辆左侧的每个角度都有对应的 -right 版本(front-3-4-right、side-right、rear-3-4-right),因此卡片可以朝向版面内侧而非外侧。选定一个并坚持使用:各条车源信息之间的一致性才是主要价值所在。
- 一个包含 20,000 条车源信息的目录需要多少费用?
- 如果每条商品信息展示一辆车的一种车漆和一个视角,每月需要 20,000 张车辆图片,每张可按任意尺寸按需多次交付;共享同一车辆、车漆和视角的商品信息算作一张图片,使用 If-None-Match 重新校验免费。Pro 每月包含 2,500 张车辆图片,Business 包含 10,000 张,因此这种规模的目录适合 Enterprise,或一次性购买可永久保留并自行托管的图片包。超出额度的回填请求会返回 402,且不计任何用量。
- 我们能把文件存放在自己的 CDN 上吗?
- 可以。付费套餐有效期间,渲染图获授权在您的产品中使用,包括存放在您自己服务器或 CDN 上的副本,且无需署名;套餐结束后 30 天内,已存储的副本须删除(图片包中的图片可永久保留)。授权不允许将其作为独立的图库、数据源或数据集提供。只有处于有效状态的套餐才能渲染下一个车型年份的车辆。
- 目录新增车型年款时会发生什么?
- @meterapp/vehicle-db 目录会随数据源发布而更新,现有 id 永不改变。枚举新年款,渲染您尚未拥有的 id,其余保持不变即可。请求带有 ETag;使用 If-None-Match 的重复请求会返回 304,且不产生费用。
- 为什么我们的部分品牌和车型字符串会返回 404?
- 因为它们是按另一个数据库的拼写写的。目录接受名称、slug 以及多种徽标和车身样式变体,但从未见过的字符串会返回 404 vehicle_not_found(不计费,但白白浪费一次请求往返)。请遍历目录并存储其 ID,而不是转换您自己的字符串;对剩余无法匹配的条目,再进行搜索或解析。