最近在看一位大佬写的源码解析博客,平时上下班用手机看不太得劲,然而平板又没有网卡,所以就想搞个离线pdf版,不便通勤工夫学习浏览。
所以,问题来了: 怎么把在线网页内容转成pdf?
这位大佬的博客是用gitbook写的,我先上网搜了下工具,发现大多是将本人本地gitbook转pdf,只有一个开源工具是用python爬取的在线gitbook,然而一看issues,中文乱码、空白页、看不到图等等问题都没解决,遂放弃……
通过我不懈的搜寻,终于找到了一个能够间接把网页保留成pdf工具:phantomjs。
phantomjs是一个无界面的,可脚本编程的 WebKit 浏览器引擎,也就是俗称的“无头浏览器”,罕用于爬取网页信息。
下载地址:https://phantomjs.org/downloa...
我用的是win零碎,下载后在bin目录下找到.exe文件,而后新建如下js脚本:
// html2pdf.jsvar page = require('webpage').create();var system = require('system');if (system.args.length === 1) { console.log('Usage: loadspeed.js <some URL>'); //这行代码很重要。但凡完结必须调用。否则phantomjs不会进行 phantom.exit();}page.settings.loadImages = true; //加载图片page.settings.resourceTimeout = 30000;//超过10秒放弃加载//截图设置,//page.viewportSize = {// width: 1000,// height: 3000//};var address = system.args[1];var name = system.args[2];page.open(address, function (status) { function checkReadyState() {//期待加载实现将页面生成pdf setTimeout(function () { var readyState = page.evaluate(function () { return document.readyState; }); if ("complete" === readyState) { page.paperSize = { width: '297mm', height: '500mm', orientation: 'portrait', border: '1cm' }; var timestamp = Date.parse(new Date()); var pdfname = name; var outpathstr = "C:/Users/Desktop/pdfs/" + pdfname + ".pdf"; // 输入门路 page.render(outpathstr); console.log("生成胜利"); console.log("$" + outpathstr + "$"); phantom.exit(); } else { checkReadyState(); } }, 1000); } checkReadyState();});
控制台cd进入bin目录,命令行执行:phantomjs html2pdf.js http://xxx.xx.xxx(博客所在地址)
即可将该网页转成一个pdf。
这时候问题又来了:
- phantomjs不能主动截取下一页;
- 每一个网页都只能生成一个pdf,最初还要找工具把所有pdf合并成一个;
- 保留的实际上是网页的截图,对于侧边栏、顶部栏、底部等我不须要的内容也会保留到页面中,不能很好地适配。
思考和察看得出解决办法:
- 这个博客的网址除了域名对立外并没有其余法则,只能手动保护一个url列表,而后通过脚本遍从来解决第 1 个问题;
- pdf合并工具网上有现成的,第 2 个问题也能解决;
- phantomjs是能够对dom进行操作的,然而有个问题,页面里如果有异步申请的资源,比方图片,就须要提早截图工夫,否则会呈现很多空白区域,具体解决办法能够参见这篇博客:应用phantomjs操作DOM并对页面进行截图须要留神的几个问题。
问题尽管是能解决,然而过于麻烦,而且这个dom操作并不能在截图的时候去掉多余内容。
通过下面一系列骚操作,我受到了启发,从而有了一个全新的思路:
通过dom操作,把整个博客的内容都爬到一个html文件中,再把这个html文件转成pdf。
话不多说,间接开撸。
为了避开浏览器同源网络策略,我基于之前搭建的node+express本地服务,并引入插件cheerio(用于dom操作)、html-pdf(用于将网页转成pdf)来实现。
首先,察看须要爬取的dom元素的特点:
我须要爬取的内容如图所示
这部分的内容能够通过.theme-default-content款式获取到:
https.get(url, function (res) { var str = ""; //绑定办法,获取网页数据 res.on("data", function (chunk) { str += chunk; }) //数据获取结束 res.on("end", function () { //沿用JQuery格调,定义$ var $ = cheerio.load(str); //获取的数据数组 var arr = $(".theme-default-content"); var main = ""; if (arr && arr.length > 0) { main = arr[0] } })
通过这段代码失去的main就是咱们要获取的主体dom。
其次,察看图片资源的url:
这里用的是相对路径,所以须要对图片门路进行解决:
// 将下面失去的main转为字符串便于解决main = $.html(main)// 对图片门路进行补全main = main.replace(/src=\"\/img/g, "src=\"" + prefixUrl + "/img")
察看下一页的url地址,都是在一个款式名为next
的span
标签内:
获取下一页内容的代码如下:
var $ = cheerio.load(str);var arr = $(".next");var nextData = "";if (arr && arr.length > 0) { nextData = arr[0] if (nextData && nextData.children && nextData.children[0] && nextData.children[0].attribs) { // 下一页地址:prefixUrl + nextData.children[0].attribs.href } else { // 没有下一页 }}
最初还须要把html转成pdf:
function htmlTopdf() { var html = fs.readFileSync(path.resolve(__dirname, htmlFileName), 'utf8'); var options = { format: 'A4' }; pdf.create(html, options).toFile(path.resolve(__dirname, pdfFileName), function (err, res) { if (err) return console.log("error message", err); console.log(res); });}
总结一下实现思路:
- 通过dom操作抓取到主体内容,并对其中的图片等资源进行解决,而后保留到html文件中;
- 找到下一页的url,将下一页的主体内容持续拼到html文件后;
- 最初将html转成pdf保留。
下面的代码不是通用的,然而如果要抓取其余网页的话,思路根本都是这三步。
Demo已开源:https://github.com/youzouzou/node-crawler/blob/main/routes/index.js
npm install
后关上http://localhost:3009/
即可生成pdf。