← 中文资源

简体中文版本

构建 URL 元数据抓取服务

一个完整的 URL 元数据抓取服务:SSRF 防护、遵守 robots 的抓取、OpenGraph 与 Twitter 卡片解析、缓存,以及恶意输入测试。

阅读英文原文

项目概述

本项目实现 URL 元数据抓取服务:安全地抓取用户提供的 URL 并提取 OpenGraph、Twitter 卡片与回退元数据以生成链接预览,同时阻断对内网的请求。一个抓取任意 URL 的服务,若不严加约束,就是一个请求伪造工具。

架构与关键决策

请求先校验协议、解析主机并拒绝私有与回环地址后再发起抓取,检查基于解析后的地址而非仅主机名,因为主机名可解析到内网、重定向可在首次检查后改目标。抓取以时间与大小上限流式读取,使恶意目标的超大或缓慢响应有界;解析只读文档头部,先取 OpenGraph 再取 Twitter 卡片再回退到标题,使缺少元数据的页面仍可产出可用预览。结果按 URL 哈希带 TTL 缓存。

实施与测试

以本地带 OpenGraph 的页面断言提取;把抓取指向回环地址与已知元数据地址断言均被阻断;断言超大响应被截断或拒绝;断言无元数据页面仍产出标题回退;断言重定向到被阻断地址被拒。

限制与后续

  • 仅解析头部,不执行页面渲染,客户端渲染型预览为空。
  • 除标准词汇外无按站点定制。
  • 缓存按 URL 键且不随内容变化失效,更新在 TTL 后才可见。

代码示例

pnpm init
pnpm add undici cheerio ioredis
pnpm add -D typescript tsx vitest
url-metadata-scraper/
├── src/fetch.ts
├── src/guard.ts
├── src/parse.ts
├── src/cache.ts
├── tests/scrape.test.ts
└── package.json
if (isPrivate(ip)) throw new Error('blocked address');
for (let hop = 0; hop < 5; hop++) { url = check(await fetchStep(url)); }
const title = $('meta[property="og:title"]').attr('content') ?? $('title').text();
const image = new URL(raw, baseUrl).href;
await redis.set(key, JSON.stringify(meta), 'EX', 3600);
pnpm vitest run