NodeJS 및 PhantomJS를 사용하여 웹사이트 페이지 정보 및 웹사이트 스크린샷_javascript 기술 캡처

WBOY
풀어 주다: 2016-05-16 17:14:35
원래의
1178명이 탐색했습니다.

PhantomJS를 사용하여 웹 페이지 스크린샷을 만드는 것은 경제적이고 실용적이지만 API가 작아서 다른 기능을 수행하기가 더 어렵습니다. 예를 들어 자체 웹 서버 Mongoose는 동시에 최대 10개의 요청만 지원할 수 있으므로 독립적인 서비스가 되기를 기대하는 것은 실용적이지 않습니다. 따라서 서비스를 지원하려면 다른 언어가 필요하며 이를 완성하기 위해 NodeJS가 사용됩니다.

PhantomJS 설치

먼저 PhantomJS 공식 홈페이지에 접속해 플랫폼에 맞는 버전을 다운로드하거나, 소스코드를 다운로드해 직접 컴파일해 보세요. 그런 다음 환경 변수에 PhantomJS를 구성하고

$ phantomjs

를 입력합니다.

응답이 있으면 다음 단계로 진행하시면 됩니다.

PhantomJS를 사용하여 간단한 스크린샷 찍기

코드 복사 코드는 다음과 같습니다 :
var webpage = require('webpage') , page = webpage.create(); page.viewportSize = { 너비: 1024, 높이: 800 }; page.clipRect = { 위쪽: 0, 왼쪽: 0, 너비: 1024 , 높이: 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('페이지 열기 실패!'); } else { page.render(' ./snapshot/test.png') } // 메모리 해제 page.close() });
여기서 창 크기를 1024 * 800으로 설정했습니다.

코드 복사 코드는 다음과 같습니다.
page.viewportSize = { width: 1024, height: 800 };
로그인 후 복사
(0, 0)부터 시작하여 1024 * 800 크기의 이미지 가로채기:

코드 복사 코드는 다음과 같습니다. 다음:
page.clipRect = { 위쪽: 0, 왼쪽: 0, 너비: 1024, 높이: 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 (Gecko와 같은 KHTML) 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에서만 요청을 할 수 있습니다.

웹소켓
코드 복사 코드는 다음과 같습니다.
PhantomJS 1.9.0이 Websocket을 지원한다는 점은 주목할 만하지만 아쉽게도 hixie-76 Websocket이지만 결국 NodeJS가 PhantomJS와 적극적으로 통신할 수 있는 솔루션을 제공하고 있습니다.

테스트 중에 PhantomJS가 로컬 Websocket 서비스에 연결하는 데 실제로 약 1초가 걸리는 것으로 나타났습니다. 당분간 이 방법은 고려하지 않을 것입니다.

phantomjs-node
复主代码 代码如下:
그 질문에는 질문으로 답하겠습니다. 공유 메모리, 소켓, FIFO 또는 표준 입력을 지원하지 않는 프로세스와 어떻게 통신합니까?

PhantomJS가 지원하는 한 가지 기능이 있는데, 바로 웹페이지를 여는 것입니다. 사실, 웹 페이지를 여는 데는 정말 좋습니다. 따라서 ExpressJS 인스턴스를 가동하고, 하위 프로세스에서 Phantom을 열고, 소켓.io 메시지를 호출로 변환하는 특수 웹페이지를 가리켜 PhantomJS와 통신합니다.

 전화를 Phantom이 수신하면 끝입니다!alert()alert()

커뮤니케이션 자체는 James Halliday의 환상적인

dnode 라이브러리를 통해 이루어집니다. 다행히도 이 라이브러리는 browserify와 결합하여 PhantomJS의 pidgin Javascript 환경에서 바로 실행할 수 있을 만큼 잘 작동합니다.

 

设计图

 

让我们开始吧

我们在第一版中选用HTTP进行实现。


首先利용 클러스터进行简单的进程守护(index.js):

复主代码 代码如下:module.exports = (함수 () {
"엄격한 사용"
  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' 작업자.id ' 사망 :(');

      process.nextTick(function () {

Cluster.fork();
      });
    })
  } else {
    require('./extract.js');
  }
})();



然后利用connect做我们의 외부 API(extract.js):

复主代码 代码如下:

