最近使用Hexo重建了我的博客,使用Stellar主题过程中,在友链部分需要填入网站封面,太过繁琐,所以考虑部署一个网页截图API来实现,搜索过后发现已经有博主使用Cloudflare Workers通过Cloudflare的“浏览器呈现”功能来实现了自动获取网页截图(使用Cloudflare制作自动更新的网站预览图),但博主仅自用很多代码均为写死的,遂拿过来改动一番。
部署教程
创建相关服务
API接口运行于Cloudflare Workers,并依赖Workers KV服务来存储截图数据,你需要在Cloudflare新建一个Workers服务(Compute菜单中)和Workers KV(存储和数据库菜单中),名称随意。
绑定KV命名空间
点进创建好的Workers服务,依次点击绑定、添加绑定、KV命名空间,将创建好的KV绑定到Worker服务中,变量名称需保证为SCREENSHOT。

部署代码
点击右上角的编辑代码,将下面的代码粘贴进去,后点击部署即可。
export default {
async fetch(request, env, ctx) {
const cache = caches.default;
const kv = env.SCREENSHOT;
const requestUrl = new URL(request.url);
const targetUrl = requestUrl.searchParams.get("url");
if (!targetUrl) {
return new Response("Missing 'url' parameter", { status: 400 });
}
const referer = request.headers.get("referer") || "";
const origin = request.headers.get("origin") || "";
const allowedDomains = env.ALLOWED_DOMAINS ? env.ALLOWED_DOMAINS.split(",") : [];
const isAllowed = allowedDomains.some(domain => {
const trimmedDomain = domain.trim();
return referer.includes(trimmedDomain) || origin.includes(trimmedDomain);
});
if (allowedDomains.length > 0 && !isAllowed) {
return new Response("Access denied: request not from allowed domain", { status: 403 });
}
const date = new Date().toISOString().split("T")[0];
const cacheKey = targetUrl;
const datedKey = `${targetUrl}?${date}`;
// 工具函数:构建 Response 对象
const buildResponse = (buffer) =>
new Response(buffer, {
headers: {
"content-type": "image/png",
"cache-control": "public, max-age=86400, immutable",
},
});
// 工具函数:尝试从 KV 和 Cache 中加载已有截图
const tryGetCachedResponse = async (key) => {
let res = await cache.match(key);
if (res) return res;
const kvData = await kv.get(key, { type: "arrayBuffer" });
if (kvData) {
res = buildResponse(kvData);
ctx.waitUntil(cache.put(key, res.clone()));
return res;
}
return null;
};
// 1. 优先使用当日缓存
let res = await tryGetCachedResponse(datedKey);
if (res) return res;
// 2. 若缓存不存在,则请求 Cloudflare Screenshot API
try {
const payload = {
url: targetUrl,
viewport: { width: 1200, height: 800 },
gotoOptions: { waitUntil: "networkidle0" },
};
const apiRes = await fetch(
`https://api.cloudflare.com/client/v4/accounts/${env.CF_ACCOUNT_ID}/browser-rendering/screenshot?cacheTTL=86400`,
{
method: "POST",
headers: {
Authorization: `Bearer ${env.CF_API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
}
);
if (!apiRes.ok) throw new Error(`API returned ${apiRes.status}`);
const buffer = await apiRes.arrayBuffer();
res = buildResponse(buffer);
// 后台缓存更新
ctx.waitUntil(Promise.all([
kv.put(cacheKey, buffer),
kv.put(datedKey, buffer, { expirationTtl: 86400 }),
cache.put(cacheKey, res.clone()),
cache.put(datedKey, res.clone()),
]));
return res;
} catch (err) {
console.error("Screenshot generation failed:", err);
// 3. 回退到通用旧缓存
res = await tryGetCachedResponse(cacheKey);
if (res) return res;
return new Response("Screenshot generation failed", { status: 502 });
}
},
};配置环境变量
代码部署完还不能立马使用,因为这个API实际是调用Cloudflare 的“浏览器呈现”功能来实现,所以还需要为服务添加Cloudflare令牌。
进入到账户API令牌管理页面,创新一个新的令牌,只需要拥有浏览器呈现的编辑权限即可,可参考下图配置。

接下来需要将得到的API TOKEN填入到环境变量中,为了避免接口被盗用,最好配置上ALLOWED_DOMAINS,仅允许指定域名调用。
| 名称 | 值(示例) | 说明 |
|---|---|---|
ALLOWED_DOMAINS | careful.fyi,localhost | 配置允许调用API接口的域名,以逗号分隔,留空则允许所有域名调用 |
CF_ACCOUNT_ID | hpk6jwuvna4u75dzwpj2a4582wwebjxe | Cloudflare账户ID |
CF_API_TOKEN | fs3xuwbvy834yf8hxh2jyuwuabec587x | API TOKEN,需拥有浏览器呈现权限 |
服务仅使用referer和origin来进行访问控制,但这两个参数又非常容易伪造,存在被滥用的风险,但个人使用已是足够,感兴趣的可加以改进,提升安全性。
API接口调用
调用方法很简单,通过[API接口地址]?url=[目标地址]格式即可返回网页的最新截图,例如:
https://careful.fyi/api/screenshot/?url=https://careful.fyi
好东西,不过可惜的是每日用量太少了,之前我写截图接口时干脆就直接缓存thum.io的结果
确实少的可怜,但目前仅作为友链页面截图的话,勉强还是够用的。
没限制域名吗刚试了下我的博客网址也是可以的
没~ 这段时间经常在本地调试,图省事儿就直接暂时去掉了限制。
CloudFlare Workers 真是个好东西啊!