1. Code
  2. JavaScript
  3. Node

Делаем снимки экрана с помощью Node.js

Scroll to top

Russian (Pусский) translation by Ilya Nikov (you can also view the original English article)

Возможно, вы использовали NodeJS в качестве веб-сервера, но знаете ли вы, что вы также можете использовать его для веб-скрапинга? В этом уроке мы рассмотрим, как можно получить содержимое статических веб-страниц а так же рассмотрим эти надоедливые страницы с динамическим контентом, - все это с помощью NodeJS и нескольких полезных модулей NPM.



Немного о веб-скрапинге

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

Несмотря на эти недостатки, важно немного узнать о веб-скрапинге и некоторых инструментах, доступных для решения этой задачи. Когда сайт не раскрывает API или какой-либо канал для получения данных (RSS / Atom и т.д.), единственный вариант, с которым мы остались, чтобы получить нужны контент ... скрапинг.

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


Зачем использовать NodeJS?

Скраперы могут быть написаны на любом языке. Причина, по которой мне нравится использовать Node, связана с его асинхронным характером, а это значит, что мой код не блокируется в любой момент процесса. Я хорошо знаком с JavaScript, так что это дополнительный бонус. Наконец, есть несколько новых модулей, которые были написаны для NodeJS, что позволяет легко и надежно скрапить веб-сайты  (ну, так надежно на сколько вообще скрапинг это позволяет!). Давайте начнем!


Простой скрапинг с помощью YQL

Начнем с простого использования: статические веб-страницы. Это стандартные веб-страницы. Для них Yahoo! Язык запросов (YQL) должен хорошо работать Для тех, кто не знаком с YQL, это синтаксис типа SQL, который можно использовать для совместной работы с различными API.

В YQL есть несколько отличных таблиц, которые помогут разработчикам получить HTML с страницы. Я хочу выделить:

Давайте рассмотрим каждую из них и посмотрим, как их реализовать в NodeJS.

таблица html

Таблица html - это самый простой способ получить HTML из URL-адреса. Обычный запрос с использованием этой таблицы выглядит следующим образом:

1
select * from html where url="http://finance.yahoo.com/q?s=yhoo" and xpath='//div[@id="yfi_headlines"]/div[2]/ul/li/a'

Этот запрос состоит из двух параметров: «url» и «xpath». Этот URL не требует пояснений. XPath состоит из строки XPath, указывающей YQL, какой раздел HTML должен быть возвращен. Попробуйте выполнить этот запрос здесь.

Дополнительные параметры, которые вы можете использовать, включают в себя browser (boolean), charset (string) и compat (string). Мне не пришлось использовать эти параметры, но обратитесь к документации, если вам они оказались нужны.

Неудобно с XPath?

К сожалению, XPath не очень популярный способ обхода древовидной структуры HTML. Он может быть сложным для чтения и написания для новичков.

Давайте посмотрим на следующую таблицу, которая делает то же самое, но позволяет вместо этого использовать CSS

Таблица данных.html.cssselect

Таблица data.html.cssselect - это мой предпочтительный способ скрапинга HTML со страницы. Он работает так же, как и таблица html, но позволяет использовать CSS вместо XPath. На практике под капотом эта таблица просто преобразует CSS в XPath, а затем вызывает html-таблицу, поэтому она немного медленнее. Для нужд скрапинга разница должна быть незначительной.

Обычный запрос с использованием этой таблицы выглядит так:

1
select * from data.html.cssselect where url="www.yahoo.com" and css="#news a"

Как вы можете видеть, он намного чище. Я рекомендую сначала попробовать этот метод, когда вы пытаетесь скрапить HTML с помощью YQL. Попробуйте этот запрос здесь.

Таблица htmlstring

Таблица htmlstring полезна для случаев, когда вы пытаетесь очистить большой фрагмент форматированного текста с веб-страницы.

Использование этой таблицы позволяет получить весь HTML-контент этой страницы в одной строке, а не как JSON, который разбит на основе структуры DOM.

Например, простой ответ JSON, который скрапит тег <a>, выглядит следующим образом:

1
"results": {
2
   "a": {
3
     "href": "...",
4
     "target": "_blank",
5
     "content": "Apple Chief Executive Cook To Climb on a New Stage"
6
    }
7
 }

Посмотрите, как атрибуты определяются как свойства? Вместо этого ответ из таблицы htmlstring будет выглядеть так:

1
"results": {
2
  "result": {
3
    "<a href=\"\" target="_blank">Apple Chief Executive Cook To Climb on a New Stage</a>

4
   }

5
}

