$ cat ./posts.db | grep -v '^使用CloudFlare Workers搭建网页截图API' | sort

使用CloudFlare Workers搭建网页截图API

date: 2026-03-02 · cat: 技术 · read: 10 min

最近使用Hexo重建了我的博客,使用Stellar主题过程中,在友链部分需要填入网站封面,太过繁琐,所以考虑部署一个网页截图API来实现,搜索过后发现已经有博主使用Cloudflare Workers通过Cloudflare的“浏览器呈现”功能来实现了自动获取网页截图(使用Cloudflare制作自动更新的网站预览图),但博主仅自用很多代码均为写死的,遂拿过来改动一番。

部署教程

创建相关服务

API接口运行于Cloudflare Workers,并依赖Workers KV服务来存储截图数据,你需要在Cloudflare新建一个Workers服务(Compute菜单中)和Workers KV(存储和数据库菜单中),名称随意。

绑定KV命名空间

点进创建好的Workers服务,依次点击绑定、添加绑定、KV命名空间,将创建好的KV绑定到Worker服务中,变量名称需保证为SCREENSHOT

uploads_2026_03_83264f908e3a4c3e8904b6ad5dc34a1b.png

部署代码

点击右上角的编辑代码,将下面的代码粘贴进去,后点击部署即可。

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令牌管理页面,创新一个新的令牌,只需要拥有浏览器呈现的编辑权限即可,可参考下图配置。

uploads_2026_03_db0508810447816fe14e7d6e23733853.png

接下来需要将得到的API TOKEN填入到环境变量中,为了避免接口被盗用,最好配置上ALLOWED_DOMAINS,仅允许指定域名调用。

名称值(示例)说明
ALLOWED_DOMAINScareful.fyi,localhost配置允许调用API接口的域名,以逗号分隔,留空则允许所有域名调用
CF_ACCOUNT_IDhpk6jwuvna4u75dzwpj2a4582wwebjxeCloudflare账户ID
CF_API_TOKENfs3xuwbvy834yf8hxh2jyuwuabec587xAPI TOKEN,需拥有浏览器呈现权限

服务仅使用referer和origin来进行访问控制,但这两个参数又非常容易伪造,存在被滥用的风险,但个人使用已是足够,感兴趣的可加以改进,提升安全性。

API接口调用

调用方法很简单,通过[API接口地址]?url=[目标地址]格式即可返回网页的最新截图,例如:

https://careful.fyi/api/screenshot/?url=https://careful.fyi 
$ grep ./comments.db 5 entries

// echo "your thoughts here" >> comments.db

发布评论

// 邮箱用于 Gravatar 头像,不会公开展示。