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

news2024/12/24 8:09:59

文章目录

  • 效果展示
  • 说明
  • 利用工具
  • 整体思路
  • Puppeteer 使用笔记
    • 保持登录状态
    • 打开新的页面
    • 点击 dialog
    • 跳转页面
    • 设置页面可见窗口大小
    • 寻找元素
    • 等待元素出现
  • 整体代码


效果展示

请添加图片描述
请添加图片描述

说明

  • 看了看网上很少做这个功能,但是我有这个需求,就抽出时间写了个简单的工具
  • 目前只能导出专栏的文章,不过思路类似
  • 并没有做 Promise 失败的重新发送,代码仍然待完善,欢迎提交分支 PR,仓库地址:github 代码仓库地址

利用工具

  • puppeteer:自动化库
  • asyncPool:并发控制库

整体思路

  1. 获取到你想提取的文章 id
  2. 根据 id 打开编辑器,利用自带的导出按钮

Puppeteer 使用笔记

保持登录状态

如果想保持登录状态的话,就需要把登录信息保存一下

const browser = await puppeteer.launch({
  userDataDir: "./userData",
});

打开新的页面

browser 对象的 newPage() 方法

const page = await browser.newPage();

点击 dialog

类似这样的弹窗,我们也可以监听到,通过 page.on() 方法,可以一直监听页面上的 dialog

在这里插入图片描述

我们实现一下,dialog 出现就点击接受按钮

page.on("dialog", async (dialog) => {
  await dialog.accept();
});

跳转页面

await page.goto(targetURL);

设置页面可见窗口大小

await page.setViewport({ width: 1080, height: 1024 });

寻找元素

官网上的 page 方法

在这里插入图片描述
比方说,我要寻找CSDN上,当前页面的所有文章

在这里插入图片描述
我可以先通过类名拿到 ul

const lis = await page.$(".column_article_list");

然后遍历一下,获取想要的信息,比方说我想获取文章的标题

在这里插入图片描述

就可以用 $$eval 来获取到元素的值

const titles = await lis.$$eval(".title", (elements) => {
    return elements.map((e) =>
      e.innerHTML
        .replace("\n", "")
        .split("<!--####试读-->")[0]
        .replace("\n", "")
        .trim()
    );
  });

等待元素出现

这里可能遇到,由于网速不好等原因,page 中的元素可能不存在,我们可以通过这个方法,等待元素出现

举个例子,我想等待导出按钮的出现

const importButton =
    "div.layout__panel.layout__panel--navigation-bar.clearfix > nav > div.scroll-box > div:nth-child(1) > div:nth-child(22) > button";
await page.waitForSelector(importButton);

整体代码

// index.js
import puppeteer from "puppeteer";
import asyncPool from "tiny-async-pool";
import { getPage, waitingOpenURL, findElement, clickImport } from "./tools.js";

(async () => {
  // 关闭无头模式,显示浏览器窗口
  // userDataDir 表示把登录信息放到当前目录下,省着我们每次调用脚本都需要登录
  const browser = await puppeteer.launch({
    headless: false,
    userDataDir: "./userData",
  });
  const page = await browser.newPage();
  page.on("dialog", async (dialog) => {
    await dialog.accept();
  });
  let targetURL = "https://blog.csdn.net/u010263423/category_9162796.html";
  await page.goto(targetURL);
  await page.setViewport({ width: 1080, height: 1024 });
  const targetPageCount = await getPage(page);
  const willOpenArr = await waitingOpenURL(targetPageCount, targetURL);
  const findArray = [];
  findArray.push(...(await findElement(page)));
  if (targetPageCount > 1) {
    for (let i = 0; i < willOpenArr.length; i++) {
      await page.goto(willOpenArr[i]);
      findArray.push(...(await findElement(page)));
    }
  }

  const baseWriteURL = `https://editor.csdn.net/md/?articleId=`;
  const baseWriteURLArray = findArray.map((i) => `${baseWriteURL}${i.id}`);
  let successHandle = 0;
  function handleURL(url) {
    return new Promise(async (resolve) => {
      const page = await browser.newPage();
      page.on("dialog", async (dialog) => {
        await dialog.accept();
      });
      await page.goto(url);
      await clickImport(page);
      await page.close();
      await new Promise((r) => setTimeout(r, 300));
      resolve(`${url} 解析完成 ${++successHandle}`);
    });
  }
  for await (const ms of asyncPool(2, baseWriteURLArray, handleURL)) {
    console.log(ms);
  }

  console.log("***已完成所有解析***");
})();
// tools.js
/**
 * 功能:获取文章标题
 * @param {*} lis
 * @returns
 */
