deno每天自动获取bing背景图

706

前言

相对 python 的环境配置过程,deno 的一体化结构,让脚本开发运行更简单。

昨天看到了 python每天自动背景图不香吗? 这篇文章,使用 python 脚本获取 bing 的背景图片。一看这个项目,正好适合写一个 deno 的体验 demo。于是就有了这篇文章。

环境安装

偏好命令行的可以看 官方网站 或者 通过单行命令将 Deno 安装到系统中(国内加速)

喜欢直接用可执行文件的,到 这里。记得要配置环境变量。

代码简述

  1. 通过解析 必应壁纸 网站的 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 壁纸的源(这里有高清图片,比 必应壁纸 网站的图片分辨率高)。

  1. 保存图片到本地
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}`);
  }
}
  1. 批量请求图片信息

以请求全站所有图片信息列表为例,首先需要知道一共有多少页。这部分同样是解析页面 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));
}

设置背景图

这里

源代码

参照 bing_wallpaper_get

一键运行

deno run --allow-read --allow-write --allow-net https://deno.land/x/bing_wallpaper_get/main.ts g

定时任务

Windows: 看 这里