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

文章目录

  • 效果展示
  • 说明
  • 利用工具
  • 整体思路
  • 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");// 获取文章标题、写作时间、文章idconst 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.mzph.cn/news/687270.shtml

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

相关文章

19-k8s的附加组件-coreDNS组件

一、概念 coreDNS组件&#xff1a;就是将svc资源的名称解析成ClusterIP&#xff1b; kubeadm部署的k8s集群自带coreDNS组件&#xff0c;二进制部署需要自己手动部署&#xff1b; [rootk8s231 ~]# kubectl get pods -o wide -A k8s系统中安装了coreDNS组件后&#xff0c;会有一个…

解锁Spring Boot中的设计模式—05.策略模式:探索【策略模式】的奥秘与应用实践!

1.策略者工厂模式&#xff08;Map版本&#xff09; 1.需求背景 假设有一个销售系统&#xff0c;需要根据不同的促销活动对商品进行打折或者其他形式的优惠。这些促销活动可以是针对不同商品类别的&#xff0c;比如男装、女装等。 2.需求实现 活动策略接口&#xff1a;定义了…

Java:集合以及集合进阶 --黑马笔记

一、集合概述和分类 1.1 集合的分类 除了ArrayList集合&#xff0c;Java还提供了很多种其他的集合&#xff0c;如下图所示&#xff1a; 我想你的第一感觉是这些集合好多呀&#xff01;但是&#xff0c;我们学习时会对这些集合进行分类学习&#xff0c;如下图所示&#xff1a;…

javaweb学习day03(JS+DOM)

一、javascript入门 1 官方文档 地址: https://www.w3school.com.cn/js/index.asp离线文档: W3School 离线手册(2017.03.11 版).chm 2 基本说明 JavaScript 能改变 HTML 内容&#xff0c;能改变 HTML 属性&#xff0c;能改变 HTML 样式 (CSS)&#xff0c;能完成 页面的数据…

K8s进阶之路-Pod的生命周期

Pod创建过程&#xff1a; 首先创建一个pod&#xff0c;然后创建一个API Server 和 Etcd【把创建出来的信息存储在etcd中】 然后创建 Scheduler&#xff0c;监控API Server是否有新的Pod&#xff0c;如果有的话&#xff0c;会通过调度算法&#xff0c;把pod调度某个node上 在nod…

HDR 摄影

HDR 摄影&#xff0c;即高动态范围 High Dynamic Range摄影&#xff0c;旨在通过合并不同曝光值的照片来捕捉场景中从最亮到最暗部分的全部细节。 这种技术对于在一个图像中展现广泛的亮度范围特别有用&#xff0c;尤其是在自然光线条件下&#xff0c;如直射日光或阴影区域&…

BUGKU-WEB source

题目描述 题目截图如下&#xff1a; 进入场景看看&#xff1a; 解题思路 看源码&#xff0c;看F12网络请求没有东西只能老老实实按照提示用Linux去扫描目录 相关工具 kali虚拟机安装gobuster 或者dirsearch 解题步骤 先查看源码&#xff1a; flag{Zmxhz19ub3RfaGvyzS…

枚举,#define,C中程序内存区域划分

目录 一、枚举 1.1枚举类型的声明 1.2枚举类型的优点 1.3枚举类型的使用 二、#define定义常量 三、C中程序内存区域划分 一、枚举 1.1枚举类型的声明 枚举顾名思义就是⼀⼀列举。 把可能的取值⼀⼀列举。 比如我们现实生活中&#xff1a; ⼀周的星期⼀到星期日是有限…

LeetCode LCR 085. 括号生成

题目链接https://leetcode.cn/problems/IDBivT/description/ 正整数 n 代表生成括号的对数&#xff0c;请设计一个函数&#xff0c;用于能够生成所有可能的并且 有效的 括号组合。 class Solution {public List<String> generateParenthesis(int n) {List<String>…

租房招聘|在线租房和招聘平台|基于Springboot的在线租房和招聘平台设计与实现(源码+数据库+文档)

