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

效果展示

说明

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

利用工具

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

整体思路

  1. 获取到你想提取的文章 id
  2. 根据 id 打开编辑器,利用自带的导出按钮
  3. 代码比较潦草,望见谅

代码

javascript 复制代码
// 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("***已完成所有解析***");
})();
javascript 复制代码
// 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();
  });
}
相关推荐
Dreamcatcher_AC20 小时前
慢慢买项目:一站式开发指南
前端·javascript·mongodb·node.js
源代码杀手1 天前
从 0 到 1 重新部署新的Node.js 项目到 Cloudflare Workers:避坑指南 + 完整流程
node.js
wgc2k1 天前
Nest.js基础-2、Node.js的版本管理和包管理
开发语言·javascript·node.js
阿珊和她的猫2 天前
Webpack常用配置项详解
前端·webpack·node.js
ljh5746491192 天前
nvm install lts 中的lts 是什么
node.js
阿珊和她的猫2 天前
Webpack 常用插件深度解析
前端·webpack·node.js
C_心欲无痕2 天前
nodejs - express:流行的 Web 应用框架
前端·node.js·express
pan3035074792 天前
Node.js组织机构与权限设计方案
node.js·数据库架构
全栈前端老曹3 天前
【前端路由】Vue Router 嵌套路由 - 配置父子级路由、命名视图、动态路径匹配
前端·javascript·vue.js·node.js·ecmascript·vue-router·前端路由
EndingCoder3 天前
TypeScript 入门:理解其本质与价值
前端·javascript·ubuntu·typescript·node.js