最近在看一个大佬写的源码分析博客。平时上下班用手机看,但是没有网卡,所以想做一个离线pdf版,方便上下班时间学习阅读。那么,问题来了:如何将在线网页内容转换为pdf?这位大佬的博客是用gitbook写的。我先是在网上搜索工具,发现大部分都是把我本地的gitbook转成pdf的。只有一个开源工具是用python爬取在线gitbook的,但是看问题的时候,中文乱码,空白页,看不到图片等等,问题都没有解决,所以放弃了。..经过我不懈的寻找,终于找到了一个可以直接将网页另存为pdf的工具:phantomjs。phantomjs是一个无界面、可脚本化的WebKit浏览器引擎,俗称“无头浏览器”,常用于抓取网页信息。下载地址:https://phantomjs.org/downloa...我用的是win系统。下载后在bin目录下找到.exe文件,然后创建如下js脚本://html2pdf.jsvarpage=require('webpage').create();varsystem=require('system');if(system.args.length===1){console.log('Usage:loadspeed.js');//这行代码很重要。任何结束的东西都必须被调用。否则phantomjs不会停止phantom.exit();}page.settings.loadImages=true;//加载图片page.settings.resourceTimeout=30000;//超过10秒后放弃加载//screenshotsettings,//page.viewportSize={//width:1000,//height:3000//};varaddress=system.args[1];varname=system.args[2];page.open(address,function(status){functioncheckReadyState(){//等待页面加载生成pdfsetTimeout(function(){varreadyState=page.evaluate(function(){returndocument.readyState;});if("complete"===readyState){page.paperSize={width:'297mm',height:'500mm',orientation:'肖像',border:'1cm'};vartimestamp=Date.parse(newDate());varpdfname=name;varoutpathstr="C:/Users/Desktop/pdfs/"+pdfname+".pdf";//输出路径page.render(outpathstr);console.log("生成成功");console.log("$"+outpathstr+"$");phantom.exi吨();}else{checkReadyState();}},1000);}checkReadyState();});控制台cd进入bin目录,命令行执行:phantomjshtml2pdf.jshttp://xxx.xx.xxx(博客地址)可以将网页转成pdf。这时候问题又来了:phantomjs无法自动拦截下一页;每个网页只能生成一个pdf,最后找到一个工具把所有的pdf合并为一个;保存的其实是网页截图,侧边栏、顶栏、底部等我不需要的内容也会保存到页面中,不能很好适配。思考观察解决方案:本博客的URL除了统一域名外,没有其他规则。只能手动维护一个url列表,然后用脚本遍历解决第一个问题;网上有现成的pdf合并工具,第二个这个问题也可以解决;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){varstr="";//获取网页数据的绑定方法res.on("data",function(chunk){str+=chunk;})//获取数据res.on("end",function(){//遵循JQuery风格,定义$var$=cheerio.load(str);//获取数据数组vararr=$(".theme-default-content");varmain="";if(arr&&arr.length>0){main=arr[0]}})main通过获取这段代码就是我们要获取的主dom。其次观察图片资源的url:这里使用的是相对路径,所以需要对图片路径进行处理://将上面得到的main转成字符串,方便处理main=$.html(main)//Process图片路径完成main=main.replace(/src=\"\/img/g,"src=\""+prefixUrl+"/img")观察下一页的url地址,都在一个名为next的样式中span标签内部:获取下一页内容的代码如下:var$=cheerio.load(str);vararr=$(".next");varnextData="";if(arr&&arr.length>0){nextData=arr[0]if(nextData&&nextData.children&&nextData.children[0]&&nextData.children[0].attribs){//下一页地址:prefixUrl+nextData.children[0].属性。href}else{//没有下一页}}最后,您需要将html转换为pdf:functionhtmlTopdf(){varhtml=fs.readFileSync(path.resolve(__dirname,htmlFileName),'utf8');varoptions={格式:'A4'};pdf.create(html,options).toFile(path.resolve(__dirname,pdfFileName),function(err,res){if(err)返回console.log("errormessage",err);console.log(res);});}总结实现思路:通过DOM操作抓取主要内容,处理其中的图片等资源,然后保存到html文件中;找到下一页的url,继续将下一页的主要内容拼写到html文件中;最后将html转换成pdf并保存。上面的代码并不通用,但是如果要爬取其他网页,思路基本就是这三步。Demo开源:https://github.com/youzouzou/node-crawler/blob/main/routes/index.jsnpminstall打开http://localhost:3009/生成pdf。