Итак, зачем вы это используете? Ну, по моему опыту, это очень полезно, когда вы пытаетесь очистить большое количество форматированного текста. Например, рассмотрим следующий фрагмент:

1
<p>Lorem ipsum <strong>dolor sit amet</strong>, consectetur adipiscing elit.</p>
2
<p>Proin nec diam magna. Sed non lorem a nisi porttitor pharetra et non arcu.</p>

Используя таблицу htmlstring, вы можете получить этот HTML как строку и использовать regex для удаления тегов HTML. Это более простая задача, чем итерация через JSON, которая была разделена на свойства и дочерние объекты на основе структуры DOM страницы.


Использование YQL с NodeJS

Теперь, когда мы немного знаем о некоторых таблицах, доступных нам в YQL, давайте внедрим веб-скребок с использованием YQL и NodeJS. К счастью, это очень просто, благодаря модулю node-yql от Derek Gathright.

Мы можем установить модуль с помощью npm:

1
npm install yql

Модуль чрезвычайно прост и состоит только из одного метода: метода YQL.exec(). Он определяется как следующее:

1
function exec (string query [, function callback] [, object params] [, object httpOptions])

Мы можем использовать его, заинклюдив и вызывая YQL.exec(). Например, допустим, мы хотим очистить заголовки от всех сообщений на главной странице Nettuts:

1
var YQL = require("yql");
2
3
new YQL.exec('select * from data.html.cssselect where url="http://net.tutsplus.com/" and css=".post_title a"', function(response) {
4
5
    //response consists of JSON that you can parse

6
7
});

Самое замечательное в YQL - это способность тестировать ваши запросы и определять, какой JSON вы возвращаете в режиме реального времени. Перейдите на консоль, чтобы попробовать этот запрос, или нажмите здесь, чтобы увидеть сырой JSON.

Объекты params и httpOptions являются необязательными. Параметры могут содержать такие свойства, как env (независимо от того, используете ли вы конкретную среду для таблиц) и format (xml или json). Все свойства, переданные в params, кодируются в URI и добавляются к строке запроса. Объект httpOptions передается в заголовок запроса. Здесь вы можете указать, хотите ли вы включить например SSL.

Файл JavaScript с именем yqlServer.js содержит минимальный код, необходимый для скрапинга с помощью YQL. Вы можете запустить его, выполнив следующую команду в своем терминале:

1
node yqlServer.js

Исключения и другие известные инструменты

YQL - мой предпочтительный выбор для скрапинга содержимого от статических веб-страниц, потому что он легко читается и прост в использовании. Тем не менее, YQL завершится с ошибкой, если на рассматриваемой веб-странице есть файл robots.txt, который отрицает ответ на него. В этом случае вы можете посмотреть некоторые из упомянутых ниже утилит или использовать PhantomJS, который мы рассмотрим в следующем разделе.

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

JSDOM - очень популярный проект, который реализует W3C DOM в JavaScript. По предоставленному HTML он может построить DOM, с которым вы можете взаимодействовать. Ознакомьтесь с документацией, чтобы узнать, как вы можете использовать JSDOM и любую JS-библиотеку (например, jQuery) совместно для скрапинга данных с веб-страниц.


Скрапинг страниц с динамическим контентом

До сих пор мы рассмотрели некоторые инструменты, которые могут помочь нам в скрапинге веб-страницы со статическим контентом. С YQL это относительно легко. К сожалению, нам часто предоставляют страницы, содержащие контент, который динамически загружается с помощью JavaScript. В этих случаях страница часто изначально пуста, а затем уже добавляется содержимое. Как мы можем решить эту проблему?

Пример

Позвольте мне привести пример того, что я имею в виду; я загрузил простой HTML-файл на свой собственный сайт, который добавляет некоторый контент через JavaScript через две секунды после события document.ready() вызывается функция. Вы можете посмотреть страницу здесь. Вот как выглядит исходный код:

1
<!DOCTYPE html>
2
<html>
3
    <head>
4
        <title>Test Page with content appended after page load</title>
5
    </head>
6
7
    <body>
8
        Content on this page is appended to the DOM after the page is loaded.
9
10
        <div id="content">
11
12
        </div>
13
14
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
15
    <script>
16
        $(document).ready(function() {
17
18
            setTimeout(function() {
19
                $('#content').append("<h2>Article 1</h2><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p><h2>Article 2</h2><p>Ut sed nulla turpis, in faucibus ante. Vivamus ut malesuada est. Curabitur vel enim eget purus pharetra tempor id in tellus.</p><h2>Article 3</h2><p>Curabitur euismod hendrerit quam ut euismod. Ut leo sem, viverra nec gravida nec, tristique nec arcu.</p>");
20
            }, 2000);
21
22
        });
