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

В качестве инструмента для парсинга сайта я использую SlimerJS.
Пример постарался привести как можно в более упрощенном и универсальной форме.
Итак, точка входа:
Первая часть этого файла представляет собой общую логику относящуюся к работа SlimerJs
script.js
Здесь происходит подключение модуля парсера и его инициализация, а в метод init() передается URL страницы каталога товаров, ссылка является относительной. Основной домен сайта задается в файле config.js
Вся логика парсера находиться в файле grab.js. Я его разделил на две части, первая часть представляет собой объект обертку над SlimerJS для одновременной работы нескольких копий браузера.
Все комментарии по коду я вынес в листинг в целях упрощения понимания кода.
grab.js
Вторая часть файла, определяет поведение и расширяет созданный объект Grab
Для удобной работы с файловой системой в SlimerJS предусмотрен API, который позволяет как читать, так и записывать данные
file.js
И последний файл, это файл конфигурации, в котором можно указать переменные, общие для всей системы
config.js
Результат работы будет в виде файла, который можно будет обработать для дальнейшего экспорта данных.
Запускается скрипт командой из консоли
Исходники

В качестве инструмента для парсинга сайта я использую SlimerJS.
Пример постарался привести как можно в более упрощенном и универсальной форме.
Итак, точка входа:
Первая часть этого файла представляет собой общую логику относящуюся к работа SlimerJs
script.js
var grab = require('./grab'); // подключение модуля grab.create().init('/catalog'); // инициализация парсера
Здесь происходит подключение модуля парсера и его инициализация, а в метод init() передается URL страницы каталога товаров, ссылка является относительной. Основной домен сайта задается в файле config.js
Вся логика парсера находиться в файле grab.js. Я его разделил на две части, первая часть представляет собой объект обертку над SlimerJS для одновременной работы нескольких копий браузера.
Все комментарии по коду я вынес в листинг в целях упрощения понимания кода.
grab.js
var file = require("./file").create(); // подключение модуля для работы с файловой системой var config = require("./config").getConfig(); // подключение глобальных переменных /** * создаем объект-конструктор */ function Grab() { this.page; // храним текущий объект "webpage" this.current_url; // сохраняем текущий URL this.parentCategory; // сохраняем категорию продукта /** * метод инициализирует объект * @param url string относительный адрес ( /contacts ) * @param parent */ this.init = function(url, parent) { this.page = require("webpage").create(); // создаем объект webpage this.callbackInit(); // определяем callback для объекта webpage if(url) { // если параметра нет, то обращаемся к домену config.host += url; } this.parentCategory = parent; this.open(config.host); // открыть URL }; /** * открыть URL * @param {string} url адрес который нужно открыть */ this.open = function(url) { /* * место для возможной бизнес логики */ this.page.open(url); }; /** * завершить работы с текущим окном */ this.close = function() { this.page.close() }; /** * инициализация callback */ this.callbackInit = function() { var self = this; /** * метод вызывается при возникновение ошибки * @param {string} message error * @param {type} stack */ this.page.onError = function (message, stack) { console.log(message); }; /** * метод вызывается при редиректе или открытий новой страницы * @param {string} url новый URL */ this.page.onUrlChanged = function (url) { self.current_url = url; // сохраняем URL как текущий }; /** * метод вызывается, если в объекте webpage срабатывает метод console.log() * @param {string} message * @param {type} line * @param {type} file */ this.page.onConsoleMessage = function (message, line, file) { console.log(message); // выводим его в текущую область видимости }; /** * метод вызывается при каждой загрузке страницы * @param {string} status статус загрузки страницы */ this.page.onLoadFinished = function(status) { if(status !== 'success') { console.log("Sorry, the page is not loaded"); self.close(); } self.route(); // вызываем основной метод бизнес логики }; }; }
Вторая часть файла, определяет поведение и расширяет созданный объект Grab
Grab.prototype.route = function() { try { // если текущая страница это страница содержащая категории продуктов if(this.isCategoryPage()) { var categories = this.getCategories(); // спарсить данные со страницы категории file.writeJson(config.result_file, categories, 'a'); // записать данные в файл for (var i = 0; i < categories.length; i++) { // пройти все категории товаров var url = categories[i].url_article; // получаем URL на страницу с товаром текущей категории new Grab().init(url, categories[i].title); // открываем новую страницу slimer.wait(3000); // ждем 3 секунды, до открытия следующей страницы } } else { // текущая страница является карточкой товара var content = this.getContent(); // спарсить данные с карточки товара file.writeJson(config.result_file, content, 'a'); // записать результат в файл this.close(); // закрыть текущее окно } this.close(); } catch(err) { console.log(err); this.close(); } }; /** * получить со страницы весь контент, относящийся к категориям * @returns {Object} */ Grab.prototype.getCategories = function() { return this.getContent('categories') }; /** * проверить, содержит ли текущая страница категории товаров * @returns {bool} */ Grab.prototype.isCategoryPage = function() { return this.page.evaluate(function() { // определить, присутствуют ли данные, относящиеся к странице товаров return !$(".catalog-list .item .price").length; }); }; /** * получить полезные данные со страницы * @param {string} typeContent какие данные нужно получить {categories|product} * @returns {Object} */ Grab.prototype.getContent = function(typeContent) { var result = this.page.evaluate(function(typeContent) { var result = []; // находим блок, в котором находятся структурированные данные (страница категорий и продуктов имеют одинаковую разметку) $(".catalog-list .item").each(function(key, value) { var $link = $(value).find('a.name'); // кешируем ссылку var obj = { // собираем данные относящиеся к категории 'type': 'category', 'title': $link.text().trim().toLowerCase(), // заголовок категории 'url_article': $link.attr('href'), // ссылка на товары входящие в эту категорию 'url_article_image': $(value).find('a.img > img').attr('src') }; // если это карточка товара, то собираем данные относящиеся к карточке товара if(typeContent !== 'categories') { obj.size = []; obj.type = 'product'; $('.razmers:first .pink').each(function(key, value) { // размеры|цвет|диагональ... obj.size.push($(value).text().trim()); }); obj.price = parseInt($(value).find('.price').text(), 10); // цена } result.push(obj); }); return result; }, typeContent); return result; }; exports.create = function() { return new Grab(); };
Для удобной работы с файловой системой в SlimerJS предусмотрен API, который позволяет как читать, так и записывать данные
file.js
var fs = require('fs'); /** * инициализация объекта обертки */ function FileHelper() { /** * чтение данных * @param {string} path_to_file относительный путь до файла * @returns array - данные */ this.read = function(path_to_file) { if(!fs.isFile(path_to_file)){ throw new Error('File ('+path_to_file+') not found'); } var content = fs.read(path_to_file); if(!content.length) { throw new Error('File ('+path_to_file+') empty'); } return content.split("\n"); }; /** * записать данные в файл * @param {string} path_to_file относительный путь до файла * @param {string} content данные для записи * @param {string} mode режимы работы 'r', 'w', 'a/+', 'b' */ this.write = function(path_to_file, content, mode) { fs.write(path_to_file, content, mode); } /** * запись данных в виде JSON * @param {string} path_to_file относительный путь до файла * @param {array} content данные для записи * @param {string} mode режимы работы 'r', 'w', 'a/+', 'b' */ this.writeJson = function(path_to_file, content, mode) { var result = ''; for(var i=0; i < content.length; i++) { result += JSON.stringify(content[i]) + "\n"; } this.write(path_to_file, result, mode); } } exports.create = function() { return new FileHelper(); };
И последний файл, это файл конфигурации, в котором можно указать переменные, общие для всей системы
config.js
var Config = function() { this.host = 'http://example.ru'; this.log_path = 'logs\\error.txt'; this.result_file = 'result\\result.txt'; }; exports.getConfig = function() { return new Config(); };
Результат работы будет в виде файла, который можно будет обработать для дальнейшего экспорта данных.
Запускается скрипт командой из консоли
slimerjs script.js
Исходники