Парсер сайта на Qt, использование QRegExp
В одной из предыдущих статей я рассказывал как написать простой парсер сайта средствами библиотеки Qt: Разработка парсера биржи фриланса. В статье описано как получить html-код страницы сайта при помощи QNetworkAccessManager . Однако, парсер биржи был достаточно простым, поэтому в слоте, отвечающем за извлечение из страницы нужных нам данных, мы ограничились поиском подстрок функцией QString::indexOf() .
Теперь мы возьмем немного более сложный пример — будем извлекать информацию о фотографиях с сайта `goodfon.ru`, а затем мы разместим картинки на виджете. В результате наше приложение будет выглядеть примерно следующим образом:
Я опишу класс QRegExp , с помощью которого мы можем искать в тексте фрагменты, соответствующие регулярному выражению и немного расскажу про сами регулярные выражения — ту часть их синтаксиса, которая потребуется для реализации нашего парсера.
Итак, чтобы написать парсер сайта, мы должны:
- зайти на этот сайт через браузер;
- определиться с тем, что мы хотим оттуда взять;
- получить html-код страницы (во всех известных мне браузерах при клике по странице правой кнопкой мыши вы сможете выбрать в меню соответствующий пункт);
- найти в html-коде нужную вам информацию;
Проделав эти нехитрые манипуляции я получил следующий фрагмент:
Тут содержатся данные лишь одной из нужных мне картинок, я могу получить отсюда категорию картинки, ее размер, время добавления, ссылку на уменьшенное изображение и ссылку на страницу с исходным изображением.
Теперь мне надо определить как эти фрагменты сможет выделить из кода страницы программ. Очевидно, я не могу искать просто ссылки, ведь они могут вести куда угодно. Я убедился, что описания всех иконок начинаются с тега <div > и больше этот класс тега div нигде не встречается. В связи с этим, мой парсер будет искать на странице этот тег, получать его позицию (номер символа в исходном тексте страницы) и от нее начинать искать остальную нужную мне информацию.
Опишем структуру нашей иконки, включив в нее всю нужную нам информацию:
Опишем интерфейс класса-обработчика html-страницы:
Класс допускает создание экземпляра, вызов функции parse (с указанием адреса страницы, с которой необходимо взять данные), информирование клиента о результатах обработки страницы при помощи сигнала finished .
Конструктор класса создает экземпляр QNetworkAccessManager , выполняющего непосредственное получение данных с сайта и соединяет свой слот с его сигналом finished , содержащим ответ (html-код страницы):
Функция parse принимает адрес страницы и формирует запрос (QNetworkRequest ) к объекту QNetworkAccessManager :
После этого программа начинает получать данные с нужной вам страницы. Как только все данные будут получены, QNetworkAccessManager выработает сигнал и будет вызван связанный с ним слот:
Слот запускается с ответом сервера. Функцией readAll() все считанные данные могут помещены в строку ( QString ). Мы создаем список иконок, который затем функция распространит подписчикам при помощи сигнала (см. Мехнизм сигналов и слотов Qt и Паттерн MVC).
Выделение нужных данных из страницы происходит с помощью регулярного выражения (в библиотеке Qt для этого используется класс QRegExp ). Рассмотрим его подробнее (чтобы понять что тут описано, заглядывайте в фрагмент html-кода, описанный выше):
- <div >, в начале описания иконки на странице находится такой фрагмент. Этот фрагмент содержит кавычки, поэтому мы экранируем с помощью слеша — иначе кавычка будет распознаваться как конец строки, а не как символ;
- .*<a href=\"// , первая ссылка в описании иконки — это ссылка на страницу с подробным описанием картинки. Между тегом div, описанным выше и этой ссылкой может находиться любое количество символов — в регулярных выражениях символ точки соответствует любому символу, а символ звездочки означает, что символов (стоящих перед звездочкой) может быть любое количество (ноль и более). Затем идет ссылка, заключенная в кавычки и два слеша;
- ([^\"]+)\" , мы знаем что после ссылки стоит кавычка, поэтому нам надо считать все символы до нее. Фрагменты регулярного выражения, заключенные в круглые скобки — это то, что мы пытаемся извлечь — в данном случае нам нужна ссылка, поэтому в скобки помещен соответствующий ей фрагмент. Квадратные скобки в регулярном выражении задают возможные символы — например [123] будет соответствовать цифрам 1, 2 или 3. Однако, если первым символом в квадратных скобках является крышечка ( ^ ) — то выбираются любые символы, кроме перечисленных, например [^\"] будет соответствовать любому символу, кроме кавычки. Символ + работает также как звездочка, но требует, чтобы символов было больше нуля (хотя бы один символ). После всего этого, в строке идет еще одна кавычка.
Разбор остальных параметров иконки производится аналогично, но адрес изображения начинается с <img src , а не с <a href как у адреса страницы. В этом примере, вызовом функции setMinimal(true) мы установили чтобы QRegExp выбирал минимальное совпадение шаблона со строкой, по умолчанию выбирается максимальное (самое длинное совпадение). Таким образом, если не изменить параметр setMinimal, то <div > будет взят от первой иконки, а <a href=\"//([^\"]+)\" — уже от последней. Однако, из-за того, что мы установили этот параметр, мы обязаны явно указывать где наш шаблон должен окончиться: ([^\"]+)\" если мы уберем \" с конца, то по шаблону [^\"]+ будет выбран только один символ (ведь мы требуем минимальное совпадение с шаблоном).
Наш парсер должен перебрать все фрагменты, соответствующие иконкам и выделить из них нужную информацию, сделать это можно с помощью функции QRegExp::indexIn(QString, int) , которая выбирает в заданной строке первый фрагмент, соответствующий шаблону, начиная с указанной позиции. Функция возвращает позицию, начиная с которой встречается вхождение шаблона или -1 в случае, если шаблон не встречается.
Функция QRegExp::matchedLength() возвращает длину подстроки, совпавшую с шаблоном, это значение используется для изменения индекса элемента строки, с которого начнется поиск на следующей итерации цикла.
Описывая регулярное выражение мы использовали круглые скобки чтобы отметить те части, которые нам нужны. Обратиться к этим частям можно при помощи функции QRegExp::cap(int) , при этом целочисленный параметр задает номер нужной нам части шаблона, индексация начинается с единицы, т.к. cap(0) всегда ранит целиком строку, совпавшую с шаблоном.
Результат разбора html-страницы парсер помещает в описанную нами структуру и добавляет в список, а также для проверки выводит в qDebug() .
И так, наш парсер выполняет разбор страницы и результат (список иконок) передает подписчикам при помощи сигнала. Теперь нам надо создать подписчика, который будет помещать иконки в виде изображений на виджет. Для этого добавим в проект файл формы (.ui):
Выберем шаблон формы (в виде виджета):
Зададим имя класса формы:
Откроем форму в редакторе Qt Creator и добавим на нее QListWidget , установим имя объекта формы, например listWidget :
Чтобы наш QListWidget занимал всю площадь формы, нужно добавить компоновщик, в редакторе форм Qt Creator для этого достаточно щелкнуть на форму правой кнопкой мыши и выбрать Компоновка->по сетке (на сомом деле, в нашем случае подойдет любой вид компоновщика).
Итак, у нас в проект была добавлена форма (.ui-файл) и класс формы (размещенный в .cpp и .h файлах). В третьей части статьи Собственные виджеты в Qt Designer более подробно описано как можно использовать созданную нами форму.
Картинки, которые мы хотим отображать на виджете находятся не на нем компьютере, а на удаленном сервере — поэтому для их отображения используем PixmapLoader (см. Получить изображение с сайта средствами Qt). Объект типа PixmapLoader добавим в класс формы, а также поместим туда слот-обработчик сигнала загрузки картинки и слот-обработчик сигнала finished от нашего парсера (метод setIcons ):
В конструкторе класса формы нужно установить для объекта listWidget свойство IconMode и размер иконок, это нужно чтобы отображать картинки на QListWidget . Кроме того, нам нужно соединиться со слотом PixmapLoader:
В функции setIcons нужно инициализировать загрузку картинок с помощью PixmapLoader :
А в слоте-обработчике загрузки картинки — добавлять картинку на QListWidget :
Все, что нам остается сделать — создать в файле main.cpp объекты галереи и парсера и соединить их с помощью механизма сигналов и слотов:
После запуска приложение будет выводить нам в окошке иконки изображений с сайта, по крайней мере до тех пор, пока на сайте не изменится верстка. Взять исходный код проекта целиком можно в репозитории.