2012-04-01 37 views
65

Tôi đang lập kế hoạch sử dụng dịch vụ web để sử dụng nội bộ, lấy một đối số, URL và trả về html đại diện cho giải quyết DOM từ URL đó. Bằng cách giải quyết tôi có nghĩa là webservice trước tiên sẽ nhận được trang tại URL đó, sau đó sử dụng PhantomJS để 'render' trang, và sau đó trả về nguồn kết quả sau khi tất cả các cuộc gọi DHTML, AJAX vv được thực hiện. Tuy nhiên, khởi chạy phantom trên cơ sở theo yêu cầu (mà tôi đang làm bây giờ) là cách quá chậm chạp. Tôi thà có một nhóm các phiên bản PhantomJS với một phiên bản luôn có sẵn để phục vụ cuộc gọi mới nhất tới dịch vụ web của tôi.Cách quản lý 'hồ bơi' của các phiên bản PhantomJS

Có công việc nào được thực hiện trên loại điều này trước đây không? Tôi muốn cơ sở này webservice trên công việc của người khác hơn là viết một quản lý hồ bơi/máy chủ proxy http cho bản thân mình từ đầu.

Bối cảnh khác: Tôi đã liệt kê 2 dự án tương tự mà tôi đã thấy từ trước đến nay và tại sao tôi tránh từng dự án, dẫn đến câu hỏi này về quản lý một nhóm phiên bản PhantomJS.

jsdom - từ những gì tôi thấy nó có chức năng tuyệt vời để thực thi tập lệnh trên trang nhưng không cố gắng sao chép hành vi của trình duyệt, vì vậy nếu tôi sử dụng nó làm mục đích chung "Trình phân giải DOM" ở đó d cuối cùng là rất nhiều mã hóa để xử lý tất cả các loại cạnh trường hợp, gọi sự kiện, vv Ví dụ đầu tiên tôi thấy là phải tự gọi hàm onload() của thẻ body cho ứng dụng thử nghiệm mà tôi đã thiết lập bằng nút . Nó có vẻ giống như sự bắt đầu của một lỗ thỏ sâu.

Selenium - Nó chỉ có nhiều bộ phận chuyển động hơn, vì vậy việc thiết lập một hồ bơi để quản lý các phiên bản trình duyệt tồn tại lâu dài sẽ phức tạp hơn việc sử dụng PhantomJS. Tôi không cần bất kỳ lợi ích ghi/ghi macro nào. Tôi chỉ muốn một webservice có vai trò như một người biểu diễn tại trang web và giải quyết DOM như thể tôi đang duyệt đến URL đó bằng trình duyệt (hoặc thậm chí nhanh hơn nếu tôi có thể làm cho nó bỏ qua hình ảnh, v.v.)

Trả lời

17

async JavaScript library hoạt động Node và có một chức năng queue đó là khá tiện dụng cho các loại hình điều:

queue(worker, concurrency)

Creates a queue object with the specified concurrency. Tasks added to the queue will be processed in parallel (up to the concurrency limit). If all workers are in progress, the task is queued until one is available. Once a worker has completed a task, the task's callback is called.

một số giả:

function getSourceViaPhantomJs(url, callback) { 
    var resultingHtml = someMagicPhantomJsStuff(url); 
    callback(null, resultingHtml); 
} 

var q = async.queue(function (task, callback) { 
    // delegate to a function that should call callback when it's done 
    // with (err, resultingHtml) as parameters 
    getSourceViaPhantomJs(task.url, callback); 
}, 5); // up to 5 PhantomJS calls at a time 

app.get('/some/url', function(req, res) { 
    q.push({url: params['url_to_scrape']}, function (err, results) { 
    res.end(results); 
    }); 
}); 

Kiểm tra các entire documentation for queue at the project's readme.

+0

Bạn có biết cách thức hoạt động xếp hàng chi tiết? Tôi nghĩ rằng nó gọi nhiều yêu cầu XHR trong hàng đợi phải không?Tôi đang tìm một giải pháp thực sự giữ các tiến trình ảo hóa chạy như một daemon, thay vì xoay từng bước một nhiệm vụ đi vào. – CMCDragonkai

+0

@CMCDragonkai Câu hỏi đề cập đến "một nhóm các trường hợp PhantomJS với một trường hợp luôn sẵn sàng để phục vụ cuộc gọi mới nhất tới dịch vụ web của tôi ", ngụ ý liên tục chạy các trình tiện ích PhantomJS, nhưng câu trả lời này sẽ hoạt động với cả hai trường hợp. Tất cả các chức năng 'async.queue' làm là đảm bảo không có nhiều hơn một số lượng nhất định các cuộc gọi đến chức năng là xuất sắc tại bất kỳ thời điểm nào; những gì bạn làm bên trong chức năng đó là tùy thuộc vào bạn. –

