NodeJs或者命令行爬取网络教程并生成PDF文件,以阮一峰JavaScript教程和ES6教程为例

4,373 阅读7分钟

前言

你想一夜暴富吗?你想一夜成名吗?你想开兰博基尼泡妞吗?你想拿钞票点烟吗?你想成为世界主宰吗?不,我不想,我只想把我喜欢的教程转成PDF文件,放到我的手机或者阅读器中,什么?你也想,那来吧,本文将介绍:

  1. 通过命令行将某网站的内容转成PDF文件
  2. 通过NodeJS爬虫将某网络教程(例如阮一峰的JavaScript教程和ES6教程等)转成PDF文件
  3. 通过NodeJS或者VScode插件将Markdown文件转成PDF文件

依赖模块

cheerio是nodejs的抓取页面模块,为服务器特别定制的,快速、灵活、实施的jQuery核心实现。适合各种Web爬虫程序

Request是Node.js中的一个模块,目标是用最可能简单的方式,在Node.js发起HTTP请求。此外也支持最新的HTTPS协议

一个通过网址将网页转成PDF的命令行工具,NodeJS版本要大于8.6.0,如果出现安装失败,请翻墙后再安装

将markdown文件转成PDF文件

命令行导出PDF文件

percollate是一位外国友人做的一个命令行工具,是对puppeteer做了一层封装,暴露出常用的API, 我们来看下文档中的例子

一个页面

percollate pdf --output some.pdf https://example.com

多个页面

percollate pdf --output some.pdf https://example.com/page1 https://example.com/page2

操作很简单是不是,哇哦,我们是不是可以美滋滋的将自己喜欢的文章转成PDF在手机或者电脑上看了,嗯,没错是的,不过这也就能玩个文章,如果想拿下整个网站的所有文章就心有余而力不足了,比如下面的这个

当然,不排除有些比较有毅力的同学,把所有url都拿到,然后拼到命令行中,就像我曾经在工作中见过某同事在项目做完后,一行一行的去删console.log(),为的是线上版本的控制台不出现打印的信息,得说下我们使用webpack打包的,这在打包的时候添加一个配置就能解决的问题,我们干嘛要费那老鼻子的劲,拒绝假努力,我们来点有效率的。

NodeJs爬虫将整个网站生成PDF文件

首先我们来瞅一眼阮大神的Javascript教程的网站,地址为 wangdoc.com/javascript/…, 我们能看到网页右边为所有章节的导航,打开Chrome开发者工具,我们能看到

红框圈出的地方即为每个章节对应的地址,到此,你应该能想到接下里我们要干什么了吧,就是通过爬虫拿到所有的章节地址,文章中的地址为相对路径,我们再拼上域名,就成了可以访问的地址了,至于怎么去抓取所有的文章地址,在这里略去,比较简单,相信你看一眼就能懂,源码在下面哟。

那么我们想在NodeJs中使用percollate,该如何操作呢?前面已经说了percollate是个命令行工具,文档上并没有告诉我们怎么在Node环境中怎么调用,难道我们要放弃,直接用puppeteer或者phantomjs去撸吗?怎么可能?秉着你是NodeJs的包,肯定能在NodeJs环境跑的宗旨,我把percollate打印出来瞅瞅

哟,percollate对象下有一个pdf的方法,这不就是我们想要的吗?来一起去瞅瞅源码,看看这个方法要怎么使用,找到项目下面的index.js文件,别问我为什么要看这个文件,程序员都知道的

我们在页面搜索pdf,最终能找到

当然我们也可以通过打印percollate.pdf查看函数的内容

ok,我们现在知道pdf方法接收两个参数,一个是urls,一个是options,通过名字基本就能推测出这两个参数的内容,urls为要转成pdf的网址的数组,options是对转成的pdf文件的一些配置,事实确如我们所料,现在我们可以愉快的生成pdf文件了,美滋滋!实际上你执行之后得到的pdf文件是这个样子的

what?为什么是html?出现这种情况我的第一反应是需要添加某些配置,但文档都是关于命令行操作的,又没有相关的解释,我了个擦,咋办?还能咋办,看源码和Issues, 以我五级的英语水平在Issues中发现这样一个标题

这位老哥想在浏览器中用percollate,哟,老哥可以呀,我Node里面还没跑起来,你就想在浏览器中跑了,点进来看看,竟然真有所发现

他把上面那位老哥想做的事给做出来了,起一个服务,然后通过web api去生成PDF文件,来看看源码,然后我找到下面一段代码

原来我们是少执行了一个percollate.configure的方法,加上之后,执行一波