export async function getTitle(lis) {
  const titles = await lis.$$eval(".title", (elements) => {
    return elements.map((e) =>
      e.innerHTML
        .replace("\n", "")
        .split("<!--####试读-->")[0]
        .replace("\n", "")
        .trim()
    );
  });
  return titles;
}

/**
 * 功能:获取文章写作时间
 * - nth-child 选择器从1开始,前面尽量是标签名吧,如果是类的话,我试了一下选择不到
 * @param {*} lis
 */
export async function getDate(lis) {
  const titleDate = await lis.$$eval(
    ".column_article_data span:nth-child(2)",
    (elements) => {
      return elements.map((e) => e.innerHTML.trim().split(" &nbsp")[0]);
    }
  );
  return titleDate;
}

export async function getID(lis) {
  const titleId = await lis.$$eval("a", (elements) => {
    return elements.map((e) => e.href.split("details/")[1]);
  });
  return titleId;
}

export async function getPage(page) {
  const pageContainer = await page.$(".ui-paging-container");
  let pageCount = 1;
  if (pageContainer) {
    const pageContext = await pageContainer.$$eval(".ui-pager", (elements) => {
      return elements.map((e) => e.innerHTML);
    });
    pageCount = Number(pageContext[pageContext.length - 3]);
  }
  console.log(pageCount);
  return pageCount;
}

export async function waitingOpenURL(targetPageCount, targetURL) {
  const arr = [];
  if (targetPageCount > 1) {
    for (let i = 2; i <= targetPageCount; i++) {
      const front = targetURL.split(".html")[0];
      const url = `${front}_${i}.html`;
      arr.push(url);
    }
  }
  return arr;
}

export async function findElement(page) {
  // 等待页面选择器的出现

  await page.waitForSelector(".column_article_list");
  const lis = await page.$(".column_article_list");

  // 获取文章标题、写作时间、文章id
  const titles = await getTitle(lis);
  const titleId = await getID(lis);
  const titleDate = await getDate(lis);

  // 整理成数组对象
  const notes = [];
  titles.forEach((item, index) => {
    const obj = {
      title: item,
      date: titleDate[index],
      id: titleId[index],
    };
    notes.push(obj);
  });
  return notes;
}

/**
 * 功能: 点击导出按钮
 * @param {*} page
 */
export async function clickImport(page) {
  await new Promise((r) => setTimeout(r, 1500));
  const importButton =
    "div.layout__panel.layout__panel--navigation-bar.clearfix > nav > div.scroll-box > div:nth-child(1) > div:nth-child(22) > button";
  await page.waitForSelector(importButton);
  await page.click(importButton);
  // await new Promise((r) => setTimeout(r, 500));

  const nextImportButton =
    "div.side-bar__inner > div.side-bar__panel.side-bar__panel--menu > a:nth-child(1)";
  await page.waitForSelector(nextImportButton);
  await page.click(nextImportButton);
  // 这个时间是不能省的,一定要给点击事件留点时间,
  // 不然直接跳转页面,下载就失效了
  await new Promise((r) => setTimeout(r, 500));
}

