Давайте напишем парсер веб-страницы! Он будет раз в час загружать набор страниц, искать в них таблицу с определённым id, склеивать всё вместе и сохранять на диск.
Мне это понадобилось чтобы сохранять архив цен на одном сайте. Благодаря модулям request и cheerio сделать это очень легко.
Нам понадобится несколько модулей Node.js: http и request для загрузки страницы, iconv-lite для перевода кодировок, cheerio для парсинга DOM страницы, fs для сохранения файла на диск, vow для правильной синхронности и, наконец, cron чтобы делать всё это по расписанию.
var http = require('http'), express = require('express'), request = require('request'), cheerio = require('cheerio'), iconv = require('iconv-lite'), fs = require('fs'), vow = require('vow'); var get_table = [], full_table = []; var cronJob = require('cron').CronJob; //Назначим работу крону - выполнять переданные функцию раз в час - в 10-ю минуту. //То есть, в 0:10:00, 1:10:00, 2:10:00 и так далее. new cronJob('* 23 * * * *', function(){ var getUrlTables=function (i, url) { //Добавляем в массив новый promise. После прохождения всего цикла получим //массив всех promise, который очень удобно отслеживать. get_table_promise[i]=vow.promise(); request({uri:url,method:'GET',encoding:'binary'}, function (err, res, body) { //Получили текст страницы, теперь исправляем кодировку и //разбираем DOM с помощью Cheerio. var $=cheerio.load( iconv.encode( iconv.decode( new Buffer(body,'binary'), 'win1251'), 'utf8') ); table=''; //Cheerio даёт возможность навигации по DOM //с помощью стандартных CSS-селекторов. $('table#info_table > tr').each(function(){ table+='<tr>'+this.html()+'</tr>'; }); //Работа с таблицей, удаление ненужных строк и прочего table=table.split('</td></tr><tr><td>'); table.splice(0,1); table=table.join('</td></tr><tr><td>'); //Складываем результат в массив результатов и завершаем promise full_table[i]='<tr><td>'+table; get_table_promise[i].fulfill(); }); } var crawlData=(function () { var urls={1:'1',2:'2',3:'3',4:'4',5:'5',6:'6',7:'7',8:'8',9:'9',10:'10'}; //Обрабатываем каждый адрес из списка for(i in urls) getUrlTables(i,urls[i]); //Передаём в all массив Promise - он дождётся завершения их всех. vow.all(get_table_promise).spread(function (building) { //Склеим все полученные таблицы в одну full_info='<table>'; for(i in urls) full_info+=full_table[i]; full_info+='</table>'; //Имя файла будет формироваться из текущей даты и времени date=new Date; day=date.getDate(); mon=date.getMonth()+1; yr=date.getFullYear(); hr=date.getHours(); date_str=((hr<10?'0':'')+hr)+'_'+((day<10?'0':'')+day) +((mon<10?'-0':'-')+mon)+'-'+yr; //Сохраняем результат fs.open("vrosts_"+date_str+".dat","w",0644, function(err,file_handle){ if(!err){ fs.write(file_handle,full_info,null,'utf8'); } }); }); })(); }, null, true);
Теперь живое объяснение. В цикле вызываем 10 (по количеству url) копий функции getUrlTables. Она принимает «номер» и url. Номер будет использоваться и для заполнения промизов (promise), и для сохранения результатов в массиве.
Переходим в функцию getUrlTables. Она запрашивает url, получает страницу в текстовом виде. Тут же переводим её в нужную (читай, правильную) кодировку utf8, и натравливаем на неё cheerio. Он формирует DOM-дерево страницы, и предоставляет интерфейс, похожий на jQuery.
С помощью этого интерфейса находим таблицу с именем info_table, а в ней — все строки, т.е. теги tr. Тут всё просто, используем стандартные CSS-селекторы.
Для каждого из найденных элементов получаем их html-код командой this.html() и облачаем их обратно в теги <tr>..</tr>.
Чистим полученную таблицу от мусора (в моём случае нужно было избавиться от первой строки таблицы, в которой лежал заголовок) и записываем её в массив результатов. Завершаем Promise командой fulfill.
Вернёмся к основной процедуре. Функция all модуля Vow даёт очень удобный метод отслеживать все необходимые промизы — она принимает на вход их массив. А у нас уже есть отличный массив get_table_promise — прямо его и пустим ей на вход.
После завершения всех промизов, т.е. после запроса и обработки всех страниц, склеиваем полученные таблицы в одну, генерируем имя файла вида costs_час_день-месяц-год.dat и сохраняем таблицу в этот файл. Таким образом, в файле теперь хранится html-код таблицы, который можно вставлять в какую-либо страницу сайта.
Теперь об оптимизациях. Конечно, правильным является переводить страницу в нужную кодировку сразу после загрузки; однако, в нашем случае можно переводить кодировку итоговой таблицы. Благодаря этому можно сэкономить много времени на переводах ненужных частей исходных файлов.
Также, можно было парсить страницы без применения модуля Cheerio — просто строковыми функциями. Наверняка это было бы быстрее, но выглядит как велосипедное решение.
Предостережение: не нужно создавать большую нагрузку на сайты, чем реже вы их запрашиваете — тем лучше. Также не стоит загружать сразу все нужные страницы, как в моём простом примере — лучше распределить это во времени. Поскольку у нас уже используется модуль cron, им это сделать очень легко: допустим, первый файл загружайте по заданию «* 10 * * *», второй — «* 11 * * *», и так далее. 10 файлов загрузятся за 10 минут, а по заданию «* 21 * * *» вы их все сохраните в файл. Ещё и промизы не понадобятся.