Нано-агрегатор новостей. Учимся парсить сайты с помощью phpQuery

июнь 17 , 2017
Демо Исходники

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

Для примера рассмотрим простую, уже довольно старую, но надежную библиотеку для парсинга - phpQuery. А чтобы было не скучно просто читать методы библиотеки, то запилим маленький проектик, который громко назовем "Агрегатор новостей".

В качестве примера возьмем новости футбола. А брать их будем с сайта bombardir.ru, на который я иногда заглядываю. Будем вытаскивать у них главные новости и статьи из центральной колонки и отображать их на нашей демо страничке (в целом, новостные агрегаторы так и работают). Посмотрите на исходный сайт, потом что получилось у нас, и если Вам интересно, как вытащить нужный контент, читайте статью дальше.


Идея агрегатора новостей

Посмотрите на главную страницу футбольного сайта. Нас интересуют 2 момента: главные новости (1) и статьи из центральной колонки (2). Мы хотим вытаскивать их с этого сайта и отображать у себя в нужном виде - вот так. Давайте допустим, что нам нужно именно так :-)

В главных новостях нам понадобится заголовок новости и ссылка на нее. Из статей можно вытащить инфы побольше: заголовок, ссылку, картинку и описание.

Ссылки будут вести на bombardir.ru, заморачиваться и перетаскивать содержимое новости к себе в тестовом проекте мы не будем. Хотя к концу урока Вы легко сможете это сделать сами.

И конечно же, эти новости и статьи будут парситься динамически. То есть, открыв демо-страницу, мы увидим актуальный контент - все то же самое, что и выводится на бомбардире.

Итак, идею обозначили, теперь немного о библиотеке парсинга.


Библиотека phpQuery. Основы.

Скачать библиотеку можно на гитхабе или найти в исходниках проекта. Это один-единственный php-файл.

Смысл либы очень простой: на основе html-кода страницы создается объект, в котором разными методами можно проводить манипуляции с dom-элементами. Да, именно в php-коде. Можно искать нужные элементы, добавлять новые узлы, менять их местами. В общем, практически все, что позволяет делать jQuery на клиенте. Библиотеки очень похожи в плане идеологии, названия многих методов и селекторов совпадают. Это мы увидим чуть ниже, когда будем писать парсер.

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

    $doc = phpQuery::newDocument($html);

И второе, как вытащить элементы по нужным селекторам

    // Главные статьи
    $articlesItems = $doc->find('.soc-main-article');

По сути все. Селекторы, как видим, такие же, как и в привычном css или jQuery.


Чек-лист того, что нужно сделать.

Перечислим, какие пункты нам нужно последовательно выполнить.

  • — подключить библиотеку phpQuery
  • — получить html-код главной страницы с bombardir.ru
  • — на основе полученного кода создать объект phpQuery
  • — вытащить из этого объекта данные о главных новостях и записать в массив
  • — аналогично со статьями из центральной колонки
  • — передать полученные данные в шаблон, чтобы не мешать логику и представление
Давайте же реализуем все пункты на практике.


Создаем проект и подключаем библиотеку phpQuery

В корень проекта закиньте файл библиотеки phpQuery.php и создайте рядом index.php. Теперь нужно в index.php подключить библиотеку, это самое простое.

    include './phpQuery.php';

Получаем html-код главной страницы с bombardir.ru

Тоже несложно.

    // Общие данные
    $site = 'https://bombardir.ru';
    $protocol = 'https:';
    $html = file_get_contents($site);

Заодно создали переменные $site и $protocol - они понадобятся при формировании ссылок.


Создаем объект phpQuery

Спойлер, как это делается, уже был.

    // Документ phpQuery
    $doc = phpQuery::newDocument($html);

Вытаскиваем данные о главных новостях

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

Каждая новость находится в блоке со ссылкой .soc-text a. Из этой ссылки мы вытащим и заголовок новости, и саму ссылку. Теперь знаем, что именно нам нужно, пишем код

    // Главные новости
    $newsItems = $doc->find('.soc-news:first li');
    
    // Перебираем новости, вытаскиваем заголовки и ссылки
    $news = array();
    foreach ($newsItems as $newsItem) {
        // Находим нужный элемент
        $newsElem = pq($newsItem)->find('.soc-text a');
        
        // Вытаскиваем атрибуты
        $title = $newsElem->text();
        $link = $newsElem->attr('href');
        
        // Добавляем url сайта при необходимости
        if (strpos($link, $site) === false) {
            $link = $site . $link;
        }
    
        // Сохраняем результаты в массив
        array_push($news, array(
            'title' => $title,
            'link' => $link
        ));
    }

