使用Cloudflare免费加速豆瓣图书封面
大概是3月份吧,我突然想回顾一下23年一年读过的书,以及记录24年我正在读书。我个人也不太会写前端,所以就用ChatGPT给我生成了一个简单、朴素的书架页面 https://simonmy.com/books.html
制作这个页面的过程很快,但是图书封面的问题让我很头大。如果是直接配置豆瓣的地址,大概率是被拦截的。我自己从豆瓣抓下来,然后传图床再加到页面也很麻烦,所就有了这个简单的小工具。
原理
- 利用Cache快速检查之前是否做过图片转存加速
- 如果命中了Cache,直接读取R2中的图片,返回用户
- 如果没有命中Cache
3.1 利用豆瓣API把图片抓取下来,上传到R2
3.2 更新Cache,以便于后续命中
3.3 读取R2中的图片,返回用户
准备工作
- 一个Cloudflare账号,没有的自己注册
- 一个托管在 Cloudflare的域名 (建议有,不强制)
关键步骤
创建存储
这一步没什么要注意的,只要名字符合规范就好。创建R2的时候,可以选择亚太区,速度理论上会快一些
创建R2
创建KV
创建Worker
这一步是最重要的一步,先到自己账户的 Worker&Pages
页面并创建一个Worke。创建的时候注意不是Pages,Worker的名字自己看着来,使用默认的Hello World模板部署即可。
创建成功之后不要急着贴代码,按照以下步骤来可以少走弯路~
第一步:绑定R2和KV
回到自己账户的 Worker&Pages
页面,进入刚才自己创建的Worker,选择 Setting - Variables
。
- 找到
KV Namespace Bindings
添加变量名为DOUBAN_KV
, 对应的值选择上一步你创建的KV的名字 - 找到
R2 Bucket Bindings
添加变量名为DOUBAN_R2
, 对应的值选择上一步你创建的R2的名字
第二步:贴代码
回到自己账户的 Worker&Pages
页面,进入刚才自己创建的Worker, 进入右上角的 Edit Code(编辑代码)
将下面的代码粘贴到Worker内的worker.js,部署即可。
const DOUBAN_API_HOST = 'frodo.douban.com';
const DOUBAN_API_KEY = '0ac44ae016490db2204ce0a042db2916';
const requestOptions = {
headers: {
// host: DOUBAN_API_HOST,
authorization: '',
'user-agent': 'User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 15_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.16(0x18001023) NetType/WIFI Language/zh_CN',
referer: 'https://servicewechat.com/wx2f9b06c1de1ccfca/84/page-frame.html',
'cache-control': 'no-store'
},
cf: {
cacheTtl: 0,
scrapeShield: false,
cacheEverything: false
}
};
async function fetchSubject({type, id }) {
const url = `https://${DOUBAN_API_HOST}/api/v2/${type}/${id}`;
const params = new URLSearchParams({
apiKey: DOUBAN_API_KEY
});
return fetch(url + '?' + params.toString(), requestOptions);
}
async function fetchCover({ type, id, cache, bucket }) {
const fileCacheKey = 'file_' + type + '_' + id;
const fileBucketKey = 'cover/' + type + '/' + id + '.jpg'
const fileCacheValue = await cache.get(fileCacheKey);
if(fileCacheValue) {
const object = await bucket.get(fileBucketKey)
if (object === null) {
return new Response('Object Not Found', { status: 404 });
}
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set('etag', object.httpEtag);
return new Response(object.body, {
headers,
});
} else {
const urlCacheKey = 'url_' + type + '_' + id;
const urlCacheValue = await cache.get(urlCacheKey);
let coverUrl = '';
if(urlCacheValue) {
coverUrl = urlCacheValue
} else {
const subject = await (await fetchSubject({ type, id })).json();
coverUrl = type === 'celebrity' ? subject.cover_img.url : subject.cover_url;
await cache.put(urlCacheKey, coverUrl)
}
const response = await fetch(coverUrl, requestOptions)
const bodySize = parseInt(response.headers.get('Content-Length'), 10)
let originalResponse = response.clone();
if(response.ok && bodySize > 0) {
await bucket.put(fileBucketKey, response.body);
await cache.put(fileCacheKey, fileBucketKey);
console.log(fileCacheKey, fileBucketKey);
}
return new Response(originalResponse.body, {
headers: {
'cache-control': '2592000'
}
});
}
}
// Export a default object containing event handlers
export default {
async fetch(request, env, ctx) {
const reg = /^\/(movie|book|music|game|celebrity)\/(\d+)\.jpg$/i;
const url = new URL(request.url);
const path = url.pathname
const match = path.match(reg)
if (!match) {
return new Response('')
}
const[_, type, id] = match
const cache = env.DOUBAN_KV
const bucket = env.DOUBAN_R2
return await fetchCover({ type, id, cache, bucket })
},
};
绑定域名(可选)
回到自己账户的 Worker&Pages
页面,进入刚才自己创建的Worker,选择 Setting - Triggers
, 自行添加即可。
用法及限制
用法
假设我们想要的是《置身事内》这本书的封面,Worker域名以 demo.account.workers.dev
为例。
- 先到豆瓣找到这本书的地址
https://book.douban.com/subject/35546622/
- 获取到书的ID为
35546622
- 那么图书封面的地址就是
https://demo.account.workers.dev/book/35546622.jpg
限制
1.由于使用Cloudflare的网络,图片的加载速度在某些地区可能得不到保证
2.Cloudflare Function免费版每日限制100,000个请求(即加载图片的总次数不能超过100,000次)如超过可能需要选择购买Cloudflare Function的付费套餐
写在最后
避免有人抬杠,这里也专门标注一下。代码仅仅为了解决豆瓣图片这个小问题,确实存在一些缺陷和不足,一是js我不太专业。二是我知道并发时可能多次下载图片导致ABA问题,没必要解决。