亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

利用NodeJS和PhantomJS抓取網站頁面信息以及網站截圖

2019-11-20 21:40:26
字體:
來源:轉載
供稿:網友
利用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: 1024, height: 800 }; 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('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的一個模塊來使用,但我們看看作者的原理解釋:

I will answer that question with a question. How do you communicate with a process that doesn't support shared memory, sockets, FIFOs, or standard input?

Well, there's one thing PhantomJS does support, and that's opening webpages. In fact, it's really good at opening web pages. So we communicate with PhantomJS by spinning up an instance of ExpressJS, opening Phantom in a subprocess, and pointing it at a special webpage that turns socket.io messages into alert()calls. Those alert() calls are picked up by Phantom and there you go!

The communication itself happens via James Halliday's fantastic dnode library, which fortunately works well enough when combined with browserify to run straight out of PhantomJS's pidgin Javascript environment.

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

 

設計圖

 

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

首先利用cluster進行簡單的進程守護(index.js):

復制代碼 代碼如下:

module.exports = (function () {
  "use strict"
  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 + ' died :(');
      process.nextTick(function () {
        cluster.fork();
      });
    })
  } else {
    require('./extract.js');
  }
})();

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

復制代碼 代碼如下:

module.exports = (function () {
  "use strict"
  var connect = require('connect')
    , fs = require('fs')
    , spawn = require('child_process').spawn
    , 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', { maxAge: pkg.maxAge }))
    .use(connect.bodyParser())
    .use('/bridge', bridge)
    .use('/api', function (req, res, next) {
      if (req.method !== "POST" || !req.body.campaignId) return next();
      if (!req.body.urls || !req.body.urls.length) return jobMan.watch(req.body.campaignId, req, res, next);

      var campaignId = req.body.campaignId
        , imagesPath = './snapshot/' + campaignId + '/'
        , urls = []
        , url
        , imagePath;

      function _deal(id, url, imagePath) {
        // just push into urls list
        urls.push({
          id: id,
          url: url,
          imagePath: imagePath
        });
      }

      for (var i = req.body.urls.length; i--;) {
        url = req.body.urls[i];
        imagePath = imagesPath + i + '.png';
        _deal(i, url, imagePath);
      }

      jobMan.register(campaignId, urls, req, res, next);
      var snapshot = spawn('phantomjs', ['snapshot.js', campaignId]);
      snapshot.stdout.on('data', function (data) {
        console.log('stdout: ' + data);
      });
      snapshot.stderr.on('data', function (data) {
        console.log('stderr: ' + data);
      });
      snapshot.on('close', function (code) {
        console.log('snapshot exited with code ' + code);
      });

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

})();

這里我們引用了兩個模塊bridge和jobMan。

其中bridge是HTTP通訊橋梁,jobMan是工作管理器。我們通過campaignId來對應一個job,然后將job和response委托給jobMan管理。然后啟動PhantomJS進行處理。

通訊橋梁負責接受或者返回job的相關信息,并交給jobMan(bridge.js):

復制代碼 代碼如下:

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

  return function (req, res, next) {
      if (req.headers.secret !== pkg.secret) return next();
      // Snapshot APP can post url information
      if (req.method === "POST") {
        var body = JSON.parse(JSON.stringify(req.body));
        jobMan.fire(body);
        res.end('');
      // Snapshot APP can get the urls should extract
      } else {
        var urls = jobMan.getUrls(req.url.match(/campaignId=([^&]*)(/s|&|$)/)[1]);
        res.writeHead(200, {'Content-Type': 'application/json'});
        res.statuCode = 200;
        res.end(JSON.stringify({ urls: urls }));
      }
  };

})();

如果request method為POST,則我們認為PhantomJS正在給我們推送job的相關信息。而為GET時,則認為其要獲取job的信息。

