生成PDF的需求多么?
你没有碰到过要将网页转换为PDF文件的需求? 图片呢?
如何保存一个网页的快照? 截图? 页面过长怎办,系统自带的截图工具没法滚屏
海外网页被墙,通过在香港服部署一PDF生成服务可以免除无谓的浏览其它垃圾/非法网址。
公司管理层需要在公司集成办公系统上查看其它系统的资料,比如hr系统上员工简历,营销系统上的某报表...
需求还是不少的。
使用历史
个人先后使用过 wkhtmltopdf , phantomjs ,puppeteer。
wkhtmltopdf 使用起来最简单,功能比较单一,看名字就知道了,在windows下直接就是一个exe,可在自己的项目中fork/startProcess 开一个新进程,并传入网页和保存的路径地址,通过 wkhtmltopdf -h 可以查看各参数和说明。如果你要生成的页面是集团内部服务,那么使用它是最方便的。
phantomjs ,同样通过名字就知道它和js 结合的非常紧密。它使用webkit内核,同pupp,用户在可在页面内注入js,像在浏览器上操作页面和元素一样操作它,但它已停止更新了。
puppeteer 是google 提供的 node 库用来操作chrome,有些人称之为无头浏览器,在linux上使用会配套下载chromium。功能上完全可以作为phantomjs 替代品,正因为它操作的实际是chrome浏览器,对于分析页面数据,模拟和破解前端逻辑非常给力。
除此之外,其实还有很多工具,网上能搜出一大把,这里就不多说了。
使用puppeteer实现
'use strict'
const Service = require("egg").Service;
const puppeteer = require('puppeteer');
class pupp extends Service {
async Create(tourl, savePath,ops) {
ops = ops || {};
//console.log("pupp.js ", tourl, savePath,ops);
const browser = await puppeteer.launch({
args : [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-gpu',
'--disable-dev-shm-usage',
'--no-first-run',
'--no-zygote',
//'--single-process'
],
ignoreHTTPSErrors:true
});
const page = await browser.newPage();
// console.log("before go to:");
await page.setUserAgent("Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36");
/*
await page.setExtraHTTPHeaders({
"accept-language":"zh-CN,zh;q=0.9,en;q=0.8",
"cookie":"csrfToken=V04e-JSpCtM_BEr_VH2-73N9",
"cache-control":"max-age=0"
});*/
if(ops.cookies)
{
console.log("setcookies return :",await page.setCookie(...ops.cookies));
await page.waitFor(200);
}
await page.goto(tourl, {
waitUntil : 'networkidle2',
timeout : 60000
});
if(ops.navigation)
await page.waitForNavigation({waitUntil:'networkidle2'});//没有这个,weibo 相关页面会是空白
//await page.screenshot({path: 'screenshot.png'});
//不然会有href 显示在页面
page.emulateMedia('screen');
let scrollEnable = true;
let scrollStep = 1000; //每次滚动的步长
let scrollCount = 0;
while (scrollEnable) {
if (scrollCount++ > 5) //最多允许滚动5次
{
console.error("too many scroll, jumpout ", tourl);
scrollEnable = false;
break;
}
scrollEnable = await page.evaluate((scrollStep) => {
let scrollTop = document.scrollingElement.scrollTop;
document.scrollingElement.scrollTop = scrollTop + scrollStep;
return document.body.clientHeight > scrollTop + scrollStep ? true : false
}, scrollStep);
await page.waitFor(200)
}
page.waitFor(2000);
await page.pdf({
path : savePath,
width : "1280px",
height : "1960px",
printBackground:true
});
await browser.close();
}
/**
将cookie string 转换为cookie object 的数组
*/
cvtCookie(ck_line)
{
let arr = fs.readFileSync("./old_cookies.txt").toString().split(";");
let objcks = arr.map(aline=>{
let ind = aline.indexOf("=");
return {name:aline.substr(0,ind),value:aline.substr(ind+1)};
});
}
}
//延时
function waitMs(ms) {
if (typeof ms != "number")
return Promise.resolve(0);
return new Promise((resolve, rej) => {
setTimeout(() => {
resolve(1);
}, ms);
});
}
module.exports = pupp;
以上是生成pdf文件的核心代码,使用的是egg框架,不熟悉并不影响阅读。有如下几点可留意一下
1:有些注释掉的代码没有去掉,有些代码也可以注释,取决于你真实的测试情况,许多网页的渲染方式不一样,有些还混合着多种js异步渲染。
2:文中的一些具体参数都是经过反复多次验证效果后添加或调整过的,官网上并不会有这些内容,有时一个默认参数就会让效果迥异。
3:代码中有段cookie设置,这是因为很多网页用户不登陆不能看,所以需要前置的用户登陆,并获取登陆成功后的cookie,进而在此步使用。需要留意模拟登陆后的cookie(甚至手动在浏览器登陆后保存cookie)使用的浏览器请求头应尽量保持一致,以免服务端将它区分为两个不同用户。
4:文中有个滚动页面的逻辑,因为有些页面仅显示一屏,当用户滚动鼠标到文末时才会继续加载,就像一些手机app,最多往下拉五次,拉太多没多大意义还浪费时间。
一点废话
puppeteer用作生成pdf有些大材小用了,用作分析和操作页面元素获取数据都十分方便的。个人认为它不太适合大批量的爬取数据,效率较低。
另外,要获取别人的页面就绕不开需要登陆和验证码,如果不是过于频繁的请求,如上文所述,直接用户手动在浏览器输入帐密,然后再将浏览器的cookie保存下来,交由上面代码处理就够了。但如果是将服务部署在服务器,这种办法就失去了作用。笔者就用过一个网上下载的新浪微博登陆库,并使用一种很“挫”的方式填充验证码,工作良好,有兴趣的同学可以留言向我了解。
下一篇,将讲述使用 selinum 获取股票数据
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。