C++ и парсинг HTML на Qt

C++ и парсинг HTML на Qt

Здравствуйте. Сегодня я опишу создание приложения для слежения за посылками от доблестной и уважаемой “Почты России”. Я раскрою такие темы:

С++ & QtCreator

Разбор DOM-дерева полученной страницы

Работа с QDateTime, получение текущей даты в Qt

Работа с FireBug

Работа с формами

Отслеживание POST-запросов

Статья написана для одного моего замечательного друга — Алексея. Надеюсь, он поймет и усвоит весь материал, который я опишу в этой статье.

Подготовка

Для начала разберемся с инструментарием и разобьем работу на шаги. В качестве ЯП я выбрал C++, в качестве GUI/рендера — любимую библиотеку Qt. Все это будет кодиться и собираться под QtCreator. Благодаря выбранному инструментарию программа получится полностью кроссплатформенной.

Опишу процесс работы программы: окно с полем ввода для номера посылки, кнопка. По нажатию на кнопку получаем неведомым образом таблицу из сайта Почты России и выводим в нашем окне через веб-компонент (и правда, зачем парсить возвращаемую html-таблицу, перерисовывать на свой интерфейс в программе, если можно взять и отобразить этот html прямо в окне).

Конечно же, я опишу процесс “вытаскивания” значений из таблицы для дальнейшей обработки (email оповещение и т. д.).

Разбираем сайт

Вот ссылка, по которой можно получить состояние посылки: http://www. russianpost. ru/resp_engine. aspx? Path=rp/servise/ru/home/postuslug/trackingpo.

По её структуре сразу заметно, какого качества программисты писали им сайт.

FireBug и все-все-все

Как я уже не раз писал в своих статьях по парсингу, нам понадобится удобный плагин под FireFox — FireBug. С его помощью мы сможем отследить куда и какой запрос идет, чтобы получить нужный ответ. Другими словами, после ввода трекинг-номера в форму мы сможем увидеть, куда посылается POST-запрос и какие параметры посылаются вообще. Красным я выделил нужный нам POST-запрос, давайте посмотрим на детальную информацию о нем (нажмите просто). В появившемся поле перейдите на вкладку POST, там увидите поля с их значениями, которые передаются запросом. На вкладке RESPONSE будет возвращаемый html, который и содержит таблицу с информацией.

BarCode — поле, которое содержит наш трекинг-номер. Ещё, как видно, CDAY, CMONTH, CYEAR содержат информацию о текущей дате (странные они, зачем пост-запросом отправлять информацию, если это же можно получить в скрипте на сервере автоматически).

HTML в Response-вкладке мы пока рассматривать не будем, вернемся к этому позже.

С++&Qt

Имя кнопки — doCheck, поля ввода: trackingNumber, QWebView (белое большое поле, в котором будем отображать html) — htmlData.

Ещё момент, чтобы использовать QWebView (компонент, использующий webkit для рендера html), нам нужно открыть pro-файл проекта и добавить 2 подключения 2 модулей (network, webkit):

QT += core gui network webkit xml

Нажмите правой кнопкой по кнопке и выберите пункт Go to slot... В появившемся окне выберите первый сигнал — clicked(). После нажатия автоматически сгенерируется слот, в котором нужно отписать код, реагирующий на нажатие кнопки.

POST запрос средствами Qt

