前言
相对 python 的环境配置过程,deno 的一体化结构,让脚本开发运行更简单。
昨天看到了 python每天自动背景图不香吗? 这篇文章,使用 python 脚本获取 bing 的背景图片。一看这个项目,正好适合写一个 deno 的体验 demo。于是就有了这篇文章。
环境安装
偏好命令行的可以看 官方网站 或者 通过单行命令将 Deno 安装到系统中(国内加速)
喜欢直接用可执行文件的,到 这里。记得要配置环境变量。
代码简述
- 通过解析 必应壁纸 网站的 DOM 获取 bing 背景图的信息。(主要是因为该网站页面是服务端渲染的,没看到请求背景图列表的接口)
import {
DOMParser,
Element,
} from "https://deno.land/x/deno_dom/deno-dom-wasm.ts";
async function getList(curPage = 1): Promise<Array<WallpaperItem>> {
let wallpaperItems: Array<WallpaperItem> = [];
const res = await fetch(`${ORIGIN_URL}?p=${curPage}`);
const { status, statusText } = res;
if (status === STATUS_CODE.OK) {
const rawData = await res.text();
const doc = new DOMParser().parseFromString(rawData, "text/html");
const body = doc?.querySelector("body");
const items = body?.querySelectorAll(".item");
if (items) {
wallpaperItems = Array.from(items).map((item) => {
// <Element> for bug: https://github.com/b-fuze/deno-dom/issues/4
const descriptionEl = (<Element> item).querySelector(
".description h3",
);
const dateEl = (<Element> item).querySelector(
".description .calendar .t",
);
const mark = (<Element> item).querySelector(".mark");
const path = mark?.attributes.href;
const imgName = path?.substring(
path.lastIndexOf("/") + 1,
path.lastIndexOf("?"),
);
const wallpaperItem: WallpaperItem = {
name: imgName,
description: descriptionEl?.textContent,
date: dateEl?.textContent,
url: `https://cn.bing.com/th?id=OHR.${imgName}_UHD.jpg`,
};
return wallpaperItem;
});
}
} else {
console.log(`${status}: ${statusText}`);
}
return wallpaperItems;
}
解析后的数据中包含图片名称、描述、时间、图片地址。
export interface WallpaperItem {
url: string;
name?: string;
description?: string;
date?: string;
}
图片地址使用 bing 壁纸的源(这里有高清图片,比 必应壁纸 网站的图片分辨率高)。
- 保存图片到本地
export async function saveFile(
node: WallpaperItem,
saveDir = DEFAULT_FILE_SAVE_DIR,
) {
const { name, date, url } = node;
const res = await fetch(url);
const { status, statusText } = res;
if (status === STATUS_CODE.OK) {
const img = await res.arrayBuffer();
const ext = url.substring(url.lastIndexOf("."));
Deno.mkdirSync(saveDir, { recursive: true });
Deno.writeFileSync(
`${saveDir}/[${date}]${name}${ext}`,
new Uint8Array(img),
);
} else {
console.log(`${status}: ${statusText}`);
}
}
- 批量请求图片信息
以请求全站所有图片信息列表为例,首先需要知道一共有多少页。这部分同样是解析页面 DOM 得到。
export async function getPageCount(): Promise<number> {
const res = await fetch(`${ORIGIN_URL}`);
const { status, statusText } = res;
if (status === STATUS_CODE.OK) {
const rawData = await res.text();
const doc = new DOMParser().parseFromString(rawData, "text/html");
const body = doc?.querySelector("body");
const pager = body?.querySelector(".page span");
const pagerTextContent = pager?.textContent || "";
const [, pageCount = "0"] = pagerTextContent.split("/");
return parseInt(pageCount);
} else {
console.log(`${status}: ${statusText}`);
return 0;
}
}
之后批量请求即可。请求时注意伪装 header、调整请求频率、使用 ip 池(如果需要频繁发出请求的话)。下面使用 asyncPool 限制并行请求数量。
频率太高,容易根据 ip ban 请求一段时间。
export async function getAll(): Promise<Array<WallpaperItem>> {
let allWallpaperItems = [];
const pageCount = await getPageCount();
if (pageCount > 0) {
const queue = new Array(pageCount).fill(undefined).map((val, index) =>
index + 1
).map((curPage) => getList(curPage));
let i = 0;
const timeout = (item: Promise<unknown>, array: Array<unknown>) => {
const time = Math.random() + 0.5;
console.log(array.length, ++i);
return new Promise((resolve) => setTimeout(() => resolve(item), time));
};
const group = await asyncPool(
REQUEST_CONCURRENCY_LIMIT,
queue,
timeout,
);
allWallpaperItems = (<Array<WallpaperItem>> []).concat(
...(<Array<Array<WallpaperItem>>> group),
);
setCache(allWallpaperItems);
} else {
allWallpaperItems = getCache();
}
return allWallpaperItems;
}
请求完事之后,别忘了将图片列表写到缓存文件中。毕竟历史数据不会发生改变。
function setCache(data: Array<WallpaperItem>) {
Deno.writeTextFile("./cache.json", JSON.stringify(data, null, 2));
}
设置背景图
看 这里
源代码
一键运行
deno run --allow-read --allow-write --allow-net https://deno.land/x/bing_wallpaper_get/main.ts g
定时任务
Windows: 看 这里