如何将自己的 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();
  });
}
相关推荐
余生H4 分钟前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
Ink1 小时前
从底层看 path.resolve 实现
前端·node.js
奔跑吧邓邓子3 小时前
npm包管理深度探索:从基础到进阶全面教程!
前端·npm·node.js
知否技术15 小时前
为什么nodejs成为后端开发者的新宠?
前端·后端·node.js
谢尔登20 小时前
【Node.js】worker_threads 多线程
node.js
osnet1 天前
showdoc二次开发
node.js·vue
泯泷1 天前
「生产必看」在企业环境中正确使用 Node.js 的九大原则
前端·后端·node.js
太阳火神的美丽人生1 天前
Vant WeApp 开启 NPM 遇到的问题总结
前端·npm·node.js
qingshun2 天前
Node 系列之预热知识(1)
node.js
余生H2 天前
前端的全栈混合之路Meteor篇:RPC方法注册及调用
前端·rpc·node.js·全栈