如何将自己的 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();
  });
}
相关推荐
丁总学Java12 分钟前
thread_id_key != 0x7777(`fibers` 包与 Node.js 16 及以上版本存在兼容性问题)
node.js·fibers
程楠楠&M14 小时前
node.js第三方Express 框架
前端·javascript·node.js·express
盛夏绽放14 小时前
Node.js 和 Socket.IO 实现实时通信
前端·后端·websocket·node.js
Roc.Chang16 小时前
macos 使用 nvm 管理 node 并自定义安装目录
macos·node.js·nvm
怕冷的火焰(~杰)18 小时前
Node基本使用
node.js
_半夏曲19 小时前
node.js、nginx、iis、tomcat针对部署方面的简述
nginx·node.js·tomcat
生椰拿铁You20 小时前
09 —— Webpack搭建开发环境
前端·webpack·node.js
酷酷的威朗普1 天前
医院绩效考核系统
javascript·css·vue.js·typescript·node.js·echarts·html5
前端李易安2 天前
Webpack 热更新(HMR)详解:原理与实现
前端·webpack·node.js
Ztiddler2 天前
【npm设置代理-解决npm网络连接error network失败问题】
前端·后端·npm·node.js·vue