我如何在不存储任何图片的情况下展示汽车照片
Source: Dev.to
请提供您希望翻译的文章正文内容,我将按照要求保留源链接并将正文翻译成简体中文。
问题
大多数汽车数据 API 要么费用高昂,要么需要许可协议,或者只覆盖热门市场。我拥有的车型包括 Renault Clio、SEAT Ibiza 和 Fiat Punto——这些是常见的欧洲汽车,甚至可能在以美国为中心的付费 API 中都找不到。
即使找到了来源,在 R2 或 S3 上托管成千上万的图片也意味着:
- 一个下载并存储图片的流水线
- 随着目录增长的存储成本
- 当图片过期时的维护工作
必须有更好的办法。
解决方案:Wikimedia Commons
Wikimedia Commons 是一个免费媒体库,拥有数百万张图片——包括大量的汽车照片,全部采用 Creative Commons 许可证。它还提供了一个 公共 JSON API,无需身份验证、无需 API 密钥,也没有每次请求的费用。
技巧: 在 Commons 中搜索 "BMW 3 Series" 并获取第一条图片结果。完成。
const url =
"https://commons.wikimedia.org/w/api.php" +
"?action=query" +
"&generator=search" +
"&gsrsearch=" + encodeURIComponent(`${brand} ${model}`) +
"&gsrnamespace=6" + // namespace 6 = File/Image namespace only
"&prop=imageinfo" +
"&iiprop=url" +
"&format=json" +
"&origin=*"; // enables CORS for browser requests
const response = await fetch(url);
const data = await response.json();
const pages = data.query?.pages;
const firstPage = Object.values(pages)[0];
const imageUrl = firstPage?.imageinfo?.[0]?.url;
就这么简单。我得到一个直接指向 Wikimedia CDN 上全分辨率图片的 URL。我的服务器从未触碰它。
Svelte 组件
我把它封装进一个 VehicleImage 组件,能够优雅地处理加载、错误以及回退:
<script lang="ts">
import { onMount } from 'svelte';
export let brand: string;
export let model: string;
export let year: number | null = null;
let imageUrl: string | null = null;
let loading = true;
let error = false;
$: searchQuery = year ? `${brand} ${model} ${year}` : `${brand} ${model}`;
async function loadImage() {
loading = true;
error = false;
try {
const url =
'https://commons.wikimedia.org/w/api.php' +
'?action=query&generator=search' +
'&gsrsearch=' + encodeURIComponent(searchQuery) +
'&gsrnamespace=6&prop=imageinfo&iiprop=url&format=json&origin=*';
const res = await fetch(url);
const data = await res.json();
const pages = data.query?.pages;
const first = Object.values(pages ?? {})[0] as any;
imageUrl = first?.imageinfo?.[0]?.url ?? null;
if (!imageUrl) error = true;
} catch {
error = true;
} finally {
loading = false;
}
}
onMount(loadImage);
</script>
{#if loading}
<p>Loading image…</p>
{:else if error || !imageUrl}
<p>Image not available.</p>
{:else}
<img src={imageUrl} alt="{brand} {model}" />
{/if}
关键洞察: 获取操作在 浏览器端 完成,而不是在服务器上。这意味着:
- 零服务器负载
- 零存储成本
- 图片直接由 Wikimedia 的全球 CDN 提供给用户
- 如果 Wikimedia 宕机,我们会显示优雅的回退——不会抛出错误
为什么采用客户端方式?
我运行在 Cloudflare Workers 上,免费层对某些操作有 10 ms 的 CPU 限制。若在服务器端为每次页面加载都去 Wikimedia 抓取图片,会非常浪费且可能触发超时。通过在 onMount 中在客户端获取,页面能够立即渲染 SSR 内容,图片随后“弹出”,从而获得更好的感知性能。
注意事项:第一条结果并不总是完美
Wikimedia 的首个搜索结果并不保证是该车型的精准照片。有时会出现:
- 来自略微不同年份的照片
- 工厂或车展的图片
- 偶尔出现的内部拍摄
因此我在所有六种语言中都加入了免责声明:
“图片仅作示例,可能并不完全代表所描述的车型。”
对于社区评论平台来说,这完全可以接受。用户并不是在向我们购买——他们是在阅读评论。一个大致的图片总比空白框好得多。
法律方面:署名很重要
有一点容易被忽视:Wikimedia Commons 上的图片默认并非公共领域。 大多数图片采用 Creative Commons 许可证,这些许可证有真实的要求:
| 许可证 | 要求内容 |
|---|---|
| CC BY | 署名作者 |
| CC BY‑SA | 署名作者 + 在相同许可证下共享衍生作品 |
| Public Domain | 无限制——自由使用 |
如果你仅仅获取 URL 并在页面上显示图片而不进行署名,可能会违反许可证——即使 Wikimedia 并未技术上阻止热链。
获取署名元数据
好消息是:返回图片 URL 的同一个 API 调用也可以返回你遵守许可证所需的全部信息。只需在 iiprop 参数中加入 extmetadata:
const url =
"https://commons.wikimedia.org/w/api.php" +
"?action=query" +
"&generator=search" +
"&gsrsearch=" + encodeURIComponent(searchQuery) +
"&gsrnamespace=6" +
"&prop=imageinfo" +
"&iiprop=url|extmetadata" + // request URL and metadata
"&format=json" +
"&origin=*";
响应中的 extmetadata 字段包含许可证名称、作者、署名 URL 等信息。提取这些值并在图片下方渲染适当的署名行,即可满足 Creative Commons 的要求。
图片横幅

<div class="attribution">
Via Wikimedia Commons
{#if author} · {author}{/if}
{#if licenseShortName}
· {licenseShortName}
{/if}
</div>
这会渲染出类似的内容:
Via Wikimedia Commons · Vauxford · CC BY‑SA 4.0
这正是许可证所要求的——同时也提升了页面的可信度,而不是削弱它。
那么热链呢?
Wikimedia 明确允许通过其 CDN(upload.wikimedia.org)进行外部图片嵌入。他们并未阻止热链。但其使用条款以及各文件的许可证仍然适用。即使技术上可行,未进行署名的图片服务仍然是对许可证的违规。
正确的思维模型: 热链被允许,署名是必需的。
结果
-
0 张图片已存储 在我的数据库或任何存储桶中
-
0 个 API 密钥 需要管理或轮换
-
0 元月度费用 用于图片服务
-
适用于 每个在 Wikipedia/Commons 上有条目的欧洲汽车品牌
-
对真正冷门的车型提供优雅的回退
-
完全合法合规 只需一个额外的 API 字段和四行 HTML
整个解决方案约 50 行 Svelte。有时最好的工程是找到已有的数据,指向它——并阅读许可证。