Contents

java调用phantomjs爬取网页及快照

/images/post/phantomjs.png

前言

前阵子遇到一个需求,需要爬取学信网学历的查询结果进行解析并截图留证,利用java完成了爬取的html解析功能后发现截图较为困难。我偶然发现了 phantomjs,可以由服务端去做类似爬虫的操作来爬取html和保存图片,正好满足上述两个需求。

phantomjs

官方网站: phantomjs.org phantomjs 是 一个基于 webkit 内核的无头浏览器,没有 UI 界面。它就是一个浏览器,只是内部的点击、翻页等人为相关操作需要程序设计实现。

提供了 javascript API 接口,可以通过 js 直接与 webkit 内核交互,在此之上可以结合 Java 语言等,通过 Java 调用 js 等相关操作,从而解决了以前 c/c++ 才能比较好的基于 webkit 开发优质采集器的限制。

提供了 windows、linux、mac 等不同 OS 的安装使用包,也就是说可以在不同平台上,二次开发采集项目(爬虫)或是自动项目测试等工作。

常用内置对象

1
2
3
4
5
6
// 获得系统操作对象,包括命令行参数、phantomjs系统设置等信息
var system = require('system');
// 获取操作dom或web网页的对象,通过它可以打开网页、接收网页内容、request、response参数,其为最核心对象。
var page = require('webpage');
// 获取文件系统对象,通过它可以操作操作系统的文件操作,包括read、write、move、copy、delete等。
var fs = require('fs');

常用API

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 通过page对象通过url链接打开页面,加载完成后回调
page.open(url, function (status) {}
// 当page.open调用时,会首先执行该函数,在此可以预置一些参数或函数,用于后边的回调函数中
page.onLoadStarted = function() {}
// page的所要加载的资源在加载过程中,失败回调处理
page.onResourceError = function(resourceError) {}
// page的所要加载的资源在发起请求时,可以回调该函数
page.onResourceRequested = function(requestData, networkRequest) {}
// page的所要加载的资源在加载过程中,每加载一个相关资源,都会在此先做出响应,
// 它相当于http头部分,核心回调对象为response,可以获取本次请求的cookies、userAgent等
page.onResourceReceived = function(response) {}
// 打印一些输出信息到控制台
page.onConsoleMessage = function (msg) {}
// alert也是无法直接弹出的,但可以回调alert的内容
page.onAlert = function(msg) {}
// 当page.open时,http请求(不包括所引起的其它的加载资源)出现了异常,
// 如404、no route to web site等,都会在此回调显示。
page.onError = function(msg, trace) {}
// 当page.open打开的url,或者是在打开过程中进行了跳转,可以在这个函数中回调。
page.onUrlChanged = function(targetUrl) {}
// 当page.open的目标URL被真正打开后,会在调用open的回调函数前调用该函数,在此可以进行内部的翻页等操作
page.onLoadFinished = function(status) {}
// 在所加载的web page内部执行该函数,像翻页、点击、滑动等,均可在此中执行
page.evaluate(function(){});
// 将当前page的现状渲染成图片,输出到指定的文件中去。
page.render("");

调用方式

1
$ /bin/phantomjs [配置文件(js文件)] [参数1] [参数2] [参数3] ...

实现

java使用java.lang.Runtime调用,接收返回结果进行操作

config.js 配置文件内容

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
var page = require('webpage').create();
var system = require('system');
var ssionid = system.args[1]; 
var address = system.args[2]; // 请求地址,url
var output = system.args[3]; // 输出
var pageWidth = 1366; //初始化游览器宽
var pageHeight = 1080; //初始化游览器高
page.viewportSize = { width: pageWidth, height: pageHeight }; //初始化游览器

// 添加ssionid至cookie
phantom.addCookie({
  'name'     : 'JSESSIONID',  
  'value'    : ssionid,  
  'domain'   : 'job.chsi.com.cn',
  'path'     : '/'
});
//打开地址
page.open(address, function (status) {
    // 打开完毕
    if (status === "success") {
      // dom操作
        var rect = page.evaluate(function () {
            var html = document.getElementsByTagName('html');
            if(html && html.length > 0){
                if(html[0].innerHTML.indexOf('id="contMain"') < 0){
                    return null;
                }
                return {
                    offset : html[0].getBoundingClientRect(),
                    innerHTML :  html[0].innerHTML   
                };
            }
            return null;
        });
        if(!rect){
            console.log(output);
            page.close();
            phantom.exit();
            return;
        }
        console.log(rect.innerHTML); // 输出html
        page.clipRect = { //设置截取区域(此设置为全屏)
          top:0,
          left:0,
          width:rect.offset.width,
          height:rect.offset.height
        };
        window.setTimeout(function () {
          page.render(output); // 输出
          page.close(); // 关闭
          phantom.exit(); // 退出
        }, 200); 
    }
});

配置文件总结

  • output为输出方式, 为输出的绝对路径(例如:‘c:/123.jpg’), 以后缀决定文件格式. 建议使用pdf格式, pdf格式目前测试来看没有出现问题, 图片则出现过背景颜色丢失的情况
  • console.log(page.renderBase64(‘JPEG’)) 以Base64的格式输出至控制台
  • 这里是因为同时进行html爬取和截图快照, 所以不使用Base64的方式, 且输出的字符串有长度限制, 建议不要大量console.log(), 否则后面的内容可能会缺失
  • page.evaluate内可进行dom操作, 这里只取了html, 同理可有规则的针对爬取想要的页面信息
  • addCookie会返回一个boolean值, 有见到别人的文章判断了这个值才进行后续操作, 但我测试来看是有bug的, 即使成功也返回了false
  • 可以利用dom操作获取元素的位置及宽高对clipRect进行设置, 可达对想要的区域截图的目的, 例如截取某个div中的内容

java封装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public String phantomjs(String confPath, String url, String ssionId, String outPath) {
  StringBuilder sb = new StringBuilder();
  InputStream is = null;
  InputStreamReader isr = null;
  BufferedReader br = null;
  try {
    String cmd = "phantomjs " + confPath + " " + ssionId + " " + url + " " + outPath;
    Process ps = Runtime.getRuntime().exec(cmd);
    is = ps.getInputStream();
    isr = new InputStreamReader(is, "utf-8"); // 注意编码格式
    br = new BufferedReader(isr);
    String tmp;
    while ((tmp = br.readLine()) != null) {
      sb.append(tmp);
    }
  } catch (IOException e) {
    e.printStackTrace();
  } catch (Exception e) {
    e.printStackTrace();
  } finally {
    IOUtils.closeQuietly(br); // 注意关闭流
    IOUtils.closeQuietly(isr);
    IOUtils.closeQuietly(is);
  }
  String html = sb.toString();
  if (html.contains(filePath)) {
    return null;
  }
  return html;
}

java封装总结

  • 我这里直接使用phantomjs是因为设置了环境变量, 否则请使用文件的绝对路径
  • 编码格式尤为重要, 否则将出现html乱码问题

总结

phantomjs的功能非常强大,使用起来也是非常简单的。本文中只是一些简单的使用,更多的请自行查看官方API

致谢

这是我第一篇hexo文章,在Bingo phantomjs的文章中得到了大量素材与灵感,以此致谢。