最近在看一位大佬写的源码解析博客,平时上下班用手机看不太得劲,但是平板又没有网卡,所以就想搞个离线pdf版,方便通勤时间学习阅读。

所以,问题来了: 怎么把在线网页内容转成pdf?

这位大佬的博客是用gitbook写的,我先上网搜了下工具,发现大多是将自己本地gitbook转pdf,只有一个开源工具是用python爬取的在线gitbook,但是一看issues,中文乱码、空白页、看不到图等等问题都没解决,遂放弃……

经过我不懈的搜索,终于找到了一个可以直接把网页保存成pdf工具:phantomjs

phantomjs是一个无界面的,可脚本编程的 WebKit 浏览器引擎,也就是俗称的“无头浏览器”,常用于爬取网页信息。

下载地址:https://phantomjs.org/download.html

我用的是win系统,下载后在bin目录下找到.exe文件,然后新建如下js脚本:

// html2pdf.js
var 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。

这时候问题又来了:

  1. phantomjs不能自动截取下一页;
  2. 每一个网页都只能生成一个pdf,最后还要找工具把所有pdf合并成一个;
  3. 保存的实际上是网页的截图,对于侧边栏、顶部栏、底部等我不需要的内容也会保存到页面中,不能很好地适配。

思考和观察得出解决办法:

  • 这个博客的网址除了域名统一外并没有其他规律,只能手动维护一个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地址,都是在一个样式名为nextspan标签内:



获取下一页内容的代码如下:

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);
});
}

总结一下实现思路

  1. 通过dom操作抓取到主体内容,并对其中的图片等资源进行处理,然后保存到html文件中;
  2. 找到下一页的url,将下一页的主体内容继续拼到html文件后;
  3. 最后将html转成pdf保存。

上面的代码不是通用的,但是如果要抓取其他网页的话,思路基本都是这三步。

Demo已开源:https://github.com/youzouzou/node-crawler/blob/main/routes/index.js

npm install后打开http://localhost:3009/ 即可生成pdf。

在线博客转PDF电子书 | JS爬虫初探的更多相关文章

  1. MVC5+EF6+AutoMapper+Bootstrap打造在线博客(1.0)

    目的:MVC5+EF6开发一个高并发.分布式的在线博客,等开发完成以后再用.net core,mysql重新开发,部署到linux系统上,这一系列就算完结,经验不足,大家一起讨论进步,源代码下载QQ群 ...

  2. 我博客上的围棋js程序

    作为一个围棋爱好者,就决定在博客里加个围棋js程序.于是,申请了博客的js权限,美化美化我的博客. 好在js的语法像C系的,看了看,写个程序应该还是可以的. 围棋里,设计好基本的数据结构: //a是1 ...

  3. github page 和 hexo 搭建在线博客

    目录: 安装node.js与git 常用git命令 安装hexo 配置hexo hexo发布到github 1.安装node.js和git工具 https://nodejs.org/en/ 直接下载安 ...

  4. [技术博客]JSCover+selenium获得js代码覆盖率

    本文档讲解了我们是如何使用JSCover来获得Selenium的测试样例的js代码文件的执行覆盖率的. 事实上网上有挺多博客讲这玩意儿了,不过完全按照网上已有的教程去弄的的话,并无法满足我们的需要. ...

  5. MVC5+EF6+AutoMapper+Bootstrap打造在线博客(1.1)

    DAL层的三个Model类: 字典表:CFDict 用户表:CFUser 用户爱好表:CFUserHobby(关联cfuser表和cfdict表) CFUser表和CFUserHobby表是一对多关系 ...

  6. Node.js 爬虫初探

    前言 在学习慕课网视频和Cnode新手入门接触到爬虫,说是爬虫初探,其实并没有用到爬虫相关第三方类库,主要用了node.js基础模块http.网页分析工具cherrio. 使用http直接获取url路 ...

  7. beta阶段学习博客(一) js交互

    js交互 js交互的三种方法

  8. Django_博客项目 引入外部js文件内含模板语法无法正确获取值得说明和处理

    问题描述 : 项目中若存在对一段js代码复用多次的时候, 通常将此段代码移动到一个单独的静态文件中在被使用的地方利用 script 标签的 src 属性进行外部调用 但是如果此文件中存在使用 HTML ...

  9. typecho博客组插件:openSug.js百度搜索框下拉提示免费代码

      Typecho候选搜索增强插件:安装openSug插件即可获得带有“搜索框提示”功能的搜索框,让Typecho搜索更便捷! 支持百度.谷歌.雅虎.Yandex.360好搜.UC神马.酷狗.优酷.淘 ...

随机推荐

  1. InnoDB解决幻读的方案——LBCC&MVCC

    最近要在公司内做一次技术分享,思来想去不知道该分享些什么,最后在朋友的提示下,准备分享一下MySQL的InnoDB引擎下的事务幻读问题与解决方案--LBCC&MVCC.经过好几天的熬夜通宵,终 ...

  2. Charles的证书下载(web)

    1.charles的证书下载(web) 1.为什么下载charles的ssl证书? 默认情况下,charles不能解析https协议的接口,里面的请求和响应数据都是乱码格式,所以我们需要下载ssl证书 ...

  3. LeetCode 617. 合并二叉树 Java

    给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠. 你需要将他们合并为一个新的二叉树.合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 ...

  4. 24.Qt Quick QML-Canvas和Context2D详解

    1.Canvas介绍Canvas是一个允许绘制直线和曲线.简单和复杂的形状.图形和引用的图形图像.它还可以添加文本.颜色.阴影.渐变和图案,并执行低级别像素操作.Canvas输出可以另存为图像文件或序 ...

  5. Nginx导航

    简介 最近都在弄微服务的东西,现在来记录下收获.我从一知半解到现在能从0搭建使用最大的感触有两点 1.微服务各大组件的版本很多,网上很多博客内容不一定适合你的版本,很多时候苦苦琢磨都是无用功 2.网上 ...

  6. [刷题] PTA 7-32 说反话-加强版

    题目描述: 给定一个英语句子,各个单词之间用空格分隔.要求编写程序,将所有单词倒序输出 输入示例: Hello World Here I Come 输出示例: Come I Here World He ...

  7. python 运算及注释

    """ 上面的两行代码,在2.0的python中,若想执行含有汉字的代码,是必不可少的,且两行代码必写在单个py文件最上方,之间不可有任何字节.且该两行代码,可在pych ...

  8. 笔记本用HDMI转VGA 使用双屏办公 听语音

    笔记本用HDMI转VGA 使用双屏办公 听语音 原创 | 浏览:1212 | 更新:2019-11-12 12:16 1 2 3 4 5 6 7 分步阅读 笔记本使用 转接头扩展出一块屏幕.使用多屏办 ...

  9. Linux 部署 iSCSI 服务端

    Linux 部署 iSCSI 服务端 服务端实验环境 iSCSI-server :RHEL8 IP:192.168.121.10 一.服务端安装 target 服务和 targetcli 命令行工具 ...

  10. 运维常用shell脚本一(系统指标巡检、自动创建用户、跳板机)

    一.系统指标巡检脚本 #!/bin/bash menu(){ cat <<EOF +---------------------------------------------+ | 日常巡 ...