在线租房和招聘平台目录 目录 基于Springboot的在线租房和招聘平台设计与实现 一、前言 二、系统功能设计 三、系统实现 1、房屋管理 2、招聘管理 3、平台资讯管理 4、平台资讯类型管理 四、数据库设计 1、实体ER图 六、论文参考 七、最新计算机毕设选题推荐 八、源…

五分钟搭建本地大数据集群

引言 刚接触大数据以及部分接触大数据多年的伙伴可能从来没有自己搭建过一套属于自己的大数据集群&#xff0c;今天就花点时间聊聊怎么快速搭建一套属于自己、且可用于操作、调试的大数据集群 正文 本次搭建的组件都有以下服务以及对应的版本 hadoop&#xff08;3.2.4&…

六、Spring/Spring Boot整合ActiveMQ

Spring/Spring Boot整合ActiveMQ 一、Spring整合ActiveMQ1.pom.xml2.Queue - 队列2.1 applicationContext.xml2.2 生产者2.3 消费者 3.Topic - 主题3.1 applicationContext.xml3.2 生产者3.3 消费者 4.消费者 - 监听器4.1 编写监听器类4.2 配置监听器4.3 生产者消费者一体 二、…

安装luajit及使用python运行lua脚本

使用Python运行lua脚本前&#xff0c;需要先安装LuaJIT&#xff0c;LuaJIT的官网是下载 (luajit.org) 目前已不再使用.exe文件的下载方式&#xff0c;需要使用Git从公共仓库下载源码&#xff0c;git命令为&#xff1a; $ git clone https://luajit.org/git/luajit.git 下载后…

Mybatis速成(二)

文章目录 1. Mybatis基础操作1.1 需求1.2 准备1.3 删除1.3.1 功能实现1.3.2 日志输入1.3.3 预编译SQL1.3.3.1 介绍1.3.3.2 SQL注入1.3.3.3 参数占位符 1.4 新增1.4.1 基本新增1.4.2 主键返回 1.5 更新1.6 查询1.6.1 根据ID查询1.6.2 数据封装1.6.3 条件查询1.6.4 参数名说明 2.…

PLC-Recorder的延伸分析功能说明

目录 一、缘起 二、如何从PLC-Recorder获取数据 1、在线获取 2、全自主打开数据文件 3、延伸分析 三、设置方法 四、效果展示 一、缘起 在各个行业&#xff0c;在不同的场景中&#xff0c;朋友们拿到数据后&#xff0c;想做的事情五花八门&#xff0c;有做宏观分析的、…

排序前言冒泡排序

目录 排序应用 常见的排序算法 BubbleSort冒泡排序 整体思路 图解分析 ​ 代码实现 每趟 写法1 写法2 代码NO1 代码NO2优化 时间复杂度 排序概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递…

Anaconda修改虚拟环境的路径

新版本的anaconda会默认将虚拟环境配置在C盘下&#xff0c;默认的路径是C:\Users\username。同时anaconda3下envs目录是空的。 这里是建立虚拟环境是将路径修改到anaconda的方法。 第一步——修改.condarc文件 首先&#xff0c;C:\Users\username找到.condarc文件 添加或修…

洛谷P5714 肥胖问题 题解

#题外话&#xff08;第25篇题解&#xff09; #先看题目 题目链接https://www.luogu.com.cn/problem/P5714 特别的&#xff0c;对于Microsoft Edge浏览器&#xff0c;其自带有截长图引擎&#xff0c;路径为三个点→截图→选定后下拉即可&#xff0c;浏览器会自动为你下拉并选中…

Code Composer Studio (CCS) - Comment (注释)

Code Composer Studio [CCS] - Comment [注释] References Add Block Comment: 选中几行代码 -> 鼠标右键 -> Source -> Add Block Comment shortcut key: Ctrl Shift / Remove Block Comment: 选中几行代码->鼠标右键->Source->Remove Block Comment s…

Unity类银河恶魔城学习记录7-6 P72 Bouncy sword源代码

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili Sword_Skill_Controller.cs using System.Collections; using System.Colle…