xiaohanyu / blog-html-to-pdf

[Fun] A sample program to convert blog website to merged pdf.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

English README

注意

新加入抓取 Microsoft Azure Architecture Center 文章的程序。程序调用方式已经变更,如果按照些说明文档操作抓取 Brendan Gregg 文章,请 git reset --hard d8cc3c2

简介

事情是这样的,我的一位朋友是 Brendan Gregg 的粉丝(啊,我也是),想把他的 blog 保存成 PDF,放到 kindle 上随时研读,群里讨论起来,就聊起来有没有一些好的办法能够把 130 篇文章由 HTML 转成 PDF。

简单想了下,要解决这个问题,有三个步骤:

  • 拿到 130 篇 blog 文章的 URL
  • 将每篇文章由 HTML 转换成 PDF
  • 最后再将转换后的 PDF 合成一个大的 PDF 文件

重点在于前两步。

拿到所有 blog 文章的 URL

第一步,拿到一个网站或者多个网站的所有 URL,本质上是一个爬虫问题。 wget 是一个非常好用的下载工具,除了下载单个文件,wget 还可以下载一整个网站并将网站的链接转换成本地链接。

我们用下面的命令拿到 brendan gregg 网站的所有链接:

wget --spider -r http://www.brendangregg.com/blog/ 2>&1 | grep '^--' | awk '{ print $3 }' | grep -v '\.\(css\|js\|png\|gif\|jpg\|JPG\)$' > /tmp/urls.txt

我们发现 brendan gregg 网站的 blog 的 URL 非常有规律:

http://www.brendangregg.com/blog/2008-12-02/a-quarter-million-nfs-iops.html
http://www.brendangregg.com/blog/2008-12-15/up-to-2gbs-nfs.html
http://www.brendangregg.com/blog/2008-12-15/up-to-2gbs-nfs.html
http://www.brendangregg.com/blog/2009-01-09/1gbs-nfs-from-disk.html
http://www.brendangregg.com/blog/2009-01-09/1gbs-nfs-from-disk.html

于是我们用如下的命令过滤出所有 blog 文章的 URL:

cat /tmp/urls.txt | grep 'blog/2' | grep '.html$' | sort | uniq > blog.txt

到此,第一步完成。

将文章由 HTML 转换成 PDF

接下来,我们要将 130 篇文章全部由 HTML 转换成 PDF。

显然,手工做是不可以滴。批量转换必须用到 headless browser

前几年 headless browser 的事实标准是 PhantomJS,不过后来 Chrome 团队放了个大招 puppeteer,基本上算宣告了 PhantomJS 寿终正寝。

我们可以用下面的代码将 HTML 网页转存成 PDF:

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://news.ycombinator.com', {waitUntil: 'networkidle2'});
  await page.pdf({path: 'hn.pdf', format: 'A4'});

  await browser.close();
})();

略微封装一下,我们可以将 130 篇 blog 文章全部转换成 PDF。

需求注意的问题:

  • JS 中写异步代码并不是特别舒服,如有可能,用 async/await 的方式,写起来舒服很多
  • Puppeteer 本质上是一个 chrome,页面多的话相当耗资源,因此 HTML 转存 PDF 的时候需要控制下频率,每次截图之后关闭 page,并 sleep(10000)。我第一版的代码没有注意到这个问题并且为了调试同时 disable 了 headerless 选项 (const browser = await puppeteer.launch({headless: false}),直接导致电脑内存耗光,出现了数十个 chromium 共存的感人画面:

完整的截图代码在这里

运行:

yarn
mkdir output
node index.js

合并 PDF

大概花十几分钟的样子,我们可以拿到约 130 篇文章的 PDF,接下来我们将 130 篇 PDF 合并成一个大的 PDF 文件,这样可以方便在移动设备上管理和阅读。

合并 PDF 的工具相当多,专业的如 Adobe Acrobat,命令行的工具有 PDFtk

Mac 上可以用 ghostscript

brew install ghostscript

合并 PDF 可以用类似下面的命令:

gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=merged.pdf pdf1.pdf pdf2.pdf

我们用 ls -t 命令列出所有的 PDF 文件,并按照文件的 modifed time 进行排序(man ls 命令 查看 -t 参数的含义)。

如此,我们得到下面的命令:

gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=merged.pdf `ls -t`

大约一两分钟的样子,130 个 PDF 合并成一个 640+ 页的 PDF。我提供两种版本下载:

思考

一呢,其实如果将 PDF 放到 kindle 这种小尺寸的设备上阅读的话,puppeteer 的截图参数还可以再改一下。我在程序里的设定是按照 A4 尺寸转换成 PDF,如果放到 kindle 上阅读,用 A5 的尺寸转 PDF 也许阅读效果会更好一点,这个时候,web 这种流式排版——流式排版这个名词是我自创的,嗯——在适配不同尺寸设备方面就显示出了巨大的优势。

二,如果做一个类似的 web 服务,会有人愿意买单么?

三,工具的强是无敌的……

About

[Fun] A sample program to convert blog website to merged pdf.


Languages

Language:JavaScript 100.0%