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

news2024/12/23 20:51:59

文章目录

  • 上一篇
  • 效果演示
  • Puppeteer 修改浏览器的默认下载位置
  • 控制并发数
  • 错误重试
  • 并发控制 + 错误重试
  • 源码


上一篇

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

效果演示

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

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

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

请添加图片描述

在这里插入图片描述

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

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

在这里插入图片描述

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

在这里插入图片描述

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 的库的源码

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

// 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 逻辑

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,我们修改一下自己的逻辑

// 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);
}

然后调用一下

await asyncPool(3, baseWriteURLArray, handleURL);

在这里插入图片描述

源码

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1460973.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

压缩感知为什么要进行“不相干欠采样”?

压缩感知理论的三个核心要素。1、不相干性信号欠采样&#xff1b;2、稀疏变换&#xff1b;3、非线性迭代重建。 为了通俗解释“不相干性信号欠采样”&#xff0c;我们可以借用一种生活中的例子——拼图。 例子 想象一下&#xff0c;我们有一张由数百片拼图块组成的完整画面。…

Java项目:20 基于SSM实现的支教管理系统

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 ssm支教管理系统&#xff08;前台后台&#xff09; 前台角色&#xff1a;支教学校志愿者 支教学校功能模块&#xff1a;支教学校查询报名职位…

Oracle conn / as sysdba遇到ORA-01031: insufficient privileges错误

背景 oracle 突然挂了&#xff0c;处于锁定状态&#xff0c;然后打算重新启动一下子。 遂 sqlplus /nolog conn / as sysdba 然后就出现了以下错误。。 ORA-01031: insufficient privileges 1.查了一圈&#xff0c;有说是 计算机 》 管理》本地用户和组》组》ORA_DBA&am…

《Linux运维总结:Ubuntu22.04忘记root密码解决方案》

一、解决方法 1、首先重新启动Ubuntu系统&#xff0c;然后快速按下shift键&#xff0c;以调出grub启动菜单&#xff0c;如下图所示&#xff1a; 2、在这里我们选择第二个&#xff08;Advance options for Ubuntu&#xff09;&#xff0c;选中后按下Enter键&#xff0c;如下图所…

三维GIS开发的就业前景

一、前言 三维GIS是一个伪概念,GIS是地理信息系统&#xff0c;三维GIS就是三维地理信息系统&#xff0c;在课本上&#xff0c;专业概念上&#xff0c;也没有这一说法吧&#xff0c;所以三维GIS&#xff0c;就是技术人员造概念拼凑造出来的&#xff0c;本质上就是GIS三维可视化…

车载软件架构Adaptive AUTOSAR —— 身份和访问管理和加密技术

车载软件架构Adaptive AUTOSAR —— 身份和访问管理和加密技术 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师(Wechat:gongkenan2013)。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 本就是小人物,输了就是输了,不要在意别人怎么看自己。…

FreeRTOS学习笔记——(任务调度)

这里写目录标题 一、开启任务调度器&#xff08;熟悉&#xff09;二、启动第一个任务&#xff08;熟悉&#xff09;2.1&#xff0c;prvStartFirstTask ()2.2&#xff0c; vPortSVCHandler () 三、任务切换&#xff08;掌握&#xff09; 一、开启任务调度器&#xff08;熟悉&…

【C++初阶】类和对象(中)

目录 一.类的6个默认成员函数 1.知识引入 ​编辑 2.构造函数 (1)概念 (2)语法特性 (3)特征 ①问题引入1 ②问题引入2 &#xff08;缺少默认构造函数&#xff09; 3.析构函数 (1)概念 (2)特性 4.拷贝构造函数 (1)概念 (2)特征 ①拷贝构造函数是构造函数的一…

css伪类函数 :is()、:where()、:has()、:not()

本文主要描述:is()、:where()、:has()、:not()&#xff0c;四个方法大部分内容相同&#xff0c;首先主要讲:is()方法&#xff0c;并根据:is()方法与:where()、:has()、:not()方法的不同来说明这三种方法的特性 :is() 使用方法 选择器特定性 安全性 伪类和伪元素的支持 :前…

Java 后端面试指南

