JS渲染引擎比较HtmlUnit/Selenium/PhantomJs

JS渲染引擎比较HtmlUnit/Selenium/PhantomJs

现如今的爬虫再也不是简单的爬取静态页面,解析Html文本这么简单,许多单页面应用,异步请求调用,页面初始化js渲染等技术的使用,使得传统的通过发起http请求获得的Document无法直接使用。因此,基于实际业务需求,在爬取某电商平台数据时,发现其页面特定位置为js渲染,固此,由此一文,基于实际代码测试,分析HtmlUnit/Selenium/PhantomJs三类流行的js渲染引擎。

-HtmlUnit

1) 内置Rhinojs浏览器引擎,没有哪一款浏览器使用该内核
2) 解析速度一般
3) 解析JS/CSS差
4) 无浏览器界面

- Selenium

1) Seleninum 1+ WebDriver = Selenium
2) 基于本地安装的浏览器,需打开浏览器
3) 需要引用相应的WebDriver,正确配置webdriver的路径参数
4) 在爬取大量js渲染页面时明显不合适

- PhantomJs

1) 神器,短小精悍
2) 可本地化运行,也可作为服务端运行
3) 基于webkit内核,性能及表现良好
4) 完美解析绝大部分页面

基于实测结果,在爬取大量任务时,推荐将PhantomJs作为服务端使用,此处,分别介绍本地及远程服务端使用例子(也可查看官网example)

本地

需要构造目标执行的js文件,利用命令行调用PhantomJS
示例:
window平台下
PhantomJs.exe target.js param1

对应的本地target.js可参考如下示例:

"use strict";
var page = require('webpage').create();
var system = require('system');
if (system.args.length !== 2) {
    console.log('Usage: server.js ');
    phantom.exit(1);
} else {
    var url = system.args[1];
    page.open(url, function (status) {
            console.log(page.content);
            phantom.exit();
            }
        });

在java程序中,通过调用控制台执行命令

        Runtime runtime = Runtime.getRuntime();
        Process p = runtime.exec("D:/phantomjs.exe target.js url);
        InputStream is = p.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        StringBuffer sb = new StringBuffer();
        String tmp = "";
        while((tmp = br.readLine())!=null){
            sb.append(tmp);
        }
        return sb.toString();

搭建远程服务器

保证远程服务器指定端口开启
示例:
在阿里ecs上开启指定端口,如3003
打开控制台,在安全组中添加自定义TCP连接,可访问的ip组设置为0.0.0.0/0,同时配置入网和出网端口

操作步骤

1) 官网下载exe文件至指定位置(linux平台同理)
2) 新建一个server.js文件
3) 命令行运行PhantomJS server.js即可开启服务
4) 本地通过在浏览器或者java代码中提交http请求,即可获得响应,url为 http://远程服务器ip地址:端口号/http://自定义url

此处server.js为关键,其设置了服务器的监听端口及响应请求逻辑
server.js示例代码:

"use strict";
var page = require('webpage').create();
var server = require('webserver').create();
var system = require('system');
var host, port;

if (system.args.length !== 2) {
    console.log('Usage: server.js ');
    phantom.exit(1);
} else {
    port = system.args[1];
    var listening = server.listen(port, function (request, response) {

        console.log("GOT HTTP REQUEST");
        console.log(JSON.stringify(request, null, 4));

        // we set the headers here
        response.statusCode = 200;
        response.headers = {"Cache": "no-cache", "Content-Type": "text/html"};
        // this is also possible:
        response.setHeader("databee", "databee");
        // now we write the body
        // note: the headers above will now be sent implictly
        //response.write("YES!");
        // note: writeBody can be called multiple times
       // var url = "http://www.baidu.com";
        var url = request.url;
        url = url.substring(1);//获得的url较为奇怪,根据request的内容进行url改造成合规url
        page.open(url, function (status) {
            if (status !== 'success') {
                response.statusCode = 403;
                 response.headers = {
                    'Cache': 'no-cache',
                    'Content-Type': 'text/html'
                };
                response.write("FAIL");
                response.close();
                console.log('FAIL to load the address');
            } else {
                response.statusCode = 200;
                response.headers = {
                    'Cache': 'no-cache',
                    'Content-Type': 'text/html'
                };
                //console.log(page.content)
                response.write(page.content);
                response.close();//response.close()表明响应结束,必须加入
                console.log('Send success');
            }
        });

       //response.close();
    });
    if (!listening) {
        console.log("could not create web server listening on port " + port);
        phantom.exit();//代表退出phantom
    }

}

提供本地发起请求Java代码示例

        URL url = new URL(finalUrl);//finalUrl此时为get请求url
        HttpURLConnection conn = (HttpURLConnection)url.openConnection();
        InputStream is = null;
        BufferedReader br = null;
        if (conn.getResponseCode() == 200) {
            is = conn.getInputStream();
        } else {
            is = conn.getErrorStream();
        }
        br = new BufferedReader(new InputStreamReader(is));
        String line = "";
        StringBuilder sb = new StringBuilder();

        while ((line = br.readLine()) != null) {
            sb.append(line);
        }
        return sb.toString();

总结

先了解,后使用 在未深入了解三款js渲染引擎时,走了许多弯路,尤其是在HtmlUnit的使用上,发现经常性报内存溢出,js/css渲染错误等各种问题

先官网,后博客 很多问题往往在官网上能够得到解答,尤其是官网上提供了响应的demo或者example说明,仔细阅读提供的示例代码,及时在没有注释说明的前提下,通过阅读代码,也能知其大致用法

先业务,后工具 工具往往只是解决问题的一个手段和方法,如果对自身业务不了解,盲目使用工具,势必得不偿失。许多工具有其自身特点,如何正确使用,往往取决于自身的业务场景于需求。因此,凡是无绝对,没有最好,只有更好;没有最差,只有更差。

你可能感兴趣的:(【爬虫】)