这下真的美滋滋了,当然这样生成的PDF文件使用的是默认配置,如果你想生成适配你手机或者阅读器的PDF文件,就需要添加你的自定义配置了,

 percollate.pdf(urlList, {
      output: "阮一峰JavaScript教程.pdf",
      css: "@page { size: A6 landscape } html { font-size: 18pt } "
    });

关于css属性的文档,点击查看

完整代码

新建一个util.js,增加一个用于发送请求的方法:

const request = require("request");

function parseBody(url) {
  return new Promise((resolve, reject) => {
    request(url, (error, res, body) => {
      if (!error && res.statusCode === 200) {
        resolve(body);
      } else {
        reject("获取页面失败" + error);
      }
    });
  });
}

module.exports = {
  parseBody
};

新建config文件,添加配置

// 阮一峰JS教程
const javaScriptCourse = {
  url: "https://wangdoc.com/javascript", // 要爬取的网站地址
  name: "阮一峰JavaScript教程.pdf", // 导出的文件名字
  wrapEle: ".menu-list", //  导航父元素的class
  css: "@page { size: A6 landscape } html { font-size: 18pt } ", // 生成pdf的大小和字体
  getUrlList(body, ele, url) {
    // 从返回的html中获取章节地址
    let urlList = [];
    $(body)
      .find(ele)
      .eq(0)
      .find("li a")
      .each((i, v) => {
        const pathStr = $(v).attr("href");
        const path = pathStr.slice(pathStr.indexOf("/"));
        urlList.push(url + path);
      });
    return urlList;
  }
};

新建index.js为项目的入口文件,引入相关依赖

const request = require("./util"),
  percollate = require("percollate"),
  markdownpdf = require("markdown-pdf"),
  fs = require("fs"),
  { javaScriptCourse, es6Course, baseOpt } = require("./config");

const getHtml = url => {
  return request.parseBody(url);
};

const getJSCourse = () => {
  const { url, name, wrapEle, getUrlList, css } = javaScriptCourse;

  getHtml(url).then(res => {
    const urlList = getUrlList(res, wrapEle, url);
    percollate.configure();
    percollate.pdf(urlList, {
      output: name,
      css
    });
  });
};

// 生成pdf文件
getJSCourse()

以上是全部代码,总共不超过80行,执行之后,我们能看到终端打印的日志

成功之后,在项目的目录下就能看到生成的pdf文件

将Markdown文件生成PDF

这个以阮一峰大神ES6教程为例,地址为:es6.ruanyifeng.com ,打开网站后,我们发现,网站是通过接口动态生成内容的,网站请求返回的内容都为Markdown,

我们略过抓取文章地址的过程,详情可在文章附上的源码中查看,执行的转化代码为

percollate.configure();
    percollate.pdf(urlList, {
      output: name,
      css: baseOpt.css,
      sandbox: true // 设置为false,动态生成的内容抓取不到
    });

生成的PDF文件如下,没有转成我们希望的样子,内容为原始的Markdown语法

到此,我没有再研究percollate添加某个配置之后,是否就可以完美的将Markdown转成PDF文件,因为我知道Node有一个包markdown-pdf可以将Markdown转成PDF文件,还知道VScode有一个插件也可以将Markdown转成PDF文件,这样的话,我们首先要生成一个包含所有内容的Markdown文件,Node的fs模块可以很容易的完成这件事情,生成Markdown文件以后,再使用上面讲述的两种方法将Markdown转成PDF即可,代码如下

 const urlList = getUrlList(res, wrapEle, url);
    
    const reqList = [];
    urlList.forEach(v => {
      console.log("请求地址---", v);
      reqList.push(getHtml(v));
    });
    console.log("开始发出请求...");

    Promise.all(reqList)
      .then(arrRes => {
        console.log("所有请求都成功了---");
        const md = arrRes.join(" ");
        // console.log(md);
        const optPath =
          "/Users/apple/Documents/my/LearningLog/NodeJs/网页生成pdf/";

        fs.writeFileSync(`${name}.md`, md, function(err) {
          if (err) {
            return console.error(err);
          }
          console.log("数据写入成功!");
        });
        console.log("开始生成pdf文件...");
        markdownpdf({
          paperFormat: "A6"
          // paperOrientation: "landscape"
        })
          .from(`${optPath}${name}.md`)
          .to(`${optPath}${name}.pdf`, function() {
            console.log("生成pdf文件成功");
          });
      })
      .catch(err => {
        console.log("请求报错---", err);
      });

关于使用VScode将Markdown文件转为PDF的方法,我这里就不赘述了,参考markdown-preview-enhanced

写在最后

本文的所有代码以及生成的PDF文件都在下面的地址,后续会更新更多的大佬免费教程的PDF文件

点此查看源码,喜欢的Star或者Fork哟

已生成的免费网络教程PDF文件:

如需调整大小、字体或者样式,请fork源码自行生成。