面试指南 TMD&#xff0c;一个后端为什么要了解那么多的知识&#xff0c;真是服了。啥啥都得了解 MySQL MySQL索引可能在以下几种情况下失效&#xff1a; 不遵循最左匹配原则&#xff1a;在联合索引中&#xff0c;如果没有使用索引的最左前缀&#xff0c;即查询条件中没有包含…

【elasticsearch实战】知识库文件系统检索工具FSCrawler

需求背景 最近有一个需求需要建设一个知识库文档检索系统&#xff0c;这些知识库物料附件的文档居多&#xff0c;有较多文档格式如&#xff1a;PDF, Open Office, MS Office等&#xff0c;需要将这些格式的文件转化成文本格式&#xff0c;写入elasticsearch 的全文检索索引&am…

YOLO-World:实时开放词汇目标检测

paper&#xff1a;https://arxiv.org/pdf/2401.17270.pdf Github&#xff1a;GitHub - AILab-CVC/YOLO-World: Real-Time Open-Vocabulary Object Detection online demo&#xff1a;https://huggingface.co/spaces/stevengrove/YOLO-World 目录 0. 摘要 1. 引言 2. 相关工…

解决docker中运行的jar包连不上数据库

目录 数据库主机地址设置问题&#xff1a; 网络连接问题&#xff1a; 数据库端口映射&#xff1a; 数据库认证问题&#xff1a; 数据库服务是否正常运行&#xff1a; 日志查看&#xff1a; 如果在 Docker 中运行的 JAR 包无法连接到数据库&#xff0c;有几个可能的原因和…

优秀自媒体工作者常用的7款ai写作工具! #AI写作#AI写作

我们做自媒体运营&#xff0c;想要快速的创作内容&#xff0c;提供文章的创作速度是我们的目标&#xff0c;我们别的大佬可以很快地就创作出一篇内容&#xff0c;而自己墨迹半天确出不了一个字呢&#xff1f;其实这关乎到创作技巧&#xff0c;下面小编就跟大家分享如何利用自媒…

Linux的Ubuntu的APT使用

Linux的Ubuntu的APT使用 apt 介绍 apt 是 Advanced Packaging Tool 的简称&#xff0c;是一款安装包管理工具。在 Ubuntu 下&#xff0c;我们可以使用 apt 命令进行软件包的安装、删除、清理等&#xff0c;类似于 Windows 中的软件管理工具。 Ubuntu 软件操作的相关命令 su…

SD-WAN专线:助力企业海外社交媒体推广

随着全球化的发展&#xff0c;越来越多的企业将目光投向海外市场&#xff0c;而在海外市场推广中&#xff0c;社交媒体平台成为了一个重要的推广渠道。然而&#xff0c;很多企业在海外社交媒体推广过程中都会遇到网络问题&#xff0c;传统的VPN解决方案往往存在IP被封、网络不稳…

32单片机基础:GPIO输入

1.1按键控制LED 按键介绍&#xff1a; 两种方式&#xff0c;我们一般用下接的方式。 第一个图&#xff1a;注意点。当按键按下&#xff0c;PA0接地&#xff0c;被置为低电平&#xff0c; 但是一旦按键松手&#xff0c;PA0悬空&#xff0c;引脚电压不确定。所以无论怎么读引脚…

MySQL 核心模块揭秘 | 06 期 | 事务提交之前,binlog 写到哪里?

1. 准备工作 参数配置&#xff1a; binlog_format ROW binlog_rows_query_log_events OFF创建测试表&#xff1a; CREATE TABLE t_binlog (id int unsigned NOT NULL AUTO_INCREMENT,i1 int DEFAULT 0,str1 varchar(32) DEFAULT ,PRIMARY KEY (id) USING BTREE ) ENGINEIn…

使用 Nuxt 构建简单后端接口及数据库数据请求

写在前面 本文主要为大家介绍&#xff0c;如何使用 Nuxt 框架实现一个简单的后端接口&#xff0c;并且从数据库中请求数据返回给前端。 实现 创建 serverMiddleware 文件夹 首先我们新建一个名字为 serverMiddleware 文件夹用来存储接口相关信息 目录结构如下&#xff1a;…

探索 JavaScript ES8 中的函数式编程并通过实例加以实践

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 介绍 函数式编程是一种强大的范式&#xff0c…