URL 元数据 API:一次调用即可构建像 Slack 一样的链接预览
使用一个 POST 请求从任何 URL 中提取 Open Graph 标签、Twitter 卡数据、网站图标和页面标题。 用不到 20 行代码构建链接预览卡。
用户将 URL 粘贴到您的聊天应用程序中。 您想要显示带有页面标题的丰富预览卡, 描述和缩略图; Slack、Discord 和 iMessage 显示相同的卡片。 你可以 获取页面、解析 HTML,然后自行提取 Open Graph 标签。 或者您可以发送一份 发布请求。
波托伊 /v1/url-metadata 端点获取任何 URL,读取其
<meta> 标签,并返回结构化 JSON:OG 标题、OG 描述、OG 图像、
Twitter 卡数据、网站图标、规范 URL、语言等。 一次调用取代了 fetch,
HTML 解析器和后备逻辑。
终点
curl -X POST https://api.botoi.com/v1/url-metadata \\
-H "Content-Type: application/json" \\
-d '{ "url": "https://github.com/anthropics/claude-code" }'
回复:
{
"success": true,
"data": {
"url": "https://github.com/anthropics/claude-code",
"status": 200,
"content_type": "text/html",
"title": "anthropics/claude-code: Claude Code is an agentic coding tool",
"description": "Claude Code is an agentic coding tool that lives in your terminal",
"og": {
"title": "anthropics/claude-code",
"description": "Claude Code is an agentic coding tool that lives in your terminal",
"image": "https://opengraph.githubassets.com/1/anthropics/claude-code",
"type": "object",
"url": "https://github.com/anthropics/claude-code",
"site_name": "GitHub"
},
"twitter": {
"card": "summary_large_image",
"title": "anthropics/claude-code",
"description": "Claude Code is an agentic coding tool that lives in your terminal",
"image": null
},
"favicon": "https://github.com/favicon.ico",
"canonical": "https://github.com/anthropics/claude-code",
"language": "en",
"author": null,
"keywords": [],
"theme_color": null
}
}
该响应为您提供渲染链接预览卡所需的一切。 这
og 对象包含 Slack 和 Discord 读取的 Open Graph 标签。
这 twitter 对象包含 Twitter 卡标签。 当页面设置两者时,您会得到
两者都有。 当页面两者都没有设置时,您仍然会获得 HTML title 和
description 作为后备。
构建聊天应用链接预览组件
这个 Preact 组件接受一个 URL,调用 API,并渲染一张带有 OG 图像、标题、 描述和站点名称。 当 OG 标签丢失时,它会回退到 HTML 标题。
import { useState, useEffect } from "preact/hooks";
interface LinkPreview {
title: string | null;
description: string | null;
image: string | null;
favicon: string | null;
url: string;
siteName: string | null;
}
function useLinkPreview(url: string) {
const [preview, setPreview] = useState<LinkPreview | null>(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!url) return;
setLoading(true);
fetch("https://api.botoi.com/v1/url-metadata", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url }),
})
.then((res) => res.json())
.then(({ data }) => {
setPreview({
title: data.og?.title || data.title,
description: data.og?.description || data.description,
image: data.og?.image || null,
favicon: data.favicon,
url: data.canonical || url,
siteName: data.og?.site_name || null,
});
})
.catch(() => setPreview(null))
.finally(() => setLoading(false));
}, [url]);
return { preview, loading };
}
function LinkPreviewCard({ url }: { url: string }) {
const { preview, loading } = useLinkPreview(url);
if (loading) {
return (
<div class="rounded-lg border border-gray-200 p-4 animate-pulse">
<div class="h-4 bg-gray-100 rounded w-3/4 mb-2"></div>
<div class="h-3 bg-gray-100 rounded w-full"></div>
</div>
);
}
if (!preview) return null;
return (
<a
href={preview.url}
target="_blank"
rel="noopener noreferrer"
class="block rounded-lg border border-gray-200 overflow-hidden
hover:border-gray-400 transition-colors no-underline"
>
{preview.image && (
<img
src={preview.image}
alt=""
class="w-full h-40 object-cover"
/>
)}
<div class="p-4">
<div class="flex items-center gap-2 mb-2">
{preview.favicon && (
<img src={preview.favicon} alt="" class="w-4 h-4" />
)}
<span class="text-xs text-gray-500">
{preview.siteName || new URL(preview.url).hostname}
</span>
</div>
<p class="font-semibold text-sm text-gray-900 mb-1">
{preview.title}
</p>
<p class="text-xs text-gray-600 line-clamp-2">
{preview.description}
</p>
</div>
</a>
);
}
这 useLinkPreview hook 处理获取并将 API 响应映射到平面
您的 UI 可以使用的对象。 后备链(data.og?.title || data.title)
意味着即使页面的 OG 标签为零,您也始终可以显示一些内容。 组件
在 API 调用正在进行时渲染加载骨架,然后交换预览卡。
在 CMS 中自动填充 SEO 元数据
内容编辑者在撰写文章时粘贴参考 URL。 而不是让它们复制粘贴 手动输入标题和描述,您的 CMS 可以从 URL 中提取该数据并预填充 搜索引擎优化领域。
async function autoFillSeoFields(referenceUrl: string) {
const res = await fetch("https://api.botoi.com/v1/url-metadata", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: \`Bearer \${process.env.BOTOI_API_KEY}\`,
},
body: JSON.stringify({ url: referenceUrl }),
});
const { data } = await res.json();
return {
seoTitle: data.og?.title || data.title || "",
seoDescription: data.og?.description || data.description || "",
ogImage: data.og?.image || "",
canonical: data.canonical || referenceUrl,
favicon: data.favicon || "",
};
}
// Usage in a CMS admin panel
const fields = await autoFillSeoFields("https://stripe.com/docs/payments");
// fields.seoTitle → "Payments | Stripe Documentation"
// fields.seoDescription → "Accept payments online..."
// fields.ogImage → "https://images.stripe.com/..."
当编辑者将 URL 粘贴到“参考”字段时,CMS 会调用 autoFillSeoFields,
填充 SEO 标题、描述和 OG 图像输入,并让编辑器从那里进行调整。
同样的方法适用于书签管理器、稍后阅读应用程序和内部 wiki 工具
从粘贴的链接自动生成卡片。
具有超时和错误处理功能的 Node.js 函数
在生产中,您需要超时,以便缓慢的目标页面不会无限期地阻止您的请求。
该函数用 5 秒的时间封装 API 调用 AbortController 超时和
回报 null 失败而不是抛出。
async function getLinkPreview(url: string) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
try {
const res = await fetch("https://api.botoi.com/v1/url-metadata", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: \`Bearer \${process.env.BOTOI_API_KEY}\`,
},
body: JSON.stringify({ url }),
signal: controller.signal,
});
if (!res.ok) {
throw new Error(\`API returned \${res.status}\`);
}
const { success, data } = await res.json();
if (!success) {
return null;
}
return {
title: data.og?.title || data.title,
description: data.og?.description || data.description,
image: data.og?.image,
favicon: data.favicon,
siteName: data.og?.site_name,
canonical: data.canonical,
twitterCard: data.twitter?.card,
};
} catch (err) {
console.error(\`Failed to fetch preview for \${url}:\`, err);
return null;
} finally {
clearTimeout(timeout);
}
}
该函数返回一个干净的对象,其中包含您的 UI 所需的字段。 来电者不接触原始的
API 响应。 如果目标页面宕机或者请求超时,函数返回
null 并且您的应用程序可以显示回退而不是崩溃。
处理边缘情况
并非每个 URL 都能配合。 有些页面没有 OG 标签。 有些位于重定向链后面。 一些 需要 10 秒来响应。 以下是如何处理每种情况。
// 1. Pages with no OG tags: fall back to title + description
const preview = await getLinkPreview(url);
const displayTitle = preview?.title || "Untitled page";
const displayDesc = preview?.description || url;
const displayImage = preview?.image || "/fallback-thumbnail.png";
// 2. Detect redirects by comparing input URL to canonical
const inputUrl = "https://bit.ly/3xYzAbc";
const result = await getLinkPreview(inputUrl);
if (result?.canonical !== inputUrl) {
console.log(\`Redirected to: \${result?.canonical}\`);
}
// 3. Batch multiple URLs with Promise.allSettled
const urls = [
"https://github.com",
"https://stripe.com",
"https://vercel.com",
];
const previews = await Promise.allSettled(
urls.map((u) => getLinkPreview(u))
);
const results = previews.map((p, i) => ({
url: urls[i],
preview: p.status === "fulfilled" ? p.value : null,
}));
没有 AND 标签: 回落至 title 和 description。 如果
这些也是空的,显示原始 URL。 显示占位符图像 og.image
为空。
重定向: API 遵循重定向并从最终页面返回元数据。
将输入 URL 与 canonical 检测重定向何时发生。
慢速页面: 在您这边设置一个超时(大多数情况下 5 秒即可)。 的 API 本身有一个内部超时,但您应该强制执行自己的超时,这样慢速目标就不会 阻碍你的用户体验。
批量抓取: 使用 Promise.allSettled 获取预览
并行多个 URL。 失败请求返回 null 而不妨碍其余部分。
要点
-
POST /v1/url-metadata返回 OG 标签、Twitter 卡标签、favicon、canonical 一个 JSON 响应中的 URL、语言和关键字。 - 该响应反映了 Slack、Discord 和 iMessage 用于呈现链接预览的数据。 你 无需编写 HTML 解析器即可获得相同的字段。
- 匿名访问的速度为每分钟 5 个请求,无需 API 密钥。 够发展了 和低流量应用程序。
-
回落至
title和description当 OG 标签丢失时。 API 总是从 HTML 返回这些<head>当它们存在时。 -
对于生产使用,添加超时,通过 URL 缓存结果,并处理
null优雅地回应。 检查 API文档 获取完整参数参考。
FAQ
- 什么是 URL 元数据 API?
- URL 元数据 API 获取网页并从其 HTML 中提取结构化数据:页面标题、元描述、Open Graph 标签(og:title、og:image、og:description)、Twitter Card 标签、favicon URL、规范 URL 和语言。 您发送一个 URL,API 将所有这些内容以 JSON 形式返回。 它使您无需亲自获取页面并解析原始 HTML。
- Slack、Discord 和 iMessage 如何生成链接预览?
- 当用户粘贴 URL 时,这些应用程序会在后台获取页面并读取其 Open Graph 元标记(og:title、og:description、og:image)。 他们根据这些值渲染预览卡。 如果 OG 标签丢失,它们会回退到 HTML 标题和元描述。 botoi /v1/url-metadata 端点返回这些应用程序读取的相同数据,因此您可以在自己的应用程序中构建相同的预览卡。
- 如果页面没有开放图谱标签会发生什么?
- API 仍返回 HTML 标题、元描述、网站图标、规范 URL 和语言。 响应中的 og 字段将为空。 当 og.title 和 og.image 丢失时,您的前端应该回退到标题和描述。
- API 是否遵循重定向?
- 是的。 API 遵循 HTTP 301/302/307/308 重定向并从最终目标 URL 返回元数据。 响应包括已解析的 URL 及其 HTTP 状态代码,因此您可以检测重定向链。
- URL 元数据 API 是免费的吗?
- 匿名访问不需要 API 密钥,每分钟允许 5 个请求,每天允许 100 个请求。 这涵盖了开发和低流量用例。 付费计划起价为 9 美元/月,费率限额更高。
开始使用 botoi 构建
150+ 个 API 端点,涵盖查询、文本处理、图片生成和开发者工具。免费套餐,无需信用卡。