mirror of
https://codeberg.org/mayx/pages
synced 2026-01-02 04:23:40 +08:00
update
This commit is contained in:
400
2025/07/24/screenshot.html
Normal file
400
2025/07/24/screenshot.html
Normal file
@@ -0,0 +1,400 @@
|
||||
<!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.." /> <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">=></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">=></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;"> 想问这篇文章</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>
|
||||
Reference in New Issue
Block a user