jobMan負責管理job,并發送目前得到的job信息通過response返回給client(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 finished = (job.urlsNum === job.finishNum)
        , data = {
        campaignId: campaignId,
        urls: job.urls,
        finished: finished
      };
      job.urls = [];
      var res = job.res;
      if (finished) {
        _jobs[campaignId] = null;
        delete _jobs[campaignId]
      }
      res.writeHead(200, {'Content-Type': 'application/json'});
      res.statuCode = 200;
      res.end(JSON.stringify(data));
    }
  }

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

  function watch(campaignId, req, res, next) {
    _jobs[campaignId].res = res;
    // 20s timeout
    _jobs[campaignId].timeout = setTimeout(function () {
      _send(campaignId);
    }, 20000);
  }

  function fire(opts) {
    var campaignId = opts.campaignId
      , job = _jobs[campaignId]
      , fetchObj = fetch(opts.html);

    if (job) {
      if (+opts.status && fetchObj.title) {
        job.urls.push({
          id: opts.id,
          url: opts.url,
          image: opts.image,
          title: fetchObj.title,
          description: fetchObj.description,
          status: +opts.status
        });
      } else {
        job.urls.push({
          id: opts.id,
          url: opts.url,
          status: +opts.status
        });
      }

      if (!job.waiting) {
        job.waiting = true;
        setTimeout(function () {
          _send(campaignId);
        }, 500);
      }
      job.finishNum ++;
    } else {
      console.log('job can not found!');
    }
  }

  function getUrls(campaignId) {
    var job = _jobs[campaignId];
    if (job) return job.cacheUrls;
  }

  return {
    register: register,
    watch: watch,
    fire: fire,
    getUrls: getUrls
  };

})();

這里我們用到fetch對html進行抓取其title和description,fetch實現比較簡單(fetch.js):

復制代碼 代碼如下:

module.exports = (function () {
  "use strict"

  return function (html) {
    if (!html) return { title: false, description: false };

    var title = html.match(//<title/>(.*?)/<//title/>/)
      , meta = html.match(//<meta/s(.*?)//?/>/g)
      , description;

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

    (title && title[1] !== '') ? (title = title[1]) : (title = 'No Title');
    description || (description = 'No Description');

    return {
      title: title,
      description: description
    };
  };

})();

最后是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();
        // kill child process
        setTimeout(function () {
          if (++this.currentNum === this.len) {
            that.postPage.close();
            phantom.exit();
          }
        }, 500);
      });
    } else {
      this.posting = false;
    }
  }
};
postMan.init(snapshot);