export function handleWriteURLs(url) {
  return new Promise((resolve) => {
    console.log(url);
    resolve();
  });
}

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

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

相关文章

preCICE流固耦合仿真资料整理

preCICE The coupling library for partitioned multi-physics simulations 资料 Tutorials preCICE—多物理场耦合工具介绍 My First Fluid-Solid Interaction Case CalculiX 流固耦合&#xff08;FESIM有限元分析&#xff09; Coupling with preCICE by Gerasimos Chourdak…

k8s-配置与存储-配置管理

文章目录 一、配置存储1.1 ConfigMap1.1.1.基于文件夹的创建方式1.1.2指定文件的创建方式1.1.3 配置文件创建configmap 1.2 Secret1.2.1Secret的应用与Docker仓库 Secret设置1. Kubernetes 中的 Secrets&#xff1a;创建 Secret 示例&#xff1a;将 Secret 挂载到 Pod 中的示例…

URL、DNS过滤,AV---防火墙综合实验

拓扑图 该实验之前的配置请看我的上一篇博客&#xff0c;这里仅配置URL、DNS过滤&#xff0c;AV 需求 8&#xff0c;分公司内部的客户端可以通过域名访问到内部的服务器 这次的拓扑图在外网多增加了一个DNS服务器和HTTP服务器 DNS服务器IP&#xff1a;40.0.0.30 HTTP服务器…

Pandas快问快答16-30题

16. 如何对一个Pandas数据框进行聚合操作? 聚合操作是数据处理中的一种重要方式&#xff0c;主要用于对一组数据进行汇总和计算&#xff0c;以得到单一的结果。在聚合操作中&#xff0c;可以执行诸如求和、平均值、最大值、最小值、计数等统计操作。这些操作通常用于从大量数…

WebGIS开发常用的JS库:VUE/React/Angular对比

Angular: 作用&#xff1a; Angular是一个完整的基于TypeScript的Web应用开发框架&#xff0c;主要用于构建单页Web应用&#xff08;SPA&#xff09;。它适用于大型和复杂的项目&#xff0c;具有强大的组件集合和丰富的文档。 架构&#xff1a; Angular采用组件化的方式&am…

19个Web前端交互式3D JavaScript框架和库

JavaScript &#xff08;JS&#xff09; 是一种轻量级的解释&#xff08;或即时编译&#xff09;编程语言&#xff0c;是世界上最流行的编程语言。JavaScript 是一种基于原型的多范式、单线程的动态语言&#xff0c;支持面向对象、命令式和声明式&#xff08;例如函数式编程&am…

ts快速入门

文章目录 一、运行环境1、线上Playground2、VSCode 编辑器3、Code Runner 插件4、ts-node 二、声明1、变量声明2、常量声明3、类型推断 三、常用数据类型1、number2、string3、boolean4、数组5、对象 四、函数1、函数声明语法2、参数详解&#xff08;1&#xff09;特殊语法&…

物体检测-系列教程8:YOLOV5 项目配置

1、项目配置 yolo的v1、v2、v3、v4这4个都有一篇对应的论文&#xff0c;而v5在算法上没有太大的改变&#xff0c;主要是对v4做了一个更好的工程化实现 1.1 环境配置 深度学习环境安装请参考&#xff1a;PyTorch 深度学习 开发环境搭建 全教程 要求torch版本>1.6&#xf…

基于Mapbox展示GDAL处理的3D行政区划展示实践

目录 前言 一、Gdal数据处理 1、数据展示 2、Java数据转换 二、Mapbox可视化 1、定义Mapbox地图 2、地图初始化 3、创建地图 三、界面优化 1、区域颜色设置 2、高度自适应和边界区分 3、中文标注 总结 前言 最近有遇到一个需求&#xff0c;用户想在地图上把行政区划…

Qt _day1

1.思维导图 2.设计一个简单登录界面 #include "mywidget.h"MyWidget::MyWidget(QWidget *parent): QWidget(parent) {this->setWindowTitle("原神启动"); // this->setStyleSheet("background-color:rgb(255,184,64)");this->setStyl…

力扣爆刷第76天--动态规划完全背包和多重背包

力扣爆刷第76天–动态规划完全背包和多重背包 文章目录 力扣爆刷第76天--动态规划完全背包和多重背包一、139.单词拆分二、56. 携带矿石资源&#xff08;第八期模拟笔试&#xff09; 一、139.单词拆分 题目链接&#xff1a;https://leetcode.cn/problems/word-break/descripti…

华为OD机试真题C卷-篇5

100分值题 小朋友来自多少小区堆内存申请跳格子3测试用例执行计划 小朋友来自多少小区 nums [int(x) for x in input().split(" ")] #index为报告的结果&#xff0c;zones[index]为报告相同结果的总人数 zones [0 for x in range(1000)] count 0i0 while(True):if…

算法项目(2)—— LSTM、RNN、GRU(SE注意力)、卡尔曼轨迹预测

本文包含什么? 项目运行的方式(包教会)项目代码LSTM、RNN、GRU(SE注意力)、卡尔曼四种算法进行轨迹预测.各种效果图运行有问题? csdn上后台随时售后.项目说明 本文实现了三种深度学习算法加传统算法卡尔曼滤波进行轨迹预测, 预测效果图 首先看下不同模型的指标: 模型RM…

解锁服务器外联:TinyProxy一键搭建指南

引言 在服务器需要访问外网的情况下&#xff0c;由于网络安全等原因&#xff0c;许多生产服务器限制了对外网的访问。本文介绍如何通过在一台能够访问外网的服务器上部署TinyProxy来实现代理&#xff0c;使得其他服务器可以通过该代理访问外网。 安装 TinyProxy是一个轻量级…

加固平板电脑在无人机的应用|亿道三防onerugged

无人机技术的快速发展已经在许多领域展现出巨大潜力&#xff0c;而加固平板电脑的应用在无人机领域中扮演着重要角色。 首先&#xff0c;加固平板电脑在无人机探测设备中发挥着关键作用。无人机探测设备通常需要实时传输高清图像和数据&#xff0c;以支持各种监测、勘测和检测…

Vue26 内置标签 v-text v-html

实例 <!DOCTYPE html> <html><head><meta charset"UTF-8" /><title>v-text指令</title><!-- 引入Vue --><script type"text/javascript" src"../js/vue.js"></script></head><…

使用备份工具xtrabackup进行差异备份详细讲解

差异备份 基于第一天进行差异备份 删除之前修改的数据备份 [rootservice ~]# rm -rf /data/backup/* [rootservice ~]# ls /data/backup 完整备份 [rootservice ~]# xtrabackup --defaults-file/etc/my.cnf --backup --target-dir/data/backup/base/ -uroot -pWyxbuke00. -H…

OpenGL学习——16.多光源

前情提要&#xff1a;本文代码源自Github上的学习文档“LearnOpenGL”&#xff0c;我仅在源码的基础上加上中文注释。本文章不以该学习文档做任何商业盈利活动&#xff0c;一切著作权归原作者所有&#xff0c;本文仅供学习交流&#xff0c;如有侵权&#xff0c;请联系我删除。L…

Clion stm32 .elf not found

用Clion新建的STM32CubeMX工程&#xff0c;第一次打开配置的时候可以正常工作。修改了CMakeLists.txt文件&#xff0c;但是关闭后第二次打开时&#xff0c;系统报错提示找不到.elf文件。 尝试解决方法&#xff1a; 重载clion项目 file ->invalidate caches --> invalid…

17.3.1.6 自定义处理

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 模拟某款图像处理软件的处理&#xff0c;它只留下红色、绿色或者蓝色这样的单一颜色。 首先按照颜色划分了6个色系&#xff0c;分别…