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 一下哦 github.com/Lovely-Ruby...

相关推荐
f8979070701 小时前
设置layui动态表格某一行的背景色
前端·javascript·layui
猿大师办公助手2 小时前
如何在Chrome最新浏览器中调用ActiveX控件?
前端·chrome
V_fanglue37052 小时前
qmt量化交易策略小白学习笔记第67期【qmt编程之获取ETF申赎清单】
大数据·前端·数据库·笔记·python·学习·区块链
林啾啾3 小时前
vue3实现自定义主题色切换功能
前端·vue.js
墨·殇3 小时前
vue2实现提取字符串数字并修改数字样式(正则表达式)
前端·javascript·vue.js
软糖工程0013 小时前
正则表达式【详细解读】
大数据·前端·爬虫·python·学习·正则表达式·数据分析
DngYT3 小时前
vue如何挂载路由
前端·javascript·vue.js
呵呵哒( ̄▽ ̄)"4 小时前
vue.js 展示树状结构数据,动态生成 HTML 内容
开发语言·前端·javascript·vue.js
安冬的码畜日常4 小时前
【CSS in Depth 2 精译_035】5.5 Grid 网格布局中的子网格布局(全新内容)
前端·css·css3·网格布局·css布局·子网格·subgrid
JuneTT4 小时前
uniapp 常用高度状态栏,导航栏,tab栏,底部安全高度
前端·javascript·uni-app