Puppeteer 使用实战:如何将自己的 CSDN 专栏文章导出并用于 Hexo 博客(二)

文章目录


上一篇

Puppeteer 使用实战:如何将自己的 CSDN 专栏文章导出并用于 Hexo 博客(一)

效果演示

上一篇实现了一些基本功能,但是还有些问题

  • 有些时候页面会卡死,或者说找不到导出的元素,导致这篇文章下载不了
  • 不能控制标签页的打开数量,不够灵活(只能一个标签页、一个标签页的工作,效率低下)
  • 下载文件的默认位置没有修改

根据上面的问题,这次添加了并发控制,以及错误重试,效果如下图:

Puppeteer 修改浏览器的默认下载位置

查了官网好久的相关配置,没找到,然后谷歌,终于在这个网站上找到了答案

我的代码修改在这里了,注意声明的位置,一定要提前

javascript 复制代码
import path from "path";
const __dirname = path.resolve(path.dirname(""));
const myDownloadPath = `${__dirname}\\my-post`;
  const page = await browser.newPage();
  const client = await page.createCDPSession();
  await client.send("Page.setDownloadBehavior", {
    behavior: "allow",
    downloadPath: myDownloadPath,
  });

这里提一嘴,我原先是把代码放到下图这个位置,(每次新建页面下重新设置),发现总是有些小 bug

  • 有的时候会下载到浏览器的默认目录(也就是代码根本没生效)
  • 多线程的时候会部分放到指定目录,部分放到默认目录,比方说双并发的时候,具体问题看我下面的图

    给我的感觉,它算是一个全局的修改,所以只需要提前 声明一次即可,不用每一次新建 newPage 就设置一次

控制并发数

这个可以参考一下这个叫 async-pool 的库的源码

我在这儿写了一个小案例,可以试试

javascript 复制代码
// https://github.com/rxaviers/async-pool/blob/1.x/lib/es7.js
async function asyncPool(poolLimit, iterable, iteratorFn) {
  const ret = [];
  const executing = new Set();
  for (const item of iterable) {
    const p = Promise.resolve().then(() => iteratorFn(item));
    ret.push(p);
    executing.add(p);
    const clean = () => executing.delete(p);
    p.then(clean).catch(clean);
    if (executing.size >= poolLimit) {
      await Promise.race(executing);
    }
  }
  return Promise.all(ret);
}

const timeout = (i) => {
  console.log("开始" + i);
  return new Promise((resolve) =>
    setTimeout(() => {
      resolve(i);
      console.log("结束" + i);
    }, 1000 + Math.random() * 1000)
  );
};

let urls = Array(10)
  .fill(0)
  .map((v, i) => i);
console.log(urls);

(async () => {
  const res = await asyncPool(2, urls, timeout);
  console.log(res);
})();

错误重试

也是用了一个 demo 逻辑

javascript 复制代码
const retry = (fn, times) => {
      return new Promise((res, rej) => {
        const attempt = () => {
          fn()
            .then(res)
            .catch((error) => {
              times-- > 0 ? attempt() : rej("机会用光了");
            });
        };
        attempt();
      });
    };

    let getNum = function () {
      console.log("函数执行一次");
      return new Promise((res, rej) => {
        let num = Math.random() * 10;
        num < 2 ? res("数字小于2") : rej("数字大于2");
      });
    };
    retry(getNum, 3)
      .then((mes) => {
        console.log(mes);
      })
      .catch((err) => {
        console.log(err);
      });

并发控制 + 错误重试

结合之前的两个 demo,我们修改一下自己的逻辑

javascript 复制代码
// tools.js
function retry(fn, times, item) {
  const allTime = times;
  const articleId = item.split("articleId=")[1] || "";
  return new Promise((res, rej) => {
    const attempt = () => {
      const currTime = allTime - times + 1;
      fn()
        .then(() => {
          console.log(
            `Retry Success: 第 ${currTime} 次重试 ${articleId} 成功!`
          );
          res(item);
        })
        .catch((error) => {
          console.log(`Warning: 第 ${currTime} 次重试 ${articleId} `);
          if (times-- > 0) {
            attempt();
          } else {
            console.log(
              `Error:  已经重试 ${item} 文章 ${currTime} 次,机会已用光`
            );
            rej();
          }
        });
    };
    attempt();
  });
}

// https://github.com/rxaviers/async-pool/blob/1.x/lib/es7.js
export async function asyncPool(poolLimit, iterable, iteratorFn) {
  const ret = [];
  const executing = new Set();
  for (let i = 0, len = iterable.length; i < len; i++) {
    const item = iterable[i];
    const articleId = item.split("articleId=")[1] || "";
    const p = Promise.resolve()
      .then(() => iteratorFn(item))
      .catch(async (err) => {
        console.log(`${articleId} 解析失败,即将重试`);
        // 这里的 retry 也添加上 await
        await retry(() => iteratorFn(item), 3, item).catch(() => {});
      });
    ret.push(p);
    executing.add(p);
    const clean = () => executing.delete(p);
    p.then(clean).catch(clean);
    if (executing.size >= poolLimit) {
      await Promise.race(executing);
    }
  }
  return Promise.all(ret);
}

然后调用一下

javascript 复制代码
await asyncPool(3, baseWriteURLArray, handleURL);

源码

想要源码可以查看此仓库,如果有用记得 star 一下哦 https://github.com/Lovely-Ruby/CSDNBlogsExport

相关推荐
大树8810 小时前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠10 小时前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质10 小时前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
小宇宙Zz11 小时前
Maven依赖冲突
java·服务器·maven
Inhand陈工11 小时前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智12 小时前
ARP代理--工作原理
运维·网络·arp·arp代理
麦聪聊数据12 小时前
数据服务化时代:企业数据能力输出的核心路径
数据库
shushangyun_12 小时前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化
古城小栈12 小时前
Unix 与 Linux 异同小叙
linux·服务器·unix
DARLING Zero two♡12 小时前
【MySQL数据库】数据类型与表约束
数据库·mysql