23
    </script>
24
    </body>
25
</html>

Теперь давайте попробуем получить текст внутри <div id="content"> с помощью YQL.

1
var YQL = require("yql");
2
3
new YQL.exec('select * from data.html.cssselect where url="http://tilomitra.com/repository/screenscrape/ajax.html" and css="#content"', function(response) {
4
5
    //This will return undefined! The scraping was unsuccessful!

6
    console.log(response.results);
7
8
});

Вы заметите, что YQL возвращает undefined, потому что, когда страница загружается, <div id = "content"> пуст. Содержимое еще не добавлено. Вы можете попробовать запрос для себя здесь.

Давайте посмотрим, как мы можем обойти эту проблему!

Встречаем PhantomJS

PhantomJS может загружать веб-страницы и имитировать браузер на основе Webkit без графического интерфейса.

Мой предпочтительный метод получения информации с таких сайтов - использовать PhantomJS. PhantomJS описывает себя как «headless Webkit с JavaScript API». В упрощенном виде это означает, что PhantomJS может загружать веб-страницы и имитировать браузер на основе Webkit без использования графического интерфейса. В качестве разработчика мы можем вызвать специальные методы, которые PhantomJS предоставляет для выполнения кода на странице. Поскольку он ведет себя как браузер, скрипты на веб-странице выполняются так же, как в обычном браузере.

Чтобы получить данные с нашей страницы, мы собираемся использовать PhantomJS-Node, отличный небольшой проект с открытым исходным кодом, который соединяет PhantomJS с NodeJS. Под капотом этот модуль запускает PhantomJS как дочерний процесс.

Установка PhantomJS

Прежде чем вы сможете установить npm модуль PhantomJS-Node, вы должны установить PhantomJS. Тем не менее, установка и сборка PhantomJS может быть немного сложной.

Сначала перейдите на PhantomJS.org и загрузите соответствующую версию для своей операционной системы. В моем случае это Mac OSX.

После загрузки разархивируйте его где-нибудь, например /Applications/. Затем вам нужно добавить его в свой PATH:

1
sudo ln -s /Applications/phantomjs-1.5.0/bin/phantomjs /usr/local/bin/

Замените 1.5.0 на загруженную вами версию PhantomJS. Имейте в виду, что не все системы будут иметь путь /usr/local/bin/. Некоторые системы будут иметь: /usr/bin/, /bin/ или usr/X11/bin.

Для пользователей Windows ознакомьтесь с кратким учебником. Чтобы понять что все настроено, откройте терминал и введите phantomjs, и если вы не получаете никаких ошибок, то можно двигаться дальше.

Если вам неудобно редактировать PATH, обратите внимание на то, где вы распаковывали PhantomJS, и я покажу еще один способ его настройки в следующем разделе, хотя я рекомендую вам всетаки отредактировать свой PATH.

Установка PhantomJS-Node

Настройка PhantomJS-Node будет намного проще. Если у вас установлен NodeJS, вы можете установить его через npm:

1
npm install phantom

Если вы не изменили свой PATH на предыдущем шаге при установке PhantomJS, вы можете перейти в каталог phantom/ и отредактировать эту строку в phantom.js.

1
ps = child.spawn('phantomjs', args.concat([__dirname + '/shim.js', port]));

Измените путь:

1
ps = child.spawn('/path/to/phantomjs-1.5.0/bin/phantomjs', args.concat([__dirname + '/shim.js', port]));

Как только это будет сделано, вы можете проверить все, запустив этот код:

1
var phantom = require('phantom');
2
phantom.create(function(ph) {
3
  return ph.createPage(function(page) {
4
    return page.open("http://www.google.com", function(status) {
5
      console.log("opened google? ", status);
6
      return page.evaluate((function() {
7
        return document.title;
8
      }), function(result) {
9
        console.log('Page title is ' + result);
10
        return ph.exit();
11
      });
12
    });
13
  });
14
});

Запуск его из командной строки, он должен вызвать следующее:

1
opened google?  success
2
Page title is Google

Если у вас есть это, вы готовы к работе. Если нет, оставьте свой комментарий, и я постараюсь вам помочь!

Использование PhantomJS-Node

Чтобы вам было проще, я включил JS-файл под названием phantomServer.js, который использует некоторые API PhantomJS для загрузки веб-страницы. Он ждет 5 секунд перед тем, как выполнить JavaScript, который скрапит страницу. Вы можете запустить его, перейдя в каталог и вызвав следующую команду в своем терминале:

1
node phantomServer.js

Я сейчас опишу как это все работает. Во-первых, нам требуется PhantomJS:

1
var phantom = require('phantom');

Затем мы реализуем некоторые методы из API. А именно, мы создаем экземпляр страницы, а затем вызываем метод open():

1
phantom.create(function(ph) {
2
  return ph.createPage(function(page) {
3
4
    //From here on in, we can use PhantomJS' API methods

5
    return page.open("http://tilomitra.com/repository/screenscrape/ajax.html",          function(status) {
6
7
            //The page is now open      

8
            console.log("opened site? ", status);
9
10
        });
11
    });
12
});

После того, как страница будет открыта, мы можем добавить JavaScript на страницу. Давайте введем jQuery через page.injectJs() метод:

1
phantom.create(function(ph) {
2
  return ph.createPage(function(page) {
3
    return page.open("http://tilomitra.com/repository/screenscrape/ajax.html", function(status) {
4
      console.log("opened site? ", status);         
5
6
            page.injectJs('http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js', function() {
7
                //jQuery Loaded

8
                //We can use things like $("body").html() in here.

9
10
            });
11
    });
12
  });
13
});

jQuery теперь загружен, но мы не знаем, загружен ли динамический контент на странице. Чтобы учесть это, я обычно добавляю код своего скрапера внутри функции setTimeout(), которая выполняется через определенный промежуток времени. Если вы хотите более динамичное решение, PhantomJS API позволяет слушать и эмулировать определенные события. Простой пример:

1
setTimeout(function() {
2
    return page.evaluate(function() {
3
4
        //Get what you want from the page using jQuery. 

5
        //A good way is to populate an object with all the jQuery commands that you need and then return the object.

6
7
        var h2Arr = [], //array that holds all html for h2 elements

8
        pArr = []; //array that holds all html for p elements

9
10
        //Populate the two arrays

11
        $('h2').each(function() {
12
            h2Arr.push($(this).html());
13
        });
14
15
        $('p').each(function() {
16
            pArr.push($(this).html());
17
        });
18
19
        //Return this data

20
        return {
21
            h2: h2Arr,
22
            p: pArr
23
        }
24
    }, function(result) {
25
        console.log(result); //Log out the data.

26
        ph.exit();
27
    });
28
}, 5000);

Объединив все это, наш файл phantomServer.js выглядит так:

1
var phantom = require('phantom');
2
phantom.create(function(ph) {
3
  return ph.createPage(function(page) {
4
    return page.open("http://tilomitra.com/repository/screenscrape/ajax.html", function(status) {
5
      console.log("opened site? ", status);         
6
7
            page.injectJs('http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js', function() {
8
                //jQuery Loaded.

9
                //Wait for a bit for AJAX content to load on the page. Here, we are waiting 5 seconds.

10
                setTimeout(function() {
11
                    return page.evaluate(function() {
12
13
                        //Get what you want from the page using jQuery. A good way is to populate an object with all the jQuery commands that you need and then return the object.

14
                        var h2Arr = [],
15
                        pArr = [];
16
                        $('h2').each(function() {
17
                            h2Arr.push($(this).html());
18
                        });
19
                        $('p').each(function() {
20
                            pArr.push($(this).html());
21
                        });
22
23
                        return {
24
                            h2: h2Arr,
25
                            p: pArr
26
                        };
27
                    }, function(result) {
28
                        console.log(result);
29
                        ph.exit();
30
                    });
31
                }, 5000);
32
33
            });
34
    });
35
    });
36
});

Эта реализация немного грубовата и дезорганизована, но она работает. Используя PhantomJS, мы можем скрапить страницу с динамическим контентом! На консоли должна выводиться следующая информация:

1
→ node phantomServer.js
2
opened site?  success
3
{ h2: [ 'Article 1', 'Article 2', 'Article 3' ],
4
  p: 
5
   [ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
6
     'Ut sed nulla turpis, in faucibus ante. Vivamus ut malesuada est. Curabitur vel enim eget purus pharetra tempor id in tellus.',
7
     'Curabitur euismod hendrerit quam ut euismod. Ut leo sem, viverra nec gravida nec, tristique nec arcu.' ] }

Заключение

В этом уроке мы рассмотрели два разных способа выполнения веб-скрапинга. Если вы делаете скрапинг со статической веб-страницы, мы можем воспользоваться YQL, который легко настроить и использовать. С другой стороны, для динамических сайтов мы можем использовать PhantomJS. Его немного сложнее настроить, но он предоставляет куда больше возможностей Помните: вы можете также использовать PhantomJS и для статических сайтов!

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