我如何在不存储任何图片的情况下展示汽车照片

发布: (2026年2月22日 GMT+8 21:04)
8 分钟阅读
原文: Dev.to

Source: Dev.to

请提供您希望翻译的文章正文内容,我将按照要求保留源链接并将正文翻译成简体中文。

问题

大多数汽车数据 API 要么费用高昂,要么需要许可协议,或者只覆盖热门市场。我拥有的车型包括 Renault ClioSEAT IbizaFiat 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。有时最好的工程是找到已有的数据,指向它——并阅读许可证。

0 浏览
Back to Blog

相关文章

阅读更多 »