首頁 >web前端 >js教程 >利用NodeJS和PhantomJS抓取網站頁面資訊以及網站截圖_javascript技巧

利用NodeJS和PhantomJS抓取網站頁面資訊以及網站截圖_javascript技巧

WBOY
WBOY原創
2016-05-16 17:14:351194瀏覽

利用PhantomJS做網頁截圖經濟適用,但其API較少,做其他功能就比較吃力了。例如,其自帶的Web Server Mongoose最高只能同時支援10個請求,指望他能獨立成為一個服務是不怎麼實際的。所以這裡需要另一個語言來支撐服務,這裡選用NodeJS來完成。

安裝PhantomJS

首先,到PhantomJS官網下載對應平台的版本,或是下載原始碼自行編譯。再將PhantomJS配置進環境變量,輸入

$ phantomjs

如果有反應,那麼就可以進行下一步了。

利用PhantomJS進行簡單截圖

複製代碼 代碼如下:
var webpage = require('webpage') , page = webpage.create(); page.viewportSize = { width: 1024, height: 800 }; page.clipRect = { top: 0, left: 0, width: page.clipRect = { top: 0, left: 0, width: 10024 , height: 800 }; page.settings = { javascriptEnabled: false, loadImages: true, userAgent: 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.31 (KHTML, like Gecko) PhantomJS/19. http://www.baidu.com', function (status) { var data; if (status === 'fail') { console.log('open page fail!'); } else { page.render(' ./snapshot/test.png'); } // release the memory page.close(); });

這裡我們設定了視窗大小為1024 * 800:

複製程式碼 程式碼如下:
page.viewportSize = { width: 1024, height: 800 };
程式碼如下:

程式碼如下: 程式碼如下:
截取從(0, 0)為起點的1024 * 800大小的圖片:

複製程式碼 程式碼如下:page.clipRect = { top: 0, left: 0, width: 1024, height: 800 };
禁止Javascript,允許圖片載入,並將userAgent改為"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.31 (KHTML, like Gecko) PhantomJS/19.0":

複製程式碼 程式碼如下: page.settings = { javascriptEnabled: false, loadImages: true, userAgent: 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit. 537.31 (KHTML, like Gecko) PhantomJS/19.0'};
然後利用page.open開啟頁面,最後截圖輸出到./snapshot/test.png中:

複製程式碼

複製程式碼複製程式碼
程式碼如下:

page.render('./snapshot/test.png') ;

 

NodeJS與PhantomJS通訊
我們先來看看PhantomJS能做什麼通訊。 命令列傳參 複製程式碼
程式碼如下:

例如:

phantomjs snapshot.js http://www.baidu.com
命令列傳參只能在PhantomJS開啟時進行傳參,在運作過程中就無能為力了。 標準輸出
複製程式碼

程式碼如下:

標準輸出能從PhantomJS輸出到NodeJS數據,但卻沒辦法從NodeJS傳數據給PhantomJS。
不過測試中,標準輸出是這幾種方式傳輸最快的,在大量資料傳輸中應考慮。  HTTP
複製程式碼

程式碼如下:

PhantomJS向NodeJS服務發出HTTP請求,然後NodeJS傳回對應的資料。 這種方式很簡單,但是請求只能由PhantomJS發出。 Websocket 複製程式碼 程式碼如下: 值得注意的是PhantomJS 1.9.0支援Websocket了,不過可惜是hixie-76 Websocket,但畢竟還是提供了一種NodeJS主動向PhantomJS通訊的方案了。 測試中,我們發現PhantomJS連上本地的Websocket服務居然需要1秒左右,暫時不考慮這種方法吧。 phantomjs-node
複製程式碼程式碼如下:

phantomjs-node成功將PhantomJS作為NodeJS的一個模組來使用,但我們看看作者的原理解釋:

我會用一個問題來回答這個問題。如何與不支援共享記憶體、套接字、FIFO 或標準輸入的進程通訊?

嗯,PhantomJS 確實支援一件事,那就是打開網頁。事實上,它在打開網頁方面確實很擅長。因此,我們透過啟動 ExpressJS 實例、在子進程中開啟 Phantom,並將其指向一個特殊網頁來與 PhantomJS 進行通信,該網頁將 socket.io 訊息轉換為 alert() 呼叫。這些alert()電話將由 Phantom 接聽,然後就可以了!

通信本身是透過James Halliday 出色的dnode 庫進行的,幸運的是,與browserify 結合使用時效果很好,可以直接在PhantomJS 的pidgin Javascript 環境中運行。

實際上phantomjs-node使用的也是HTTP或Websocket來進行通訊,不過其依賴龐大,我們不如做一個簡單的東西,暫時還是不考慮這個東東吧。

 

設計圖

 

讓我們開始吧
我們在第一版中採用HTTP進行實作。

先利用叢集進行簡單的進程監控(index.js):

複製代碼代碼如下:

module.exports = (function () {
"使用嚴格"
  var cluster = require('cluster')
    , fs = require('fs');

  if(!fs.existsSync('./snapshot')) {
    fs.mkdirSync('./snapshot');
  }

  if (cluster.isMaster) {
    cluster.fork();

    cluster.on('exit', function (worker) {
      console.log('Worker'worker.id '死了:(');
    . > cluster.fork();
      });
    })
  } else {
    require('./extract.js));
    require('./extract.js));
}
}

然後利用connect做我們的外部API(extract.js):

複製程式碼程式碼如下:

module.exports = (function () {
  "use strict"
  var connect = require('connect')
    , fs = require('connect')
    , fs = require('connect')
    , fs = require('connect')
    , fs = require('fs')    , jobMan = require('./lib/jobMan.js')    , Bridge = require('./lib/bridge.js')

   pkg = JSON.parse(fs.readFileSync('./package.json'));

  var app = connect()
    .use(connect.logger('dev'))
    .use('/snapshot', connect.static(__dirname '/snapshot', { maxAmaxAter pkg. maxAge }))
    .use(connect.bodyParser())
    .use('/bridge', 橋)
    .use('/api', function (req, res, next) {

      if (req.method !== "POST" || !req.body.campaignId) return next();

      if (!req.body.urls || !req.body. jobMan.watch(req.body.campaignId, req, res, next);

      var marketingId = req.body.campaignId
        , imagesPath = './snapshot , url
        , imagePath;

      function _deal(id, url, imagePath) {
        // 直接推入 url 名單
   🎜>          url: url,
          以影像路徑: imagePath
        });
      }

      for (var i = req.body.urls.length; i--;) {
        url = req.body.urls[i] ;
        imagePath = imagesPath i '.png';
        _deal(i, url, imagePath);
   _deal(i, url, imagePath);
   
      jobMan.register(campaignId, urls, req, res, next);

      var snapshot = spawn('phantomjs', ['shotps.js), marketps. 'data', function (data) {

        console.log('stdout: ' data);
      });
   (5); console.log('stderr: ' data);
      });
      snapshot.on('close', function (code) { ;
      });

    })
    .use(connect.static(__dirname '/html', { maxAge: pkg.maxAge }))
   : ' 'http://localhost:' pkg.port);

})();



這裡我們引用了兩首Modulebridge和jobMan。

其中bridge是HTTP通訊橋樑,jobMan是工作管理器。
通訊橋樑負責接受或返回作業的相關信息,並聯作業Man(bridge.js):

複製程式碼

程式碼如下:


module.exports = (function () {
  "use strict"
  var jobMan = require('./jobMan.js')
    , fs = require('')
    , pkg = JSON.parse(fs.readFileSync('./package.json'));

  return function (req, res, next) {
      if (req.headers.secret !== pkg.secret) return next();
 (req.method === "POST") {
        var body = JSON.parse(JSON.stringify(req.body));
     ');
      // 快照APP可以取得需要提取的url
      } else {
        var urls = jobMan.getUrls(req.m.Match(req.m.m. |&|$)/)[1]);
        res.writeHead(200, {'Content-Type': 'application/json'});
   end(JSON.stringify({ urls: urls }));
      }
  };

})();


如果請求方法為POST,則認為PhantomJS正在給我們主動job的相關資訊。而為GET時,則認為其要取得job的資訊。

jobMan負責管理作業,並發送目前得到的作業資訊透過回應傳回給客戶端(jobMan.js):

複製程式碼程式碼如下:

module.exports = (function () {
  "use strict"
  var fs = require('fs')
    , fetch = require('./fetch.js')
    , _jobs = {};

  function _send(campaignId){
    var job = _jobs[campaignId];
    if (!job) return;
    if (job.waiting) {
      job.waiting = false;
      clearTimeout(job.timeout);
      var finish = (job.urlsNum === job.finishNum)        urls: job.urls,
        完成:完成
      };
      job.urls = [];        刪除_jobs[campaignId]
      }
      res.writeHead(200, {'Content-類型': 'appli  res.writeHead(200, {'Content-Type': 'appli/json'}); 🎜>      res.end ( JSON.stringify(data));
    }
  }

  function register(campaignId, urls, req, res, next) {  : urls.length,
      finishNum: 0,
      urls: [],
      iting: false,
      timeout: null
} ;
    watch(campaignId, req, res, next);
  }

  function watch(campaignId, req, res, next) {
    _jobs[campaignId].res = res;
    // 20 秒逾時
 {
      _send(campaignId);
    }, 20000);
  }

  function fire(opts) {
    var CampaignId = opts.campaignId

      , job = _jobs[campaignId]  

    if (job) {
      if ( opts.status && fetchObj.title) {
      🎜>          url: opts.url,
          圖片:opts.image,
          標題:fetchObj.title,        });
     }其他{

job.urls.push({

          id: opts.id,
               });
      }

if(!🎜>      job.finishNum ;

    } else {
      console.log('作業
  function getUrls(campaignId) {
    var job = _jobs[campaignId];
    if (job) return job.cacheUrls;
   if (job) return job.cacheUrls;
 >
  return {
    註冊:註冊,
    觀看:觀看,
    火:火,
    getUrls:getUrls
})();



這裡我們用fetch對html進行抓取其標題與描述,fetch實作比較簡單(fetch.js):





複製程式碼


程式碼如下:


module.exports = (function () {
  "使用嚴格"

  回傳函數 (html) {
    if (!html) return { 標題: false, 說明: false };

    var title = html.match(/

(.*?)/)
      , meta = html.match(/; /g)
      ,描述;

    if (meta) {
      for (var i = meta.length; i--;) {
        if(meta[i].indexOf('name="description     if(meta[i].indexOf('name="description) | | 元[i].indexOf('name="描述"') > -1){
          說明= 元[i].match(/content="(.*?)"/)[1] ;
        }
      }
    }

    (標題&& 標題[1] !== '') ? (標題= 標題[1]) : (標題= '無標題');
    描述|| (說明= '無描述');

    return {
      標題:標題,
      說明:說明
    };
  }

;

    };
  }

;;

;

;

;

最後是PhantomJS運行的源代碼,其啟動後通過HTTP向bridge獲取job信息,然後每完成job的其中一個url就通過返回HTTP給bridge(snapshot.js): 複製程式碼程式碼如下:

var webpage = require('webpage')
  , args = require('system').args
  , fs = require('fs')
  , campaignId = args[1]
  , pkg = JSON.parse(fs.read('./package.json'));

function snapshot(id, url, imagePath) {
  var page = webpage.create()
    , send
    , begin
    , save
    , end;
  page.viewportSize = { width: 1024, height: 800 };
  page.clipRect = { top: 0, left: 0, width: 1024, height: 800 };
  page.settings = {
    javascriptEnabled: false,
    loadImages: true,
    userAgent: 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.31 (KHTML, like Gecko) PhantomJS/1.9.0'
  };
  page.open(url, function (status) {
    var data;
    if (status === 'fail') {
      data = [
        'campaignId=',
        campaignId,
        '&url=',
        encodeURIComponent(url),
        '&id=',
        id,
        '&status=',
      ].join('');
      postPage.open('http://localhost:' + pkg.port + '/bridge', 'POST', data, function () {});
    } else {
      page.render(imagePath);
      var html = page.content;
      // callback NodeJS
      data = [
        'campaignId=',
        campaignId,
        '&html=',
        encodeURIComponent(html),
        '&url=',
        encodeURIComponent(url),
        '&image=',
        encodeURIComponent(imagePath),
        '&id=',
        id,
        '&status=',
      ].join('');
      postMan.post(data);
    }
    // release the memory
    page.close();
  });
}

var postMan = {
  postPage: null,
  posting: false,
  datas: [],
  len: 0,
  currentNum: 0,
  init: function (snapshot) {
    var postPage = webpage.create();
    postPage.customHeaders = {
      'secret': pkg.secret
    };
    postPage.open('http://localhost:' + pkg.port + '/bridge?campaignId=' + campaignId, function () {
      var urls = JSON.parse(postPage.plainText).urls
        , url;

      this.len = urls.length;

if (this.len) {
for (var i = this.len; i--;) {
url = urls[i];
snapshot(url.id, url.url , url.imagePath);
}
}
});
this.postPage = postPage;
},
post: function (data) {
this.datas .push(data);
if (!this.posting) {
this.posting = true;
this.fire();
}
},
fire: function () {
if (this.datas.length) {
var data = this.datas.shift()
, that = this;
this.postPage.open('http:// localhost:' pkg.port '/bridge', 'POST', data, function () {
that.fire();
// 子プロセスを強制終了
setTimeout(function () {
if ( this.currentNum === this.len) {
that.postPage.close();
phantom.exit();
}
}, 500);
}) ;
} else {
this.posting = false;
}
}
};
postMan.init(snapshot);

效果

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn