Как вы очищаете динамически сгенерированную веб-страницу в NodeJs?

Есть сайты, DOM и содержимое которых генерируются динамически при загрузке страницы. (Сайты на основе Angularjs печально известны этим)

Какой подход вы используете? Я пробовал как phantomjs, так и jsdom, но, похоже, я не могу заставить страницу выполнить свой javascript, прежде чем я очищаю.

Вот простой пример jsdom (не основанный на angularjs, но все же динамически сгенерированный)

var env = require('jsdom').env;

exports.scrape = function(link, callback) {
  var config = {
    url: link,
    headers: { 
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36'
      },
    done: jsdomDone
  };

  env(config);
}

function jsdomDone(err, window) {
  var info = null;
  if(err) {
    console.error(err);
  } else {
    var $ = require('jquery')(window);

    console.log($('.profilePic').attr('src'));
  }
}

exports.scrape('https://www.facebook.com/elcompanies');

Я пробовал фантомы с умеренным успехом.

var page = new WebPage()
var fs = require('fs');

page.onLoadFinished = function() {
  console.log("page load finished");
  window.setTimeout(function() {
    page.render('export.png');
    fs.write('1.html', page.content, 'w');
    phantom.exit();
  }, 10000);
};

page.open("https://www.facebook.com/elcompanies", function() {
  page.evaluate(function() {
  });
});

Здесь я жду события onLoadFinished и даже ставлю 10-секундный таймер. Интересно то, что в то время как мой захват изображения export.png страницы показывает полностью обработанную страницу, мой 1.html не показывает элемент класса .profilePic на его законном месте. Похоже, он находится в каком-то коде javascript, окруженном каким-то блоком "require("TimeSlice").guard(function() {bigPipe.onPageletArrive({...")

Если вы можете предоставить мне рабочий пример, который соскребает изображение с этой страницы, это было бы полезно.


person kane    schedule 03.01.2016    source источник
comment
Не знаю, почему мой предыдущий комментарий был удален. Есть ли причина, по которой мой вопрос заминусован? Если я нарушаю условия SO или спрашиваю что-то, чего не должен, я хотел бы знать   -  person kane    schedule 04.01.2016
comment
Вы не нарушаете никаких условий, и вопрос в порядке. Просто обратите внимание, что, добавляя награду к вопросу, вы привлекаете к нему больше внимания, что обычно приводит к большему количеству голосов. В этом случае вам просто нужно исследовать, чтобы увидеть, когда именно javascript будет выполнен на вашей целевой странице, а затем выяснить, позволят ли вам phantomjs или jsdom ждать так долго, прежде чем очищать. jsdom, например, имеет три события, которые он может прослушивать, но я не думаю, что какое-либо из них сработает в вашей ситуации (вы уже используете то, которое срабатывает последним).   -  person Kevin B    schedule 06.01.2016
comment
Отвечает ли это на ваш вопрос? Как очистить страницы с динамическим содержимым с помощью узла .js?   -  person ggorlen    schedule 02.07.2021


Ответы (4)


Я провел парсинг в Facebook, используя nightmarejs.
Вот код, который я сделал, чтобы получить некоторый контент из некоторых постов на странице Facebook.