+2

Bạn của tôi, gần 4 năm sau, đã cứu tôi khá đau đầu. – mgmcdermott

0

Nếu bạn đang sử dụng nodej, bạn có thể sử dụng https://github.com/sgentle/phantomjs-node, điều này sẽ cho phép bạn kết nối một số quá trình ảo ảnh tùy ý với quy trình NodeJS chính của bạn, do đó, khả năng sử dụng async.js và nhiều tiện ích nút.

+0

Điều này không đúng. Nếu bạn tạo nhiều hơn một thể hiện của JS ảo và chạy chúng cùng một lúc bạn nhận được 'Lỗi: nghe EADDRINUSE'. Im hiện đang tìm kiếm một cách để đưa các thể hiện ảo trên các cổng khác nhau hoặc bất cứ điều gì gây ra EADDRINUSE. – RachelC

+1

Tất nhiên, trách nhiệm của bạn là bắt đầu các bản sao ảo để chúng nghe trên một cổng khác. –

61

Tôi thiết lập Dịch vụ đám mây PhantomJs và khá nhiều điều bạn đang yêu cầu. Nó đã cho tôi khoảng 5 tuần thực hiện công việc.

Vấn đề lớn nhất bạn gặp phải là sự cố đã biết của memory leaks in PhantomJs. Cách tôi làm việc xung quanh điều này là để chu kỳ trường hợp của tôi mỗi 50 cuộc gọi.

Vấn đề lớn thứ hai mà bạn gặp phải là xử lý trên mỗi trang là rất cpu và bộ nhớ chuyên sâu, vì vậy bạn sẽ chỉ có thể chạy 4 hoặc nhiều trường hợp trên mỗi CPU.

Vấn đề lớn thứ ba mà bạn gặp phải là PhantomJs khá là lập dị với các sự kiện kết thúc trang và chuyển hướng. Bạn sẽ được thông báo rằng trang của bạn đã hoàn tất hiển thị trước khi nó thực sự là. There are a number of ways to deal with this, nhưng không có gì 'tiêu chuẩn' không may.

Vấn đề lớn thứ tư bạn sẽ phải giải quyết là interop giữa nodejs và phantomjs may mắn có a lot of npm packages that deal with this issue để lựa chọn.

Vì vậy, tôi biết tôi thiên vị (như tôi đã viết giải pháp tôi sẽ đề xuất) nhưng tôi khuyên bạn nên kiểm tra PhantomJsCloud.com miễn phí cho việc sử dụng ánh sáng.

Tháng 1 năm 2015 cập nhật: Vấn đề lớn khác (thứ 5) mà tôi gặp phải là cách gửi yêu cầu/phản hồi từ người quản lý/người cân bằng tải. Ban đầu tôi đã sử dụng máy chủ HTTP tích hợp của PhantomJS, nhưng vẫn tiếp tục chạy vào các hạn chế của nó, đặc biệt là về kích thước phản hồi tối đa. Tôi đã kết thúc bằng cách viết yêu cầu/phản hồi cho hệ thống tệp cục bộ như các dòng giao tiếp. * Tổng thời gian dành cho việc thực hiện dịch vụ đại diện cho 20 vấn đề trong tuần có thể là 1000 giờ làm việc. * và FYI Tôi đang viết lại hoàn toàn cho phiên bản tiếp theo .... (đang tiến hành)

+0

Câu trả lời hay Jason. Sẽ rất tuyệt nếu bạn có thể tiếp tục cho chúng tôi biết thêm về các chi tiết triển khai. Làm thế nào để bạn quản lý tất cả các trường hợp ví dụ? Ngoài ra, làm cách nào để bạn khởi chạy các phiên bản de Phantom từ chính Node? Bất kỳ đề xuất mô-đun để làm như vậy? Hoặc bạn sinh ra các quy trình? – Nobita

+1

Tôi thực hiện tất cả việc quản lý từ một ứng dụng 'bộ định tuyến nút' trên máy chủ. nó khởi chạy nhiều cá thể phantomjs.exe thông qua các lệnh quy trình sinh sản nodejs bình thường. không có gì đặc biệt trong vấn đề đó. Tôi đã thử tất cả các trình bao bọc phantomjs khác nhau được tìm thấy trên NPM, nhưng thực ra chúng hầu như hút. Đã kết thúc chỉ bằng cách sử dụng máy chủ http được tích hợp của phantomjs để giao tiếp với/từ ứng dụng bộ định tuyến nodejs. – JasonS

