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,一经查实,立即删除!

相关文章

(十一)Java 之 String 类

目录 一. 前言 二. String 类 2.1. 创建字符串 2.2. 字符串长度 2.3. 连接字符串 2.4. 创建格式化字符串 2.5. String 常用方法 三. 课后习题 一. 前言 在 Java 中&#xff0c;String 类是一种符合面向对象思想的字符串类&#xff0c;String 类是用于表示字符串的类。它…

ChatGPT的底层核心概念

ChatGPT的底层核心概念 1.1 词嵌入 ​ 词嵌入是一种将单词或文本转换为数字向量的技术。简单来说&#xff0c;它将自然语言中的词汇转换为计算机可以理解的形式&#xff0c;因为计算机无法直接理解单词或文本。例如&#xff0c;对于句子“The monkey is on the horse"&a…

React中hooks使用限制及保存函数组件状态

React Hooks 的限制主要有两条&#xff1a; 不要在循环、条件或嵌套函数中调用 Hook&#xff1b; 在 React 的函数组件中调用 Hook。 首先&#xff0c;Hooks是一个对象&#xff0c;大致结构如下&#xff1a; const hook: Hook {memoizedState: null,baseState: null,baseQ…

面试题之项目做过哪些性能优化可以从哪方面说?

常见的性能优化措施&#xff1a; 1. 减少 HTTP 请求数&#xff1a;合并和压缩 CSS、JavaScript ⽂件&#xff0c;使⽤雪碧图、字体图标等减少图片请求&#xff0c;减少不必要的资源请求。 2. 减少 DNS 查询&#xff1a;减少使用不同的域名&#xff0c;以减少 DNS 查询次数。…

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

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

ubuntu20.04.6wifi图标消失问题解决方案

介绍 本人电脑 惠普战99 2023版 集显版 双系统&#xff1a;win11 ubuntu 20.04.6LTS 安装ubuntu系统后WiFi图标无法显示&#xff0c;四处寻找方法&#xff0c;得以解决 注意 本人亲测有效&#xff0c;但不保证所有机型适用 方法 下载firmware&#xff1a; 链接: https:…

【算法题】108. 将有序数组转换为二叉搜索树

题目 给你一个整数数组 nums &#xff0c;其中元素已经按 升序 排列&#xff0c;请你将其转换为一棵 高度平衡 二叉搜索树。 高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。 示例 1&#xff1a; 输入&#xff1a;nums [-10,-3,0…

AcWing1239.乘积最大

[题目概述] 给定 N 个整数 A1,A2,…AN。 请你从中选出 K 个数&#xff0c;使其乘积最大。 请你求出最大的乘积&#xff0c;由于乘积可能超出整型范围&#xff0c;你只需输出乘积除以 1000000009 的余数。 注意&#xff0c;如果 X<0&#xff0c; 我们定义 X 除以 1000000009…

平台组成-门户系统

门户系统是整个平台的门面。从功能上讲&#xff0c;可以区分为内部门户和外部门户。内部门户面向内部应用&#xff0c;是内部管理的入口&#xff0c;一般运行在内网里&#xff1b;外部门户面向公众&#xff0c;是企业宣传、对外服务的窗口&#xff0c;一般运行在外网上。从访问…

20240211-日常学习(嵌入式微处理器)

目录 嵌入式微处理器的存储体系结构指令集主流的嵌入式微处理器 一些具体介绍冯诺依曼/普林斯顿结构ARM系列嵌入式微处理器 嵌入式微处理器的 存储体系结构 冯诺依曼/普林斯顿结构哈佛结构 指令集 RISCCISC 主流的嵌入式微处理器 ARM系列嵌入式微处理器MIPS系列嵌入式微处…

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

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

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

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

112 C++可调用对象,std::function std::bind

一 可调用对象 本节课将可调用对象整理一下 1 函数指针 2.具有operator()成员函数的类对象仿函数&#xff08;&#xff09; 3. 可被转换为函数指针的类对象 4. 类成员函数指针 5.总结 二 std::function(可调用对象包装器) 1.绑定普通函数 2.绑定类的静态成员函数 3.绑定仿函数…

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;如直射日光或阴影区域&…

力扣代码学习日记四

Problem: 459. 重复的子字符串 文章目录 思路解题方法复杂度代码 思路 给定一个非空的字符串 s &#xff0c;检查是否可以通过由它的一个子串重复多次构成。 示例 1: 输入: s "abab" 输出: true 解释: 可由子串 "ab" 重复两次构成。示例 2: 输入: s &q…

独孤思维:出了20多单,但优化空间巨大

前些日子&#xff0c;在绝版书陪跑群里&#xff0c;有个学员&#xff0c;说自己买了20多本书。 大家都觉得挺不错的&#xff0c;想听听她分享。 结果分享出来以后&#xff0c;我觉得有很大的优化空间。 虽然她把每个精准的对标店铺&#xff0c;做了筛选。 但是对于单个品&a…

BUGKU-WEB source

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

【c++基础】小鱼的航程

说明 有一只小鱼&#xff0c;它上午游泳150公里&#xff0c;下午游泳100公里&#xff0c;晚上和周末都休息&#xff08;实行双休日)&#xff0c;假设从周x(1<x<7)开始算起&#xff0c;请问这样过了n天以后&#xff0c;小鱼一共累计游泳了多少公里呢&#xff1f; 输入数…