Разбираемся. Сначала в переменную $newsItems заносим элементы li новостей. Обратите внимание на селектор .soc-news:first. Все же некоторые отличия от синтаксиса на клиенте есть - в css используется свойство :first-child.

Затем создаем пустой массив $news, в который запишем нужные данные.

В цикле на каждый элемент li создаем объект $newsElem. Функция pq нужна, чтобы мы могли у только что созданного объекта вызывать find-методы. Затем вытаскиваем заголовок и ссылку в $title и $link и производим небольшую манипуляцию со ссылкой. Суть в том, что иногда на бомбардире ссылка дается абсолютная, иногда относительная. Нам же всегда нужны абсолютные ссылки, поэтому стоит проверить наличие строки домена в ссылке и если такового нет, дописать вручную.

И в конце закинем полученные данные в ассоциативный массив $news и проделаем ровно то же самое со статьями из центральной части сайта.


Вытаскиваем статьи из центральной колонки

Алгоритм работы ровно такой же, как и с новостями. Только в статьях у нас добавляются картинки и описания статей. Думаю, проговаривать заново смысла нет, поэтому сразу код.

    // Главные статьи
    $articlesItems = $doc->find('.soc-main-article');
    
    // Перебираем статьи, вытаскиваем картинки, заголовки, тексты и ссылки
    $articles = array();
    foreach ($articlesItems as $articleItem) {
        // Находим нужный элемент
        $articleElem = pq($articleItem);
        
        // Вытаскиваем данные
        $image = $articleElem->find('.soc-cover-img')->attr('src');
        $title = $articleElem->find('.soc-article-name')->text();
        $text = $articleElem->find('.soc-article-text a')->text();
        $link = $articleElem->find('.soc-article-text a')->attr('href');
    
        // Добавляем протокол или url сайта при необходимости
        if (strpos($image, $protocol) === false) {
            $image = $protocol . $image;
        }
        if (strpos($link, $site) === false) {
            $link = $site . $link;
        }
    
        // Сохраняем результаты в массив
        array_push($articles, array(
            'image' => $image,
            'title' => $title,
            'text' => $text,
            'link' => $link
        ));
    }

В случае с картинкой проверяем и дописываем протокол, потому что бомбардир указывает адреса картинок, начиная с двух слэшей.

Теперь у нас в двух массивах $news и $articles есть все данные, о которых мы договаривались. Осталось их отобразить в более-менее приличном виде.


Создаем шаблон

Создадим в корне проекта пока пустой файл template.php и подключим его в index.php

    include './template.php';

С index.php закончили, открываем template.php. Там будет обычный html-код с php-вставками. Содержимое файла с небольшой смесью php и html-кода, по традиции, картинкой (для копипасты кода просто скачайте исходники)

Это обычный html-файл. Таблица из двух колонок: новости и статьи. В циклах перебираются соответствующие массивы и выводятся нужные данные из них. В css/main.css подключены стили, чтобы выглядело не так страшно. Впрочем, они особо никому не интересны, а если будете копипастить, то можно прямо здесь.

И на этом работа закончена!


Что в итоге?

У нас получился довольно простой, но забавный агрегатор футбольных новостей. phpQuery оказалась очень легкой в освоении. Конечно, мы затронули очень малую ее часть, но для парсинга любого сайта этого достаточно.

Вариантов применения полученной информации масса.

  • — собирать новости с какого-нибудь mail.ru (идея не новая, но все же)
  • — горячие акции и скидки в интернет-магазинах (на этом вообще стартапы мутят)
  • — курс валют прямо с главной страницы яндекса (адекватные люди, конечно, возьмут открытую xml-ку с сайта центробанка, но всегда быть адекватными скучно)
  • — и еще, и еще... Допишите свои варианты :-)

Высказывайте идеи в комментариях и подписывайтесь на рассылку, чтобы не пропустить новые статьи. И кому не лень, небольшой опрос в тему.

Демо Исходники
Заходите в группу в контакте - https://vk.com/webdevkin
Анонсы статей, обсуждения интернет-магазинов, vue, фронтенда, php, гита.
Истории из жизни айти и обсуждение кода.
Как Вам статья? Оцените!