+0

điều gì về việc tạo một số đối tượng trang web trong một phiên bản phantomJS? Có gì không ổn với điều đó ? – Xsmael

5

Là một giải pháp thay thế cho câu trả lời tuyệt vời @JasonS bạn có thể thử PhearJS, mà tôi đã xây dựng. PhearJS là một giám sát viên được viết bằng NodeJS cho các phiên bản PhantomJS và cung cấp một API thông qua HTTP. Nó có sẵn mã nguồn mở từ Github.

1

nếu bạn đang sử dụng nodejs tại sao không sử dụng selen-webdriver

  1. chạy một số phantomjs dụ như webdriver phantomjs --webdriver=port_number
  2. cho mỗi phantomjs dụ tạo PhantomInstance

    function PhantomInstance(port) { 
        this.port = port; 
    } 
    
    PhantomInstance.prototype.getDriver = function() { 
        var self = this; 
        var driver = new webdriver.Builder() 
         .forBrowser('phantomjs') 
         .usingServer('http://localhost:'+self.port) 
         .build(); 
        return driver; 
    } 
    

    và đặt tất cả các chúng thành một mảng [phantomInstance1, phantomInstance2]

  3. tạo dispather.js rằng có được phantomInstance miễn phí từ mảng và

    var driver = phantomInstance.getDriver(); 
    
+0

Đây không phải là cách tốt. Hãy tin tôi ... trong chương trình của tôi, tôi đã sử dụng selenium-webdriver nhưng cuối cùng tôi đã từ bỏ nó! –

14

Đối với luận án thạc sĩ của tôi, tôi đã phát triển thư viện phantomjs-pool mà thực hiện chính xác này. Nó cho phép cung cấp các công việc sau đó được ánh xạ tới các nhân viên của PhantomJS. Thư viện xử lý phân phối công việc, giao tiếp, xử lý lỗi, ghi nhật ký, khởi động lại và một số nội dung khác. Thư viện đã được sử dụng thành công để thu thập dữ liệu hơn một triệu trang.

Ví dụ:

Các mã sau đây thực hiện một tìm kiếm Google cho các số 0-9 và tiết kiệm một ảnh chụp màn hình của trang như googleX.png. Bốn trang web được thu thập thông tin song song (do việc tạo ra bốn công nhân). Tập lệnh được bắt đầu qua node master.js.

master.js (chạy trong môi trường Node.js)

var Pool = require('phantomjs-pool').Pool; 

var pool = new Pool({ // create a pool 
    numWorkers : 4, // with 4 workers 
    jobCallback : jobCallback, 
    workerFile : __dirname + '/worker.js', // location of the worker file 
    phantomjsBinary : __dirname + '/path/to/phantomjs_binary' // either provide the location of the binary or install phantomjs or phantomjs2 (via npm) 
}); 
pool.start(); 

function jobCallback(job, worker, index) { // called to create a single job 
    if (index < 10) { // index is count up for each job automatically 
     job(index, function(err) { // create the job with index as data 
      console.log('DONE: ' + index); // log that the job was done 
     }); 
    } else { 
     job(null); // no more jobs 
    } 
} 

worker.js (chạy trong môi trường PhantomJS)

var webpage = require('webpage'); 

module.exports = function(data, done, worker) { // data provided by the master 
    var page = webpage.create(); 

    // search for the given data (which contains the index number) and save a screenshot 
    page.open('https://www.google.com/search?q=' + data, function() { 
     page.render('google' + data + '.png'); 
     done(); // signal that the job was executed 
    }); 

}; 
+1

Đây là một thư viện tuyệt vời. Tôi tự hỏi, liệu có cách nào để phát hiện khi không có quá trình nào khác được sinh ra? Như trong, chờ đợi, thông qua async hoặc một lời hứa, sau 'pool.start()' để làm điều gì đó một khi một loạt các quy trình đã hoàn thành? – afithings

+0

Cảm ơn bạn. Hiện tại không có cách nào để làm điều này đơn giản như với async. Tuy nhiên, bạn có thể sử dụng gọi lại cho từng công việc riêng lẻ (mà sẽ kích hoạt khi một công việc được thực hiện) và tăng số lượt truy cập theo cách đó. Vì vậy, bạn vẫn có thể phát hiện khi tất cả công việc đã hoàn tất. –

Các vấn đề liên quan