效果

 

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产精品日日摸夜夜添夜夜av| 亚洲精品之草原avav久久| 日韩成人黄色av| 久久久噜久噜久久综合| 欧美一级大片在线免费观看| 色香阁99久久精品久久久| 精品国内产的精品视频在线观看| 不卡av日日日| 欧美激情一区二区久久久| 久久免费国产视频| 欧美床上激情在线观看| 欧美精品亚州精品| 亚洲女人天堂色在线7777| 亚洲成人激情在线| 国产欧美在线观看| 91亚洲人电影| 久久久久国产精品www| 日韩在线高清视频| 亚洲国产精品一区二区三区| 美女999久久久精品视频| 日韩av在线电影网| 亚洲国产精品久久久| 69视频在线播放| 亚洲综合中文字幕在线观看| 欧美成人精品激情在线观看| 久久久久国产精品一区| 夜夜嗨av一区二区三区四区| 国内精品免费午夜毛片| 久热99视频在线观看| 欧美国产极速在线| 久久999免费视频| 国产精品www色诱视频| 欧美刺激性大交免费视频| 欧美电影在线免费观看网站| 日韩av在线影视| 91日韩在线视频| 国产精品视频内| 国产精品专区h在线观看| 久久久精品中文字幕| 日本亚洲欧洲色α| 久久久久女教师免费一区| 久久九九精品99国产精品| 成人黄色午夜影院| 久久久在线观看| 97国产一区二区精品久久呦| 国产激情久久久久| 欧美午夜视频在线观看| 青青a在线精品免费观看| 亚洲精品国产精品国产自| 黑人欧美xxxx| 精品久久久久久中文字幕| 日韩电影视频免费| 国产成一区二区| 亚洲精品综合久久中文字幕| 久久久精品一区二区三区| 日韩综合中文字幕| 日韩电影免费观看中文字幕| 国产精品视频在线播放| 在线观看国产精品91| 欧美激情网站在线观看| 日韩精品免费在线视频| 久久久久久久久久国产| 96pao国产成视频永久免费| 国产日产亚洲精品| 亚洲色无码播放| 欧美极品美女视频网站在线观看免费| 日韩有码在线视频| 91精品久久久久久久久| 精品视频在线导航| 一个人看的www欧美| 韩国欧美亚洲国产| 欧美极品少妇全裸体| 欧美激情视频三区| 日韩影视在线观看| 日韩美女av在线| 97不卡在线视频| 国产精品99久久久久久久久| 国产成人激情小视频| 亚洲欧美激情视频| 色播久久人人爽人人爽人人片视av| 国产精品欧美亚洲777777| 久久精品国产精品亚洲| 久久91精品国产91久久跳| 国内偷自视频区视频综合| 中文字幕日韩在线观看| 国产精品看片资源| 国产精品久久久久久久久男| 中文字幕亚洲一区| 97免费中文视频在线观看| 91免费看片在线| 97国产精品久久| 91牛牛免费视频| 国外日韩电影在线观看| 亚洲午夜av久久乱码| 在线亚洲男人天堂| 欧美日韩aaaa| 日韩女优在线播放| 久久国产精品偷| 青青草99啪国产免费| 亚洲精品av在线| 91精品国产91| 国产日产欧美精品| 国产成人午夜视频网址| 久久免费视频这里只有精品| 久久久久国产一区二区三区| 欧美理论电影在线观看| 九九视频直播综合网| 欧美成aaa人片免费看| 欧美中文字幕在线播放| 日本一欧美一欧美一亚洲视频| 亚洲18私人小影院| 亚洲女在线观看| 日韩av综合中文字幕| 久久综合久中文字幕青草| 欧美性极品xxxx娇小| 国产97在线播放| 日韩免费观看在线观看| 欧美巨乳美女视频| 亚洲欧美综合另类中字| 亚洲视频免费一区| 91sao在线观看国产| 国产69精品久久久久99| 精品少妇v888av| 亚洲日韩第一页| 成年无码av片在线| 国产精品爱久久久久久久| 91精品久久久久久久久久| 久久99久久99精品免观看粉嫩| 国产不卡精品视男人的天堂| 亚洲精品欧美日韩| 97久久精品人搡人人玩| 91在线精品播放| 欧美夫妻性视频| 日本精品视频在线播放| 欧美洲成人男女午夜视频| 久久久人成影片一区二区三区| 国产精品狼人色视频一区| 亚洲国产古装精品网站| 久久99国产精品自在自在app| 91精品国产综合久久香蕉最新版| 日本伊人精品一区二区三区介绍| 黄网动漫久久久| 亚洲第一页在线| 国产国语videosex另类| 欧美一区二粉嫩精品国产一线天| 性欧美办公室18xxxxhd| 日韩精品中文字幕久久臀| 欧美裸身视频免费观看| 国产精品日韩在线| 欧美国产视频日韩| 久久久精品久久久久| 亚洲最新av在线| 国产精品看片资源| 欧美一级在线播放| 一区二区三区黄色| 亚洲欧美日韩天堂| 两个人的视频www国产精品| www.亚洲天堂| 日本亚洲欧美三级| 亚洲无亚洲人成网站77777| 亚洲www永久成人夜色| 红桃视频成人在线观看| 欧美老女人xx| 亚洲精品久久久久久久久|