module.exports = (function () {
  "엄격한 사용"
  var connect = require('connect')
    , fs = require('fs')
    , generate = 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
        ,imagePath = './snapshot/'campaignId '/'
        , urls = []
        , url
        , imagePath;

      function _deal(id, url, imagePath) {
        // URL 목록에 푸시합니다
        urls.push({
          id: id,
          url,
          이미지 경로: imagePath
        });
      }

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

      jobMan.register(campaignId, urls, req, res, next);
      var snapshot = generate('phantomjs', ['snapshot.js', CampaignId]);
      snapshot.stdout.on( 'data', 함수(데이터) {
        console.log('stdout: ' data);
      });
      snapshot.stderr.on('data', 함수(데이터) {
console.log('stderr: ' data);
      });
      snapshot.on('close', function (code) {
        console.log('스냅샷이 코드 ' code로 종료되었습니다);
      });

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

})();

저희는 교량과 직업을 가지고 있습니다.

其中bridge是HTTP讯桥梁,jobMan是工work管理器。我们通过campaignId来对应一个job,然后将job와response委托给jobMan管리。然后启动PhantomJS进行处리。

일반적으로 讯桥梁负责接受或者返回job的关信息,并交给jobMan(bridge.js):

复主代码 代码如下:

module.exports = (function () {
  "엄격한 사용"
  var jobMan = require('./jobMan.js')
    , fs = require('fs')
    , pkg = JSON.parse(fs.readFileSync('./package.json'));

  반환 함수(req, res, next) {
      if (req.headers.secret !== pkg.secret) return next();
      // 스냅샷 앱은 URL 정보를 게시할 수 있습니다
      if (req.method === "POST") {
        var body = JSON.parse(JSON.stringify(req.body));
        jobMan.fire(body);
        res.end(' ');
      // 스냅샷 앱은 추출해야 하는 URL을 얻을 수 있습니다
      } 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 }));
      }
  };

})();

요청 방법은 POST, 则我们认为PhantomJS正에서 我们推送job적상关信息。而为GET时,则认为其要获取job적信息。

jobMan负责管理job,并发送目前得到的job信息通过response返回给client(jobMan.js):

复主代码 代码如下:

module.exports = (function () {
  "엄격한 사용"
  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;        완료됨: 완료됨
      };
      job.urls = [];
      var res = job.res;
      if (완료됨) {
        _jobs[campaignId] = null;
        delete _jobs[campaignId]
      }
      res.writeHead(200, {'Content-Type': 'application/json'});
      res.statuCode = 200;
      res.end( JSON.stringify(data));
    }
  }

  함수 레지스터(campaignId, urls, req, res, next) {
    _jobs[campaignId] = {
      urlsNum: urls.length,
      끝번호: 0,
      urls: [],
      캐시Urls: urls,
      res: null,
      대기: false,
      시간 초과: null
    } ;
    watch(campaignId, req, res, next);
  }

  function watch(campaignId, req, res, next) {
    _jobs[campaignId].res = res;
    // 20초 제한 시간
    _jobs[campaignId].timeout = setTimeout(function () {

      _send(campaignId);

    }, 20000);
  }

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

    if (직업) {
      if ( opts.status && fetchObj.title) {
        job.urls.push({
          id: opts.id,

          url: opts.url,

          이미지: opts.image,
          제목: fetchObj.title,
          설명: fetchObj.description,
          상태: opts.status
       });
     } 그 밖의 {
job.urls.push({
          id: opts.id,
          url: opts.url,
          상태: opts.status
       });
      }

      if (!job.waiting) {
        job.waiting = true;
        setTimeout(function () {
         _send(campaignId);

       }, 500);

      }
      job.finishNum ;
    } else {
      console.log('작업을 찾을 수 없습니다!');
    }
  }

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

  return {
    등록: 등록,
    watch: watch,
    fire: fire,

    getUrls: getUrls

  };

})();


这里我们较简单(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"') > -1 || 메타[i].indexOf('name="Description"') > -1){
          설명 = 메타[i].match(/content="(.*?)"/)[1] ;
        }
      }
    }

    (제목 && 제목[1] !== '') ? (제목 = 제목[1]) : (제목 = '제목 없음');
    설명 || (설명 = '설명 없음');

    return {
      제목: 제목,
      설명: 설명
    };
  };

})();

PhantomJS는 url就过HTTP返回给bridge(snapshotcommunications.js)를 사용하여 PhantomJS를 실행하고 있습니다.

复主代码 代码如下:

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;
  },
  게시물: 함수(데이터) {
    this.datas .push(data);
    if (!this.posting) {
      this.posting = true;
      this.fire();
    }
  },
  화재: 기능 () {
    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(스냅샷);

效果

 

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 이슈
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