Files
pages/2025/07/24/screenshot.html
2025-12-31 16:00:29 +00:00

401 lines
32 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- Begin Jekyll SEO tag v2.8.0 -->
<title>使用Cloudflare制作自动更新的网站预览图 | Mayx的博客</title>
<meta name="generator" content="Jekyll v3.9.5" />
<meta property="og:title" content="使用Cloudflare制作自动更新的网站预览图" />
<meta name="author" content="mayx" />
<meta property="og:locale" content="zh_CN" />
<meta name="description" content="Cloudflare的功能真是越来越多了而且还免费" />
<meta property="og:description" content="Cloudflare的功能真是越来越多了而且还免费" />
<meta property="og:site_name" content="Mayx的博客" />
<meta property="og:type" content="article" />
<meta property="article:published_time" content="2025-07-24T00:00:00+08:00" />
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="使用Cloudflare制作自动更新的网站预览图" />
<meta name="google-site-verification" content="huTYdEesm8NaFymixMNqflyCp6Jfvd615j5Wq1i2PHc" />
<meta name="msvalidate.01" content="0ADFCE64B3557DC4DC5F2DC224C5FDDD" />
<meta name="yandex-verification" content="fc0e535abed800be" />
<script type="application/ld+json">
{"@context":"https://schema.org","@type":"BlogPosting","author":{"@type":"Person","name":"mayx"},"dateModified":"2025-07-24T00:00:00+08:00","datePublished":"2025-07-24T00:00:00+08:00","description":"Cloudflare的功能真是越来越多了而且还免费","headline":"使用Cloudflare制作自动更新的网站预览图","mainEntityOfPage":{"@type":"WebPage","@id":"/2025/07/24/screenshot.html"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://avatars0.githubusercontent.com/u/17966333"},"name":"mayx"},"url":"/2025/07/24/screenshot.html"}</script>
<!-- End Jekyll SEO tag -->
<link rel="canonical" href="https://mabbs.github.io/2025/07/24/screenshot.html" />
<link type="application/atom+xml" rel="alternate" href="/atom.xml" title="Mayx的博客" />
<link rel="alternate" type="application/rss+xml" title="Mayx的博客(RSS)" href="/rss.xml" />
<link rel="alternate" type="application/json" title="Mayx的博客(JSON Feed)" href="/feed.json" />
<link rel="stylesheet" href="/assets/css/style.css?v=1767196818" />
<!--[if !IE]> -->
<link rel="stylesheet" href="/Live2dHistoire/live2d/css/live2d.css" />
<!-- <![endif]-->
<link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="Mayx的博客" />
<link rel="webmention" href="https://webmention.io/mabbs.github.io/webmention" />
<link rel="pingback" href="https://webmention.io/mabbs.github.io/xmlrpc" />
<link rel="preconnect" href="https://summary.mayx.eu.org" crossorigin="anonymous" />
<link rel="prefetch" href="https://www.blogsclub.org/badge/mabbs.github.io" as="image" />
<link rel="blogroll" type="text/xml" href="/blogroll.opml" />
<link rel="me" href="https://github.com/Mabbs" />
<script src="/assets/js/jquery.min.js"></script>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery-ajaxtransport-xdomainrequest/1.0.3/jquery.xdomainrequest.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<script>
var lastUpdated = new Date("Thu, 01 Jan 2026 00:00:18 +0800");
var BlogAPI = "https://summary.mayx.eu.org";
</script>
<script src="/assets/js/main.js"></script>
<!--[if !IE]> -->
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async="async" src="https://www.googletagmanager.com/gtag/js?id=UA-137710294-1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-137710294-1');
</script>
<script src="/assets/js/instant.page.js" type="module"></script>
<!-- <![endif]-->
</head>
<body>
<!--[if !IE]> --><noscript><marquee style="top: -15px; position: relative;"><small>发现当前浏览器没有启用JavaScript这不影响你的浏览但可能会有一些功能无法使用……</small></marquee></noscript><!-- <![endif]-->
<!--[if IE]><marquee style="top: -15px; position: relative;"><small>发现当前浏览器为Internet Explorer这不影响你的浏览但可能会有一些功能无法使用……</small></marquee><![endif]-->
<div class="wrapper">
<header class="h-card">
<h1><a class="u-url u-uid p-name" rel="me" href="/">Mayx的博客</a></h1>
<img src="https://avatars0.githubusercontent.com/u/17966333" fetchpriority="high" class="u-photo" alt="Logo" style="width: 90%; max-width: 300px; max-height: 300px;" />
<p class="p-note">Mayx's Home Page</p>
<form action="/search.html">
<input type="text" name="keyword" id="search-input-all" placeholder="Search blog posts.." />&#160;<input type="submit" value="搜索" />
</form>
<br />
<p class="view"><a class="u-url" href="/Mabbs/">About me</a></p>
<ul class="downloads">
<li style="width: 270px; border-right: none;"><a href="/MayxBlog.tgz">Download <strong>TGZ File</strong></a></li>
</ul>
</header>
<section class="h-entry">
<small><time class="date dt-published" datetime="2025-07-24T00:00:00+08:00">24 July 2025</time> - 字数统计3245 - 阅读大约需要11分钟 - Hits: <span id="/2025/07/24/screenshot.html" class="visitors">Loading...</span></small>
<h1 class="p-name">使用Cloudflare制作自动更新的网站预览图</h1>
<p class="view">by <a class="p-author h-card" href="//github.com/Mabbs">mayx</a></p>
<div id="outdate" style="display:none;">
<hr /><p>
这是一篇创建于 <span id="outime"></span> 天前的文章,其中的信息可能已经有所发展或是发生改变。
</p>
</div>
<script>
daysold = Math.floor((new Date().getTime() - new Date("Thu, 24 Jul 2025 00:00:00 +0800").getTime()) / (24 * 60 * 60 * 1000));
if (daysold > 90) {
document.getElementById("outdate").style.display = "block";
document.getElementById("outime").innerHTML = daysold;
}
</script>
<hr />
<b>AI摘要</b>
<p id="ai-output">这篇文章介绍了如何利用Cloudflare的“浏览器呈现”功能创建一个自动更新的网站预览图服务。作者发现这个新功能可以用来展示网站在不同设备上的显示效果通过在Cloudflare Workers中使用iframe和CSS缩放技术以及调用Cloudflare的接口抓取浏览器渲染的截图。虽然免费用户每天只有10分钟的使用时间限制了实时更新但作者通过缓存实现了每天自动更新一次的预览图并分享了具体的实现代码和使用方法。作者赞赏Cloudflare提供的这项强大且免费的服务。</p>
<hr />
<ul><li><a href="#起因">起因</a></li><li><a href="#制作自适应的网站预览">制作自适应的网站预览</a></li><li><a href="#使用cloudflare浏览器呈现进行截图">使用Cloudflare浏览器呈现进行截图</a></li><li><a href="#感想">感想</a></li></ul>
<hr />
<main class="post-content e-content" role="main"><p>Cloudflare的功能真是越来越多了而且还免费<!--more--></p>
<h1 id="起因">
<a href="#起因"><svg class='octicon' viewBox='0 0 16 16' version='1.1' width='16' height='32' aria-hidden='true'><path fill-rule='evenodd' d='M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z'></path></svg></a> 起因
</h1>
<p>前段时间我在登录Cloudflare的时候发现Workers上多了一个“浏览器呈现”的功能可能已经出来一段时间了不过之前一直没关注看介绍这个功能可以让Worker操作运行在Cloudflare服务器上的浏览器。这功能挺有意思而且免费用户也能用不如想个办法好好利用一下。 </p><p>
一般来说这个功能可以干什么呢既然是在AI盛行的时候出现……估计是为了搞Agent之类的吧不过看<a href="https://developers.cloudflare.com/browser-rendering/platform/limits/">文档</a>对免费用户来说一天也只有10分钟的使用时间估计也没什么应用价值……那除了这个之外还能做些什么我发现有好多博客主题喜欢给自己的README里添加一个能查看主题在多种设备上显示效果的预览图以展示主题的自适应能力。那么既然现在能在Cloudflare上操作浏览器那么我也可以做一个类似的而且这个预览图还可以自动更新。</p>
<h1 id="制作自适应的网站预览">
<a href="#制作自适应的网站预览"><svg class='octicon' viewBox='0 0 16 16' version='1.1' width='16' height='32' aria-hidden='true'><path fill-rule='evenodd' d='M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z'></path></svg></a> 制作自适应的网站预览
</h1>
<p>既然打算做预览图那么我应该用什么方案按照不同尺寸的视口截几张图再拼起来吗这显然就太复杂了况且在Cloudflare Workers中处理图片也相当困难。这时我想起来曾经见到过一个工具只要输入网址就可以在一个页面中同时展示网站在四种不同设备手机、平板、笔记本电脑、台式机上的显示效果叫做“多合一网页缩略图”实现原理是使用iframe和CSS缩放模拟多种设备视口。搜了一下发现这套代码被不少网站使用所以就随便找了其中一个工具站把代码和素材扒了下来稍微改了一下然后放到<a href="https://github.com/Mabbs/responsive">GitHub</a>方便等一会用Cloudflare访问这个部署在<a href="https://mabbs.github.io/responsive/">GitHub Pages</a>上的页面来进行截图。</p>
<h1 id="使用cloudflare浏览器呈现进行截图">
<a href="#使用cloudflare浏览器呈现进行截图"><svg class='octicon' viewBox='0 0 16 16' version='1.1' width='16' height='32' aria-hidden='true'><path fill-rule='evenodd' d='M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z'></path></svg></a> 使用Cloudflare浏览器呈现进行截图
</h1>
<p>接下来截图就简单了不过Cloudflare有两种截图的办法<a href="https://developers.cloudflare.com/browser-rendering/workers-bindings/">用Workers</a>的话可以直接用Puppeteer之类的库连接浏览器但用这个库需要安装要本地搭环境……我毕竟不是专门搞JS开发的一点也不想在本地安装Node.js环境所以就不想用这种方式。另外一种是通过<a href="https://developers.cloudflare.com/browser-rendering/rest-api/">调用Cloudflare的接口</a>这种非常简单只需要填几个参数请求就行唯一的问题就是要填一个Token……我一直觉得Worker调用Cloudflare自己的服务不应该需要Token之类的东西毕竟内部就能验证了没必要自己搞但是我看了半天文档貌似无论如何只要想调接口就必须搞个Token……那没办法就搞吧其实也很简单只需要在“账户API令牌”里添加一个有浏览器呈现编辑权限的令牌就行。 </p><p>
至于展示……这个接口调用比较耗时而且一天只能调用10分钟截图的话估计也就够30次左右还有每分钟3次的限制😓所以实时更新肯定是不行了图片肯定得缓存一天更新一次感觉应该就够了。另外次数这么少的话写成接口给大伙用貌似也没啥意义所以我就把地址写死了于是以下就是最终实现的代码</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="k">default</span> <span class="p">{</span>
<span class="k">async</span> <span class="nx">fetch</span><span class="p">(</span><span class="nx">request</span><span class="p">,</span> <span class="nx">env</span><span class="p">,</span> <span class="nx">ctx</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">cache</span> <span class="o">=</span> <span class="nx">caches</span><span class="p">.</span><span class="k">default</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">kv</span> <span class="o">=</span> <span class="nx">env</span><span class="p">.</span><span class="nx">SCREENSHOT</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">https://mabbs.github.io/responsive/</span><span class="dl">"</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">date</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">().</span><span class="nx">toISOString</span><span class="p">().</span><span class="nx">split</span><span class="p">(</span><span class="dl">"</span><span class="s2">T</span><span class="dl">"</span><span class="p">)[</span><span class="mi">0</span><span class="p">];</span>
<span class="kd">const</span> <span class="nx">cacheKey</span> <span class="o">=</span> <span class="nx">url</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">datedKey</span> <span class="o">=</span> <span class="s2">`</span><span class="p">${</span><span class="nx">url</span><span class="p">}</span><span class="s2">?</span><span class="p">${</span><span class="nx">date</span><span class="p">}</span><span class="s2">`</span><span class="p">;</span>
<span class="c1">// 工具函数:构建 Response 对象</span>
<span class="kd">const</span> <span class="nx">buildResponse</span> <span class="o">=</span> <span class="p">(</span><span class="nx">buffer</span><span class="p">)</span> <span class="o">=&gt;</span>
<span class="k">new</span> <span class="nx">Response</span><span class="p">(</span><span class="nx">buffer</span><span class="p">,</span> <span class="p">{</span>
<span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">content-type</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">image/png</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">cache-control</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">public, max-age=86400, immutable</span><span class="dl">"</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">});</span>
<span class="c1">// 工具函数:尝试从 KV 和 Cache 中加载已有截图</span>
<span class="kd">const</span> <span class="nx">tryGetCachedResponse</span> <span class="o">=</span> <span class="k">async</span> <span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">res</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">cache</span><span class="p">.</span><span class="nx">match</span><span class="p">(</span><span class="nx">key</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">res</span><span class="p">)</span> <span class="k">return</span> <span class="nx">res</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">kvData</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">kv</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">key</span><span class="p">,</span> <span class="p">{</span> <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">arrayBuffer</span><span class="dl">"</span> <span class="p">});</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">kvData</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">res</span> <span class="o">=</span> <span class="nx">buildResponse</span><span class="p">(</span><span class="nx">kvData</span><span class="p">);</span>
<span class="nx">ctx</span><span class="p">.</span><span class="nx">waitUntil</span><span class="p">(</span><span class="nx">cache</span><span class="p">.</span><span class="nx">put</span><span class="p">(</span><span class="nx">key</span><span class="p">,</span> <span class="nx">res</span><span class="p">.</span><span class="nx">clone</span><span class="p">()));</span>
<span class="k">return</span> <span class="nx">res</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
<span class="p">};</span>
<span class="c1">// 1. 优先使用当日缓存</span>
<span class="kd">let</span> <span class="nx">res</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">tryGetCachedResponse</span><span class="p">(</span><span class="nx">datedKey</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">res</span><span class="p">)</span> <span class="k">return</span> <span class="nx">res</span><span class="p">;</span>
<span class="c1">// 2. 若缓存不存在,则请求 Cloudflare Screenshot API</span>
<span class="k">try</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">payload</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">url</span><span class="p">:</span> <span class="nx">url</span><span class="p">,</span>
<span class="na">viewport</span><span class="p">:</span> <span class="p">{</span> <span class="na">width</span><span class="p">:</span> <span class="mi">1200</span><span class="p">,</span> <span class="na">height</span><span class="p">:</span> <span class="mi">800</span> <span class="p">},</span>
<span class="na">gotoOptions</span><span class="p">:</span> <span class="p">{</span> <span class="na">waitUntil</span><span class="p">:</span> <span class="dl">"</span><span class="s2">networkidle0</span><span class="dl">"</span> <span class="p">},</span>
<span class="p">};</span>
<span class="kd">const</span> <span class="nx">apiRes</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span>
<span class="s2">`https://api.cloudflare.com/client/v4/accounts/</span><span class="p">${</span><span class="nx">env</span><span class="p">.</span><span class="nx">CF_ACCOUNT_ID</span><span class="p">}</span><span class="s2">/browser-rendering/screenshot?cacheTTL=86400`</span><span class="p">,</span>
<span class="p">{</span>
<span class="na">method</span><span class="p">:</span> <span class="dl">"</span><span class="s2">POST</span><span class="dl">"</span><span class="p">,</span>
<span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
<span class="na">Authorization</span><span class="p">:</span> <span class="s2">`Bearer </span><span class="p">${</span><span class="nx">env</span><span class="p">.</span><span class="nx">CF_API_TOKEN</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">Content-Type</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">application/json</span><span class="dl">"</span><span class="p">,</span>
<span class="p">},</span>
<span class="na">body</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">payload</span><span class="p">),</span>
<span class="p">}</span>
<span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">apiRes</span><span class="p">.</span><span class="nx">ok</span><span class="p">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="s2">`API returned </span><span class="p">${</span><span class="nx">apiRes</span><span class="p">.</span><span class="nx">status</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">buffer</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">apiRes</span><span class="p">.</span><span class="nx">arrayBuffer</span><span class="p">();</span>
<span class="nx">res</span> <span class="o">=</span> <span class="nx">buildResponse</span><span class="p">(</span><span class="nx">buffer</span><span class="p">);</span>
<span class="c1">// 后台缓存更新</span>
<span class="nx">ctx</span><span class="p">.</span><span class="nx">waitUntil</span><span class="p">(</span><span class="nb">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">([</span>
<span class="nx">kv</span><span class="p">.</span><span class="nx">put</span><span class="p">(</span><span class="nx">cacheKey</span><span class="p">,</span> <span class="nx">buffer</span><span class="p">),</span>
<span class="nx">kv</span><span class="p">.</span><span class="nx">put</span><span class="p">(</span><span class="nx">datedKey</span><span class="p">,</span> <span class="nx">buffer</span><span class="p">,</span> <span class="p">{</span> <span class="na">expirationTtl</span><span class="p">:</span> <span class="mi">86400</span> <span class="p">}),</span>
<span class="nx">cache</span><span class="p">.</span><span class="nx">put</span><span class="p">(</span><span class="nx">cacheKey</span><span class="p">,</span> <span class="nx">res</span><span class="p">.</span><span class="nx">clone</span><span class="p">()),</span>
<span class="nx">cache</span><span class="p">.</span><span class="nx">put</span><span class="p">(</span><span class="nx">datedKey</span><span class="p">,</span> <span class="nx">res</span><span class="p">.</span><span class="nx">clone</span><span class="p">()),</span>
<span class="p">]));</span>
<span class="k">return</span> <span class="nx">res</span><span class="p">;</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">"</span><span class="s2">Screenshot generation failed:</span><span class="dl">"</span><span class="p">,</span> <span class="nx">err</span><span class="p">);</span>
<span class="c1">// 3. 回退到通用旧缓存</span>
<span class="nx">res</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">tryGetCachedResponse</span><span class="p">(</span><span class="nx">cacheKey</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">res</span><span class="p">)</span> <span class="k">return</span> <span class="nx">res</span><span class="p">;</span>
<span class="k">return</span> <span class="k">new</span> <span class="nx">Response</span><span class="p">(</span><span class="dl">"</span><span class="s2">Screenshot generation failed</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="na">status</span><span class="p">:</span> <span class="mi">502</span> <span class="p">});</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="p">};</span>
</code></pre></div></div>
<p>使用方法很简单创建一个Worker把以上代码粘进去然后把从“账户API令牌”中生成的令牌填到Worker的密钥中名称为<code class="language-plaintext highlighter-rouge">CF_API_TOKEN</code>,另外再加一个名称为<code class="language-plaintext highlighter-rouge">CF_ACCOUNT_ID</code>的密钥内容是账户ID就是打开仪表板时URL中的那串16进制数字除此之外还需要创建一个KV数据库绑定到这个Worker上绑定的名称是<code class="language-plaintext highlighter-rouge">SCREENSHOT</code>。如果想给自己的网站生成可以Fork我的<a href="https://github.com/Mabbs/responsive">仓库</a>然后把里面首页文件中的网址替换成你的网站然后再把Worker中的url替换成Fork后仓库的GitHub Pages地址就可以了。 </p><p>
最终的效果如下: </p><p>
<img src="https://screenshot.mayx.eu.org" alt="ScreenShot" /></p>
<h1 id="感想">
<a href="#感想"><svg class='octicon' viewBox='0 0 16 16' version='1.1' width='16' height='32' aria-hidden='true'><path fill-rule='evenodd' d='M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z'></path></svg></a> 感想
</h1>
<p>Cloudflare实在是太强了虽然这个浏览器呈现免费用量并不多但是有这么一个功能已经吊打很多Serverless服务了毕竟浏览器对服务器资源的占用也不小小内存的服务器甚至都不能运行如果要自己搭的话成本可能也不小而现在Cloudflare能免费提供应该说不愧是赛博活佛吗🤣。</p></main>
<small style="display: block">tags: <a rel="category tag" class="p-category" href="/search.html?keyword=Cloudflare"><em>Cloudflare</em></a> - <a rel="category tag" class="p-category" href="/search.html?keyword=Workers"><em>Workers</em></a> - <a rel="category tag" class="p-category" href="/search.html?keyword=%E7%BD%91%E7%AB%99%E6%88%AA%E5%9B%BE"><em>网站截图</em></a> - <a rel="category tag" class="p-category" href="/search.html?keyword=%E8%87%AA%E5%8A%A8%E5%8C%96"><em>自动化</em></a> <span style="float: right;"><a href="https://gitlab.com/mayx/mayx.gitlab.io/tree/master/_posts/2025-07-24-screenshot.md">查看原始文件</a></span></small>
<h4 style="border-bottom: 1px solid #e5e5e5;margin: 2em 0 5px;">推荐文章</h4>
<p id="suggest-container">Loading...</p>
<script>
var suggest = $("#suggest-container");
$.get(BlogAPI + "/suggest?id=/2025/07/24/screenshot.html&update=" + lastUpdated.valueOf(), function (data) {
if (data.length) {
getSearchJSON(function (search) {
suggest.empty();
var searchMap = {};
for (var i = 0; i < search.length; i++) {
searchMap[search[i].url] = search[i];
}
var tooltip = $('<div class="content-tooltip"></div>').appendTo('body').hide();
for (var j = 0; j < data.length; j++) {
var item = searchMap[data[j].id];
if (item) {
var link = $('<a href="' + item.url + '">' + item.title + '</a>');
var contentPreview = item.content.substring(0, 100);
if (item.content.length > 100) {
contentPreview += "……";
}
link.hover(
function(e) {
tooltip.text($(this).data('content'))
.css({
top: e.pageY + 10,
left: e.pageX + 10
})
.show();
},
function() {
tooltip.hide();
}
).mousemove(function(e) {
tooltip.css({
top: e.pageY + 10,
left: e.pageX + 10
});
}).data('content', contentPreview);
suggest.append(link);
suggest.append(' - ' + item.date + '<br />');
}
}
});
} else {
suggest.html("暂无推荐文章……");
}
});
</script>
<br />
<div class="pagination">
<span class="prev">
<a href="/2025/07/13/hacked.html">
上一篇:一次服务器被入侵的经历
</a>
</span>
<br />
<span class="next">
<a href="/2025/08/01/sw-proxy.html">
下一篇用Service Worker实现一个反向代理
</a>
</span>
</div>
<!--[if !IE]> -->
<link rel="stylesheet" href="/assets/css/gitalk.css">
<script src="/assets/js/gitalk.min.js"></script>
<div id="gitalk-container"></div>
<script>
var gitalk = new Gitalk({
clientID: '36557aec4c3cb04f7ac6',
clientSecret: 'ac32993299751cb5a9ba81cf2b171cca65879cdb',
repo: 'mabbs.github.io',
owner: 'Mabbs',
admin: ['Mabbs'],
id: '/2025/07/24/screenshot', // Ensure uniqueness and length less than 50
distractionFreeMode: false, // Facebook-like distraction free mode
proxy: "https://cors-anywhere.mayx.eu.org/?https://github.com/login/oauth/access_token"
})
gitalk.render('gitalk-container')
</script>
<!-- <![endif]-->
</section>
<!--[if !IE]> -->
<div id="landlord" style="left:5px;bottom:0px;">
<div class="message" style="opacity:0"></div>
<canvas id="live2d" width="500" height="560" class="live2d"></canvas>
<div class="live_talk_input_body">
<form id="live_talk_input_form">
<div class="live_talk_input_name_body" >
<input type="checkbox" id="load_this" />
<input type="hidden" id="post_id" value="/2025/07/24/screenshot.html" />
<label for="load_this">
<span style="font-size: 11px; color: #fff;">&#160;想问这篇文章</span>
</label>
</div>
<div class="live_talk_input_text_body">
<input name="talk" type="text" class="live_talk_talk white_input" id="AIuserText" autocomplete="off" placeholder="要和我聊什么呀?" />
<button type="submit" class="live_talk_send_btn" id="talk_send">发送</button>
</div>
</form>
</div>
<input name="live_talk" id="live_talk" value="1" type="hidden" />
<div class="live_ico_box" style="display:none;">
<div class="live_ico_item type_info" id="showInfoBtn"></div>
<div class="live_ico_item type_talk" id="showTalkBtn"></div>
<div class="live_ico_item type_music" id="musicButton"></div>
<div class="live_ico_item type_youdu" id="youduButton"></div>
<div class="live_ico_item type_quit" id="hideButton"></div>
<input name="live_statu_val" id="live_statu_val" value="0" type="hidden" />
<audio src="" style="display:none;" id="live2d_bgm" data-bgm="0" preload="none"></audio>
<input id="duType" value="douqilai" type="hidden" />
</div>
</div>
<div id="open_live2d">召唤伊斯特瓦尔</div>
<!-- <![endif]-->
<footer>
<p>
<small>Made with ❤ by Mayx<br />Last updated at 2026-01-01 00:00:18<br /> 总字数614622 - 文章数178 - <a href="/atom.xml" >Atom</a> - <a href="/README.html" >About</a></small>
</p>
</footer>
</div>
<script src="/assets/js/scale.fix.js"></script>
<!--[if !IE]> -->
<script src="/assets/js/main_new.js"></script>
<script src="/Live2dHistoire/live2d/js/live2d.js"></script>
<script src="/Live2dHistoire/live2d/js/message.js"></script>
<!-- <![endif]-->
</body>
</html>