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

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

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

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

РЕДАКТИРОВАТЬ:

Я до сих пор не понял, как поставить его на паузу. Вот код, который не вызывает функцию 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(), в противном случае она будет отображаться немедленно, и я увижу только введенные учетные данные до перенаправления страницы. Может я что-то еще упускаю?


person Jpaji Rajnish    schedule 09.10.2014    source источник
comment
Вы не показали, как вы устанавливаете onLoadStarted и onLoadFinished. Вы должны сделать это до evaluate и готовый обработчик должен содержать render   -  person Artjom B.    schedule 11.10.2014
comment
Я отредактировал его, чтобы включить определения обратного вызова. Я не думаю, что это проблема, потому что они не вызываются для события click() с самого начала. Однако их вызывают для каждого page.open().   -  person Jpaji Rajnish    schedule 11.10.2014
comment
Возможно, вы exit слишком рано. Пожалуйста, посмотрите последний фрагмент в моем ответе. Работает ли это для вас?   -  person Artjom B.    schedule 11.10.2014
comment
Я думаю, что теперь знаю, в чем причина... страница, на которую я вхожу, является страницей asp.net, и, если я не ошибаюсь, страницы aspx фактически не возвращают новый URL-адрес html, а вместо этого динамически изменяют HTML-теги и данные на той же странице... Я должен подтвердить это. Единственное сомнение, которое у меня есть, это то, что URL-адрес действительно меняется, когда я нажимаю кнопку отправки. Это все еще страница .aspx, но с другим URL-адресом. Опять же, я не слишком много знаю о .aspx, поэтому мне придется кое-что проверить.   -  person Jpaji Rajnish    schedule 11.10.2014


Ответы (3)


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

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 для навигации по веб-страницам.

person Artjom B.    schedule 09.10.2014

Используйте оболочку высокого уровня, 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.

person Grigorii Chudnov    schedule 09.10.2014
comment
кошмарный js является модулем узла и поэтому не применим для простого PhantomJS. - person Artjom B.; 09.10.2014
comment
использование библиотеки делает это намного проще. Спасибо, что указали на это! для моих нужд это был лучший вариант - person Jesús Carrera; 27.03.2015
comment
Вау, часы борьбы с фантомом, все мои проблемы решили за 5 минут с кошмаром. Какой удивительный инструмент, не могу +1 достаточно. - person Kyle Chadha; 17.05.2015

Вот мой код, основанный на некоторых других ответах. В моем случае мне не нужно было специально оценивать какой-либо другой 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 (связанных с проблемами загрузки сертификатов).

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

person http203    schedule 06.11.2016
comment
Вы уверены, что этот код работает так, как вы его описываете? Функция обратного вызова page.open должна фактически вызываться одновременно со срабатыванием обработчика события page.onLoadFinished. Выполнение обоих действий означало бы для меня, что второе не будет уволено. - person Artjom B.; 06.11.2016
comment
@Artjom-B Да, это работает. Это то же самое, что вы опубликовали, за исключением того, что я удалил вызовы page.onLoadStarted и page.evaluate, поскольку они мне не нужны. Я не уверен, что понимаю, что вы имеете в виду. - person http203; 06.11.2016