zoukankan      html  css  js  c++  java
  • PhantomJS 基础及示例 (转)

    概述

    PhantomJS is a headless WebKit scriptable with a JavaScript API. It has fast and native support for various web standards: DOM handling, CSS selector, JSON, Canvas, and SVG.(http://phantomjs.org/)

    PhantomJS 是一个无界面的webkit内核浏览器,你可以把它当作一个没有界面的 Safari。

    安装

    目前 PhantomJS 的最新版本的2.0,官方文档中有提到说:如果在使用老版本时碰到一些难解的 bug ,可以升级到最新版试试。

    windows

    直接下载 phantomjs-2.0.0-windows.zip ,并解压,将 bin 文件夹中的可执行文件phantomjs.exe的路径添加到环境变量后(可能需要重启机器才能生效),就可以在命令行环境(cmd 或 cygwin )中使用 phantomjs 命令执行 js 文件了。

    Linux

    安装二进制文件包

    可以在 Bitbucket 下载已经编译好的二进制文件安装包,不过目前 Linux 提供到 PhantomJS 1.9.8的安装包,最新的 PhantomJS 2.0还没有发布。
    安装方式:

    1. 下载phantomjs-1.9.8-linux-x86_64.tar.bz2
    2. 进入安装目录,解压二进制文件
    > cd /usr/local
    > tar zxvf phantomjs-1.9.8-linux-x86_64.tar.bz2
    
    1. 创建软链接mysql指向解压出来的文件夹,或将解压出来的文件夹重命名为phantomjs:
    > ln -sf phantomjs-1.9.8-linux-x86_64/bin/phantomjs phantomjs
    

    编译源码的方式

    由于 WebKit 模块中有数千个文件,因此由源码编译 PhantomJS 会花费很长的时间,文档上说,开四个并行的进程进行编译工作,需要超过30分钟的时间,因此官方文档推荐直接下载和安装二进制文件。

    具体的安装方法,这里就不再赘述,大家可以到官方文档上查看。

    是否安装成功

    我们可以使用下面的命令来查看 PhantomJS 是否安装成功:

    > phantomjs -v
    

    命令运行 phantomjs xxx.js即可执行一个 PhantomJS 程序。

    webpage 模块

    webpage 是 PhantomJS 的核心模块,你可以通过以下方式,获得一个 webpage 模块的实例:

    var webPage = require("webpage"),
        page = webPage.create();
    

    open()

    打开一个 url 链接,并加载对应的页面,一旦页面加载完成,就会触发回调,你也可以使用page.onLoadFinished方法来监听页面是否加载完成。下面,我们来用 open() 方法打开腾讯课堂

    var page = require("webpage").create;
    
    page.open("http://ke.qq.com", function(status) {
        if(status !== "success") {
            console.log("open fail!");
        }
        phantom.exit();
    });
    

    上面的代码中,open() 方法接受了两个参数。第一个参数是要打开网页的 url(要记得加协议头哦!),默认使用 GET 方法打开,第二个参数是回调参数,网页加载完成后该函数将会执行,它的参数status表示网页是否打开成功,打开成功就是success,否则就是fail。要注意的是,只要收到服务器返回的结果,status参数就是success,即使服务器返回的是404或500错误。

    我们也可以使用其他的http方法打开页面。

    var webPage = require("webpage");
    var page = webPage.create();
    var postBody = "user=username&password=password";
    
    page.open("http://www.google.com/", "POST", postBody, function(status) {
      console.log("Status: " + status);
      // Do other things here...
    });
    

    上面的代码是官方文档的事例,使用POST方法向服务器发送数据。open方法的第二个参数用来指定HTTP方法,第三个参数用来指定该方法所要使用的数据。

    从PhantomJS 1.9开始,我们还可以使用json对象来对http请求进行更详细的配置。

    var webPage = require('webpage');
    var page = webPage.create();
    var settings = {
      operation: "POST",
      encoding: "utf8",
      headers: {
        "Content-Type": "application/json"
      },
      data: JSON.stringify({
        some: "data",
        another: ["custom", "data"]
      })
    };
    
    page.open('http://your.custom.api', settings, function(status) {
      console.log('Status: ' + status);
      // Do other things here...
    });
    

    evaluate()

    在打开一个网页后,我们往往有对其进行操作的需求,例如模拟点击登陆按钮、获取某个DOM元素等等,也就是需要在页面中执行javascript代码,这时候我们就需要使用到evaluate()方法。

    // 获取打开页面的title
    var page = require('webpage').create();
    
    page.open(url, function(status) {
      var title = page.evaluate(function() {
        return document.title;
      });
      console.log('Page title is ' + title);
      phantom.exit();
    });
    

    由于因为evaluate()方法相当于一个沙盒,在其中是无法访问evaluate()之外的变量的。那如何将我想要获取的dom元素的id传进evaluate呢?

    从PhantomJS 1.6开始,我们可以将外部变量以如下的方式传给evaluate内部,需要注意的是,能传入evaluate方法内部的参数只能是简单的基本类型,例如数值、字符串、json对象等能被JSON序列化的类型,而无法接受更复杂的对象,它的返回值也同样如此。

    page.open('https://item.taobao.com/item.htm?id=520115087331', function(status) {
      var domId = "J_SellCounter"
      var sellCounter = page.evaluate(function(id) {
        return document.getElementById(id).innerText;
      }, domId);
    
      console.log(sellCounter);
      phantom.exit();
    
    });
    

    由于open()方法打开的网页内部的 console 语句,和 evaluate() 方法中的 console 语句都不会执行,给我们开发调试带来了不便。这时可以采用 onConsoleMessage 回调函数,来打印出上面两种情况中的 console 语句中的信息:

    var webPage = require('webpage');
    var page = webPage.create();
    
    page.onConsoleMessage = function(msg, lineNum, sourceId) {
      console.log('CONSOLE: ' + msg + ' (from line #' + lineNum + ' in "' + sourceId + '")');
    };
    

    其中 msg 是需要打印的信息,lineNum 和 sourceId 是 console.log 在文件中的行号以及这个文件对应的标识 id。

    includeJs()

    可以使用 includeJs()方法加载外部脚本,例如 jquery。

    var webPage = require('webpage');
    var page = webPage.create();
    
    page.open('http://www.example.com', function(status) {
        if(status !== "success") {
            console.log("open fail!");
        }
        page.includeJs('http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js', function() {
          page.evaluate(function() {
                // jQuery is loaded, now manipulate the DOM
              var $loginForm = $('form#login');
              $loginForm.find('input[name="username"]').value('phantomjs');
              $loginForm.find('input[name="password"]').value('c45p3r');
              $('#loginBtn').click();
          });
          phantom.exit();
        });
    })
    

    注意,由于includeJs是异步加载脚本,所以phantom.exit()需要放在page.includeJs()的回调函数中,否则phantomjs进程会过早退出。

    render()

    render() 可以将打开的网页截图并保存成本地图片,可以将指定的图片文件名作为参数传入,render 方法可以根据文件名的后缀将图片保存成对应的格式。目前支持PNGGIFJPEGPDF四种图片格式。

    var webPage = require('webpage');
    var page = webPage.create();
    
    page.viewportSize = {  1920, height: 1080 };
    page.open("http://www.google.com", function start(status) {
      page.render('google_home.jpeg', {format: 'jpeg', quality: '100'});
      phantom.exit();
    });
    

    该方法的第一个参数是保存的文件名,第二个可选参数是一个 JSON 对象,format 指定图片格式, quality 指定0-100区间内的图片质量,必须是整数。

    onResourceRequested

    当页面去请求一个资源时,会触发 onResourceRequested() 方法的回调函数。回调函数接受两个参数,第一个参数requestData是这个HTTP请求的元数据对象,包括以下属性:

    • id: 所请求资源的id号,这个应该是phantomjs给标识的。

    • method: 所使用的HTTP方法(GET/POST/PUT/DELETE等)。

    • url: 所请求资源的URL

    • time: 包含请求该资源时间的一个Date对象。

    • headers: 该请求的http请求头中的信息数组。

    第二个参数networkRequest包含以下方法:

    • abort(): 终止当前的网络请求,这会导致调用onResourceError回调函数。
    • changeUrl(newUrl):改变当前网络请求的URL。
    • setHeader(key, value):设置HTTP头信息。
    var webPage = require('webpage');
    var page = webPage.create();
    
    page.onResourceRequested = function(requestData, networkRequest) {
      console.log('Request (#' + requestData.id + '): ' + JSON.stringify(requestData));
    };
    
    page.open("http://ke.qq.com", function(status) {
        if(status) {
            console.log("fail!");
        }
        phantom.exit();
    });
    

    onResourceReceived

    onResourceReceived属性用于指定一个回调函数,当网页收到所请求的资源时,就会执行该回调函数。回调函数只有一个参数,就是所请求资源的服务器发来的HTTP response的元数据对象,包括以下字段。

    • id:所请求的资源编号,此编号phantomjs标识。

    • url:所请求的资源的URL 

    • time:包含HTTP回应时间的Date对象

    • headers:响应的HTTP头信息数组

    • bodySize:解压缩后的收到的内容大小

    • contentType:接到的内容种类

    • redirectURL:重定向URL(如果有的话)

    • stage:对于多数据块的HTTP回应,头一个数据块为start,最后一个数据块为end。

    • status:HTTP状态码,成功时为200。

    • statusText:HTTP状态信息,比如OK。

    需要注意的是,该方法收到的response对象是没有response.body的具体内容的。

    可以利用正则表达式,来筛选出我们想要操作的一些响应资源。比如我想从淘宝教育的课程详情页跳转到购买页(在淘宝网中),可以从淘宝同学请求的资源url中筛选出带淘宝网商品详情页的商品id,然后用这个淘宝网商品id拼接成一个淘宝网的商品详情页url,再次使用open()方法打开这个url,就可以跳转到该课程的购买页中。

    var page = require('webpage').create(),
        url1 = "http://i.xue.taobao.com/detail.htm?courseId=32679",
        url2 = "https://item.taobao.com/item.htm?id=",
        itemId = 0,
        mItem = "",
        siteType = "taobao";
    page.onConsoleMessage = function(msg) {
      console.log('console:  ' + msg);
    };
    page.onResourceReceived = function(response) {
        /*if(mItem = response.url.match(/^http://(?:.*)[?|&]item=(d*)/)) {
            itemId = mItem[1];
            console.log(itemId);
            phantom.exit();
        }*/
        // 获取课程对应的淘宝网商品id
        if(mItem = response.url.match(/itemId=(d*)/)) {
            itemId = parseInt(mItem[1]);
        }
    }
    page.open(url1, function(status) {
        if(status !== "success") {
            console.log("tongxue fail!");
            phantom.exit();
        }
        page.render("tongxue.png");
        // 打开课程对应的淘宝商品详情页。
        page.open(url2 + itemId, function(status) {
            if(status !== "success") {
                console.log("tongxue fail!");
                phantom.exit();
            }
            // 由于页面中的资源是动态加载的,需要setTimeout 10s 等待资源加载完,再操作页面。
            setTimeout(function() {
                var apply = page.evaluate(function() {
                    // 获取课程交易量
                    return document.getElementById("J_SellCounter").innerText;
                    //return document.getElementById("bd").innerHTML;
                });
                 console.log("apply:", apply);
                //fs.write("body.html", apply, "w");
                phantom.exit();
            }, 10000);
    
        });
    
    });
    

    小栗子

    动态获取淘宝商品详情页的商品交易量

    相信大家都知道爬虫的基本方式无非是抓取页面中的 url,然后分析;但是页面中的 url 也些是静态的,有些事通过js动态生成的,故爬虫也分抓静及抓动之分。

    因为淘宝商品详情页的交易量是异步拉取的,在异步数据还没有返回时,页面上交易量那一栏只是一个无意义的“-”,如图:

    当异步数据返回后,才会显示出真正的交易量:

    因此,

    var webPage = require('webpage');
    var page = webPage.create();
    var pageTb = webPage.create();
    var tbUrl = "https://item.taobao.com/item.htm?id=520115087331";
    
    
    page.settings.userAgent = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36";
    
    pageTb.open(tbUrl, function(status) {
    
        // 由于是拉取异步数据,我们打开页面后,等待12s再去操作dom,获取交易量
        setTimeout(function() {
            var result = pageTb.evaluate(function() {
                return document.getElementById("J_SellCounter").innerText;
            });
            console.log(result);
            //生成当前页面截图
            pageTb.render("xuqintb2.png");
            phantom.exit();
        }, 12000);
    });
    

    win7上执行命令:

    $ phantomjs.exe --ssl-protocol=any xuqinTb.js
    1379
    

    win7上得到了交易量(由于是打开https协议头的网页,所以执行js文件时,需要添加"--ssl-protocol=any"参数)

    PhantomJS不能做什么

    • PhantomJS是一个阉割版的webkit,不支持flash、webGL、video/audio、css 3-d,phontomjs不想背负操作系统强相关的特性,跨平台比较困难。
    • 如果使用Page模块的onResourceReceived()方法监听页面收到的请求资源,是无法得到该资源的response.body的,这也是目前PhantomJS最受开发者吐槽的点之一。

    原文链接:http://ivweb.io/topic/560b402ac2317a8c3e08621c

  • 相关阅读:
    使用反射调用某个类的成员方法
    java反射机制
    SVProgressHUD
    __objc__
    loadView
    v2ex 下拉刷新 SCRootViewController
    stuck with
    v2ex 下拉刷新模块
    转载
    vue-cli脚手架构建了项目如何去除Eslint验证(语法格式验证)
  • 原文地址:https://www.cnblogs.com/mafeng/p/7428766.html
Copyright © 2011-2022 走看看