Как дождаться события click() для загрузки в phantomjs перед продолжением?

Phantomjs имеет эти два действительно полезных обратных вызова onLoadStarted и onLoadFinished, которые позволяют существенно приостановить выполнение во время загрузки страницы. Но я искал, и я не могу найти эквивалент, если вы click() кнопку отправки или гиперссылку. Подобная загрузка страницы происходит, но onLoadStarted не вызывается для этого события, я думаю, потому что не существует явного page.open(), который происходит. Я пытаюсь найти чистый способ приостановить выполнение во время загрузки.

Одно из решений, очевидно, вложенное setTimeout, но я бы хотел избежать этого сценария, потому что он взломал и полагался на пробную версию и ошибку вместо надежного и более надежного, как тестирование на что-то или ожидание события.

Существует ли конкретный обратный вызов для такого типа загрузки страницы, который я пропустил? Или может быть какой-то общий шаблон кода, который может справиться с такими вещами?

EDIT:

Я до сих пор не понял, как заставить его остановиться. Здесь код, который не вызывает функцию onLoadStarted(), когда я вызываю команду click():

var loadInProgress = false;

page.onLoadStarted = function() {
  loadInProgress = true;
  console.log("load started");
};

page.onLoadFinished = function() {
  loadInProgress = false;
  console.log("load finished");
};

page.open(loginPage.url, function (status) {
    if (status !== 'success') {
        console.log('Unable to access network');
        fs.write(filePath + errorState, 1, 'w');
        phantom.exit();
    } else {
        page.evaluate(function (loginPage, credentials) {
            console.log('inside loginPage evaluate function...\n')
            document.querySelector('input[id=' + loginPage.userId + ']').value = credentials.username;
            document.querySelector('input[id=' + loginPage.passId + ']').value = credentials.password;      
            document.querySelector('input[id=' + loginPage.submitId + ']').click();
            //var aTags = document.getElementsByTagName('a')
            //aTags[1].click();
        }, loginPage, credentials);

        page.render(renderPath + 'postLogin.png');
        console.log('rendered post-login');

Я дважды проверил, что идентификатор верен. page.render() покажет, что информация отправлена, но только если я помещаю ее в setTimeout(), в противном случае она отображает ее немедленно, и я вижу только учетные данные, введенные до перенаправления страницы. Может, мне не хватает чего-то другого?

Ответы

Ответ 1

Я думаю, что функции onLoadStarted и onLoadFinished - это все, что вам нужно. Возьмем, например, следующие script:

var page = require('webpage').create();

page.onResourceReceived = function(response) {
    if (response.stage !== "end") return;
    console.log('Response (#' + response.id + ', stage "' + response.stage + '"): ' + response.url);
};
page.onResourceRequested = function(requestData, networkRequest) {
    console.log('Request (#' + requestData.id + '): ' + requestData.url);
};
page.onUrlChanged = function(targetUrl) {
    console.log('New URL: ' + targetUrl);
};
page.onLoadFinished = function(status) {
    console.log('Load Finished: ' + status);
};
page.onLoadStarted = function() {
    console.log('Load Started');
};
page.onNavigationRequested = function(url, type, willNavigate, main) {
    console.log('Trying to navigate to: ' + url);
};

page.open("http://example.com", function(status){
    page.evaluate(function(){
        // click
        var e = document.createEvent('MouseEvents');
        e.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
        document.querySelector("a").dispatchEvent(e);
    });
    setTimeout(function(){
        phantom.exit();
    }, 10000);
});

Он печатает

Trying to navigate to: http://example.com/
Request (#1): http://example.com/
Load Started
New URL: http://example.com/
Response (#1, stage "end"): http://example.com/
Load Finished: success
Trying to navigate to: http://www.iana.org/domains/example
Request (#2): http://www.iana.org/domains/example
Load Started
Trying to navigate to: http://www.iana.org/domains/reserved
Request (#3): http://www.iana.org/domains/reserved
Response (#2, stage "end"): http://www.iana.org/domains/example
New URL: http://www.iana.org/domains/reserved
Request (#4): http://www.iana.org/_css/2013.1/screen.css
Request (#5): http://www.iana.org/_js/2013.1/jquery.js
Request (#6): http://www.iana.org/_js/2013.1/iana.js
Response (#3, stage "end"): http://www.iana.org/domains/reserved
Response (#6, stage "end"): http://www.iana.org/_js/2013.1/iana.js
Response (#4, stage "end"): http://www.iana.org/_css/2013.1/screen.css
Response (#5, stage "end"): http://www.iana.org/_js/2013.1/jquery.js
Request (#7): http://www.iana.org/_img/2013.1/iana-logo-header.svg
Request (#8): http://www.iana.org/_img/2013.1/icann-logo.svg
Response (#8, stage "end"): http://www.iana.org/_img/2013.1/icann-logo.svg
Response (#7, stage "end"): http://www.iana.org/_img/2013.1/iana-logo-header.svg
Request (#9): http://www.iana.org/_css/2013.1/print.css
Response (#9, stage "end"): http://www.iana.org/_css/2013.1/print.css
Load Finished: success

Это показывает, что нажатие ссылки дважды вызывает событие LoadStarted и событие NavigationRequested, потому что есть перенаправление. Хитрость заключается в том, чтобы добавить обработчики событий перед выполнением действия:

var page = require('webpage').create();

page.open("http://example.com", function(status){
    page.onLoadFinished = function(status) {
        console.log('Load Finished: ' + status);
        page.render("test37_next_page.png");
        phantom.exit();
    };
    page.onLoadStarted = function() {
        console.log('Load Started');
    };

    page.evaluate(function(){
        var e = document.createEvent('MouseEvents');
        e.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
        document.querySelector("a").dispatchEvent(e);
    });
});

Если вам нужно это сделать, возможно, пришло время попробовать что-то еще, например CasperJS. Он работает поверх PhantomJS, но имеет гораздо лучший API для навигации по веб-страницам.

Ответ 2

Используйте оболочку высокого уровня, nightmarejs. Вы можете легко click там и ждать потом.

Вот код (раздел "Примеры" ):

var Nightmare = require('nightmare');
new Nightmare()
  .goto('http://yahoo.com')
    .type('input[title="Search"]', 'github nightmare')
    .click('.searchsubmit')
    .run(function (err, nightmare) {
      if (err) return console.log(err);
      console.log('Done!');
    });

Дополнительные примеры и использование API можно найти на github

Ответ 3

Вот мой код, основанный на некоторых других ответах. В моем случае мне не нужно было специально оценивать какой-либо другой javascript. Мне просто нужно было дождаться завершения загрузки страницы.

var system = require('system');
if (system.args.length === 1) {
    console.log('Try to pass some arguments when invoking this script!');
}
else {
    var page = require('webpage').create();
    var address = system.args[1];

    page.open(address, function(status){
        page.onLoadFinished = function(status) {
            console.log(page.content);
            phantom.exit();
        };    
    });     
}

Сохраните выше в файле с именем scrape.js и назовите его следующим образом:

phantomjs --ssl-protocol=any --ignore-ssl-errors=true scrape.js https://www.example.com

Параметры, связанные с SSL, добавляются во избежание других проблем, которые возникают у некоторых сайтов HTTPS (связанных с проблемами загрузки сертификатов).

Надеюсь, это поможет кому-то!