module.exports = function checkFacebook(callback) {
var nightmare = Nightmare();
Promise.resolve(nightmare
  .viewport(1000, 1000)
  .goto('https://www.facebook.com/login/')
  .wait(2000)
  .evaluate(function(){
    document.querySelector('input[id="email"]').value = facebookEmail
    document.querySelector('input[id="pass"]').value = facebookPwd
    return true
  })
  .click('#loginbutton input')
  .wait(1000)
  .goto('https://www.facebook.com/groups/bierconomia')
  .evaluate(function(){
    var posts = document.getElementsByClassName('_1dwg')
    var length = posts.length
    var postsContent = []
    for(var i = 0; i < length; i++){
      var pTag = posts[i].getElementsByTagName('p')
      postsContent.push({
        content: pTag[0] ? pTag[0].innerText : '',
        productLink: posts[i].querySelector('a[rel = "nofollow"]') ? posts[i].querySelector('a[rel = "nofollow"]').href : '',
        photo: posts[i].getElementsByClassName('_46-i img')[0] ? posts[i].getElementsByClassName('_46-i img')[0].src : ''
      })
    }
    return postsContent
  }))
  .then(function(results){
    log(results)
    return new Promise(function(resolve, reject) {
      var leanLinks = results.map(function(result){
        return {
          post: {
            content: result.content,
            productLink: extractLinkFromFb(result.productLink),
            photo: result.photo
          }
        }
      })
      resolve(leanLinks)
    })
  })


Что мне кажется полезным в кошмаре, так это то, что вы можете использовать функцию ожидания либо для ожидания X мс, либо для рендеринга определенного класса.

person Christian Saiki    schedule 12.01.2016
comment
Кошмар не пробовал, но выглядит многообещающе. я попробую - person kane; 13.01.2016
comment
Просто заметка, Кошмар не безголовый. Запуск зависит от Electron, поэтому в производственной среде он может быть довольно тяжелым. - person Max Baldwin; 16.03.2017
comment
Да, я отказался от использования кошмара js. Теперь я использую node horseman -› github.com/johntitus/node-horseman Это было довольно легко портировать код на всадника - person Christian Saiki; 17.03.2017

Это связано с тем, что сгенерированные веб-страницы на основе вызовов AJAX имеют асинхронные вызовы AJAX, и вы не можете полагаться на события onLoad (поскольку данные по-прежнему недоступны).

По моему личному мнению, самым надежным способом было бы отследить, какие службы REST вызываются из этого HTML, и делать прямые вызовы к ним. Иногда вам потребуется использовать значения, найденные в HTML, или значения, взятые из других вызовов.

Я знаю, это может показаться сложным, и на самом деле это так. Вам нужно отладить страницу и узнать, что вызывается. Но это точно сработает.

Кстати, с этой задачей поможет использование инструментов разработчика Chrome. Просто наблюдайте, какие вызовы выполняются на вкладке сети. Вы даже можете наблюдать за тем, что было отправлено и получено при каждом вызове AJAX.

person David Rissato Cruz    schedule 12.01.2016
comment
Это может работать для определенных сайтов, но мне нужен общий подход, который отображает динамически сгенерированную страницу до ее очистки. - person kane; 13.01.2016
comment
Поэтому вам нужно использовать фантом, если вы хотите выполнить js. Я прокомментирую ваш вопрос, потому что я видел там проблему - person David Rissato Cruz; 13.01.2016

Если это одноразовая вещь, то есть, если я просто хочу очистить одну страницу один раз, я просто использую браузер и artoo-js.

person Juan Pablo Morales    schedule 11.01.2016
comment
Я хотел бы сделать это программно - person kane; 13.01.2016

Я никогда не пытался записать страницу на диск с помощью фантома, но у меня есть два наблюдения:

1) вы используете fs.write для записи на диск, но writeFile — это асинхронный вызов. Это означает, что вам нужно либо изменить его на fs.writeFileSync, либо использовать обратный вызов перед закрытием фантома.

2) Я надеюсь, что вы не ожидаете написать HTML в файл, открыть его в браузере и отобразить, как при сохранении png, потому что так не работает. Некоторые объекты могут быть сохранены непосредственно в свойствах DOM, и, конечно, есть значения, хранящиеся в переменных javascript, эти вещи никогда не будут сохранены.

person David Rissato Cruz    schedule 12.01.2016
comment
Re (1) fs.write не проблема. Файл html пишется. Re (2) Я надеялся увидеть тот же DOM, что и при просмотре страницы. Когда я открываю сохраненный html, он правильно отображается в моем браузере, но когда я открываю html в блокноте, он не показывает тот же DOM - person kane; 13.01.2016