现如今的爬虫再也不是简单的爬取静态页面,解析Html文本这么简单,许多单页面应用,异步请求调用,页面初始化js渲染等技术的使用,使得传统的通过发起http请求获得的Document无法直接使用。因此,基于实际业务需求,在爬取某电商平台数据时,发现其页面特定位置为js渲染,固此,由此一文,基于实际代码测试,分析HtmlUnit/Selenium/PhantomJs三类流行的js渲染引擎。 -HtmlUnit
|
1 2 3 4 5 | <code><code> 1 ) Seleninum 1 + WebDriver = Selenium 2 ) 基于本地安装的浏览器,需打开浏览器 3 ) 需要引用相应的WebDriver,正确配置webdriver的路径参数 4 ) 在爬取大量js渲染页面时明显不合适 </code></code> |
- PhantomJs
1 2 3 4 5 | <code><code><code> 1 ) 神器,短小精悍 2 ) 可本地化运行,也可作为服务端运行 3 ) 基于webkit内核,性能及表现良好 4 ) 完美解析绝大部分页面 </code></code></code> |
基于实测结果,在爬取大量任务时,推荐将PhantomJs作为服务端使用,此处,分别介绍本地及远程服务端使用例子(也可查看官网example)
本地
需要构造目标执行的js文件,利用命令行调用PhantomJS
示例:
window平台下
PhantomJs.exe target.js param1
对应的本地target.js可参考如下示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <code><code><code> "use strict" ; var page = require( 'webpage' ).create(); var system = require( 'system' ); if (system.args.length !== 2 ) { console.log( 'Usage: server.js <some port="">' ); phantom.exit( 1 ); } else { var url = system.args[ 1 ]; page.open(url, function (status) { console.log(page.content); phantom.exit(); } });</some></code></code></code> |
在java程序中,通过调用控制台执行命令
1 2 3 4 5 6 7 8 9 10 | <code><code><code> 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();</code></code></code> |
搭建远程服务器
保证远程服务器指定端口开启
示例:
在阿里ecs上开启指定端口,如3003
打开控制台,在安全组中添加自定义TCP连接,可访问的ip组设置为0.0.0.0/0,同时配置入网和出网端口
操作步骤
1) 官网下载exe文件至指定位置(linux平台同理)
2) 新建一个server.js文件
3) 命令行运行PhantomJS server.js即可开启服务
4) 本地通过在浏览器或者java代码中提交http请求,即可获得响应,url为 https://远程服务器ip地址:端口号/https://自定义url
此处server.js为关键,其设置了服务器的监听端口及响应请求逻辑
server.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 55 56 57 58 59 | <code><code><code> "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 <some port="">' ); 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("<title></title>"); // note: writeBody can be called multiple times // var url = "https://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 } }</some></code></code></code> |
提供本地发起请求Java代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <code><code><code> 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();</code></code></code> |
|