Есть неплохая статья (http://vasinnet. blogspot. com/2010/01/post-qt-qnetworkaccessmanager. html) В ней описывается отправка POST-запросов с помощью QNetworkAccessManager.

Для осуществления запроса нам нужно получить данные: трекинг номер из поля ввода, текущую дату (частями). Первое нужно реализовать в слоте-обработчике нажатия кнопки (мы его создали в предыдущем пункте).

Текущая дата в Qt

QDate curDate = QDate::currentDate();

QString day = QVariant(curDate. day()).toString();

QString month = QVariant(curDate. month()).toString();

QString year = QVariant(curDate. year()).toString();

С помощью этого кода мы можем получить текущую дату (день, месяц, год). Подробнее можно почитать в документации по QDateTime классам. Также отмечу, что нормального метода перевода int? QString я не нашел, поэтому приходится извращаться, переводя int в QVariant (аналог boost::any), а его уже в строку.

Генерация POST-запроса

// Получаем трекинг-номер из поля ввода

QString number = ui->trackingNumber->text();

// Составляем пост-запрос. Основу для строки я взял из FireBug'a в поле POST-запроса

QString postRequest = "OP=&PATHCUR=rp/servise/ru/home/postuslug/trackingpo&PATHFROM=&WHEREONOK=&ASP=&PARENTID=&FORUMID=&"

"NEWSID=&DFROM=&DTO=&CA=&CDAY=" + day + "&CMONTH=" + month + "&CYEAR=" + year + "&NAVCURPAGE=&"

"SEARCHTEXT=&searchAdd=&PATHWEB=RP/INDEX/RU/Home&PATHPAGE=RP/INDEX/RU/Home/Search&search1=&"

"BarCode=" + number + "&searchsign=1"; qDebug() << postRequest;

Первой строкой я получаю трекинг-номер из поля ввода, а далее я генерирую post-запрос. Как я уже писал раньше в своих статьях, post-запросы передаются в виде: name1=value1&name2=value2&...

Строку, которую вы видите вверху, я получил из FireBug'a в разделе POST-запроса, в параметрах (выше есть скрин, в самом низу сгенерирована эта строка). Только нужно заменить данные, например CDAY, CMONTH на наши переменные.

Отправка POST-запроса

// Вот здесь находится скрипт, обрабатывающий пост-запросы

QString siteUrl = "http://www. russianpost. ru/resp_engine. aspx? Path=rp/servise/ru/home/postuslug/trackingpo";

QNetworkAccessManager *pManager = new QNetworkAccessManager; connect(pManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinish(QNetworkReply*))); pManager->post(QNetworkRequest(QUrl(siteUrl)), postRequest. toUtf8());

Отправляется пост-запрос вот таким вот простым кодом. Всем этим управляет класс QNetworkAccessManager. В нем есть сигнал finished, вызывается, когда запрос завершен и получен ответ. В качестве слота нужно создать: private slots: void replyFinish(QNetworkReply*);

В классе вашего окна, и именно к этому слоту подключать сигнал. (Кто не понял, к статье приложу архив проекта).

Обработка POST-ответа void MainWindow::replyFinish(QNetworkReply *reply)

{

QString answer = QString::fromUtf8(reply->readAll());

//qDebug() << answer;

QDomDocument doc; doc. setContent(answer);

QDomNodeList tables = doc. elementsByTagName("table");

QDomNode table1 = tables. item(9);

QDomNode table2 = tables. item(10);

// … Далее будет

}

В классе ответа QNetworkReply есть метод readAll, с его помощью можно получить текстовую составляющую ответа. Если теперь вывести переменную answer, то вы увидите html-код вернувшейся страницы, на которой находится наша таблица. Вся задача — вытащить эту таблицу.

Работа с DOM

Рассказ о базовой работе с DOM и QDocument занял бы много времени, поэтому я взамен буду только предлагать ссылки на документацию.

SetConent из QDomDocument позволяет вручную установить html-содержимое. Теперь у нас есть некоторые функционал для манипулирования и поиском в DOM-дереве.

Кто хочет “крутого” кода, можно с помощью firebug'a найти таблицу в html'e и посмотреть её класс или прочие атрибуты, по которым можно искать, но чтобы не усложнять статью я методом тыка перебрал все таблицы и нашел нужные мне.

Функция getElementsByTagName возвращает нам все хендлы на переданный тег (в нашем случае — все таблицы). Перебором, как я уже говорил, нашел таблицу с информацией по отправке).

QDomNode в строку

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

QString htmlTable;

QTextStream stream(&htmlTable); table1.save(stream, 2); table2.save(stream, 2); qDebug() << htmlTable;

В классе QDomNode есть метод: save, он принимает на вход stream (поток, например: файл, строка, ввод) и количество пробелов для отделения между тегами (форматирование кода). Как видите по коду, я подцепил строку htmlTable к TextStream и передал этот поток в save-функцию. На выходе я получил строку htmlTable, внутри которой html-отображение 2 таблиц.

Осталось самое простое — передать этот html в браузер, делается это одной строкой: ui->htmlData->setHtml(htmlTable);


Карта сайта


Информационный сайт Webavtocat.ru