0,8
рейтинг
9 сентября 2009 в 17:18

Разработка → парсим сайты легко и непринуждённо вместе с phpQuery

Привет хабр, думаю у некоторых из вас возникала потребность стянуть что нибудь нужное с какова нибудь сайта. Но писать тонны функций, чтобы вытащить фразу, это очень муторно, долго и не продуктивно.
Представляю вашему вниманию phpQuery. Это php вариант известного javascript фраемворка — jQuery.
Автор постарался на славу и портировал почти всё что нам нужно.
  • Selectors
  • Attributes
  • Traversing
  • Manipulation
  • Ajax
  • Events
  • Utilities
  • Plugin ports

Давайте посмотрим что она умеет.

А умеет она всё, что умеет её старший брат jQuery.
Собственно, для начала, далеко ходить не будем и остановимся на %username%.habrahabr.ru/blog/

Имеется довольно много способов сделать это.
phpQuery::newDocument($html, $contentType = null) Создаём новый документ из разметки. Если не указали $contentType, будет определен основываясь на разметке. Если не получится то будем считать что это, text/html в utf-8.
phpQuery::newDocumentFile($file, $contentType = null) Создаём новый документ из файла. Работает также как и newDocument()
phpQuery::newDocumentHTML($html, $charset = 'utf-8')
phpQuery::newDocumentXHTML($html, $charset = 'utf-8')
phpQuery::newDocumentXML($html, $charset = 'utf-8')
phpQuery::newDocumentPHP($html, $contentType = null) Подробнее можете почитать об этом здесь.
phpQuery::newDocumentFileHTML($file, $charset = 'utf-8')
phpQuery::newDocumentFileXHTML($file, $charset = 'utf-8')
phpQuery::newDocumentFileXML($file, $charset = 'utf-8')
phpQuery::newDocumentFilePHP($file, $contentType) Подробнее можете почитать об этом здесь.

Ну а мы, далеко ходить не будем. Давай %username%, распарсим твои записи в блоге. Сначала скачаем phpQuery. Теперь создаём что то вроде index.php
<?php
   require ('phpQuery/phpQuery.php');
   
  $habrablog = file_get_contents('http://%username%.habrahabr.ru/blog/');
  
  $document = phpQuery::newDocument($habrablog);
  
  $hentry = $document->find('div.hentry');
  
  foreach ($hentry as $el) {
    $pq = pq($el); // Это аналог $ в jQuery
    $pq->find('h2.entry-title > a.blog')->attr('href', 'http://%username%.habrahabr.ru/blog/')->html('%username%'); // меняем атрибуты найденого элемента
    $pq->find('div.entry-info')->remove(); // удаляем ненужный элемент
    $tags = $pq->find('ul.tags > li > a');
    $tags->append(': ')->prepend(' :'); // добавляем двоеточия по бокам
    $pq->find('div.content')->prepend('<br />')->prepend($tags); // добавляем контент в начало найденого элемента
  }
  
  echo $hentry;
?>

Это всего лишь малая часть того что возможно сделать.
Также вместе с ней поставляется такая штука как jQueryServer. По сути, это тоже самое что и phpQuery, но на стороне клиента.
Пример из демки
<script type="text/javascript">
jQuery.serverConfig.url = '/phpQuery/jQueryServer/jQueryServer.php';
function demo() {
  $.server({
      url: document.location.toString(),
      dataType: 'json'
    })
    .find('li')
    .client(function(response){
      $.each(response, function(k, li){
        $('ul').append(li);
      });
    });
}
$(function(){
  $('ul').append('<li>above LIs will be downloaded and appended below in 2 seconds...</li>');
  setTimeout(demo, 2000);
});
  </script>

Данный вариант довольно практичный и позволяет распарсить контент с нескольких сайтов в несколько секунд, не утруждая себя в написании php кода.
Материалы по теме

Google Code
Официальный блог

Если вам интересно в следующей статье я хочу рассмотреть парсинг сайтов доступных только авторизованным пользователям (без капчи конечна). Да, phpQuery умеет и это, правда не без помощи Zend Framework.
Николай Костюрин @JiLiZART
карма
33,0
рейтинг 0,8
Fullstack Web Developer
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (35)

  • +1
    Спасибо, интересно было почитать. А по поводу производительности — вы сравнивали насколько это быстрее/медленнее чем парсить используя регекспы?
  • 0
    сегодня с утра как раз ручками писал… особо писать нечего кроме регулярки
  • 0
    работает. но кривота гербедж коллектора в php такова что на парсинге сайтов в 20 000 страниц не успевает удалять объекты (которые удаляются implicitly) скрипт отжирает до гига памяти. мораль — учим XPath & DOMXML
    • 0
      Таким способом «XPath & DOMXML» память намного экономнее используется?
      • +1
        ну метров 102-105 на неоптимизированном php-cli. скорость работы тоже на пару порядков выше — 20-30 минут вместо 4 часов на phpquery. да и кода меньше получается.
        • 0
          Спасибо за консультацию, тоже в принципе достаточно много, но как говорится, за удобство приходится платить. Если памяти мало, то мой вывод — лучше использовать регулярные выражения.
          • 0
            это user memory на процесс. memory_get_usage дает от десяти до 30 мегабайт.

            ну хз хз. ругйлярки мне не подошли, потому что приходилось парсить выражения типа «h2 который находится в том же div что и третья таблица в документе» — без классов и id. xpath это делает одной строкой — //table[3]/../h2
  • 0
    И еще интересно, как на счет потребления памяти по сравнению с регулрными выражениями?
    Я конечно не тестировал, но предполагаю, что ругулярки будут выигрывать на порядок.
    • 0
      Регулярные выражения не подходят для парсинга xml-подобного документа. Дело не в скорости, а в сложности.
  • 0
    Штука очень забавная. Правда не без глюков.
    А насчет регулярок — да, быстрее, но с ней гораздо проще, особенно при разборе сложных докментов.
    • 0
      Именно это удобство меня и зацепило в ней.
      Насчёт глюков, библиотека ещё молодая, если судить по дате публикации архивов =) Да и документированность плохая, радуют «Enter description here...» в коде =)
  • +1
    А если документ невалидный в плане хмл?
    • 0
      Т.к. phpQuery использует DOMXML & XPath внутри, то и отношение к malformed xml у него аналогичное, поэтому наверное лучше перед загрузкой прогнать через Tidy
  • 0
    Хороший инструмент. спасибо!
    Как бы ещё добраться до контента, генерируемого динамически, например тем же Javascript'ом?
    • +1
      тем же javascript'ом и добраться :)
      передать на сервер можно через ajax
      • 0
        Не совсем понял что Вы Имеете в виду. Вы предлагаете с помощью phpquery решить этот вопрос или «вручную» разбираться в ява скрипте? А если там какое-либо сложное высчитывамие переменных для запроса или не запрос вовсе, а какой-нибудь document.write, но опять-таки со сложновысчитанными параметрами?
        • 0
          Вижу очень безумную идею, расширение к firefox, которое дампит данные по факту загрузки страницы и отсылает их на сервер. где они уже и сохраняются, при этом можно firefox автоматизировать, пусть сам ходит по страницам и отсылает данные, жаль что это клиент side
        • +1
          нет. Если проблема частного характера, то можно тем же jquery повесить обработчики и генерируемый контент пересылать на сервер.
          Если общего и неизвестно какие функции генерируют контент, можно проверять блоки данных на изменение с первоначальной информацией.
          вообще к задаче лучше подходить практически — решать конкретно то что нужно делать. потом и обобщить можно…
          • 0
            Можете привести пример задачи общего характера?
            • +1
              притянутый за уши только :)
              к примеру есть несколько сайтов с разной структурой, где стоят какие то информеры, которые тянуться уже после загрузки страницы. расположение неизвестно.

              имхо, даже частная задача будет встречаться редко и писать для этого отдельную программку смысла нет, видимо по этому и нету такого в phpQuery
              • 0
                А как Вы предлагаете
                проверять блоки данных на изменение с первоначальной информацией

                если они подгружатеся динамически уже после загрузки страницы?
                • 0
                  без понятия. надо рассматривать конкретную задачу :)

                  ***теоретически… допустим у нас есть сайт, которые разбит по блокам с какими то id
                  при первом парсинге запоминаем эти блоки и их длину (можно на клиенте str.length)
                  проходимся опять по блокам и сраниваем их длину. если контента стало больше — появилось свеженькое.
  • 0
    Афигенная штука, если надо распарсить сложный древовидный документ. На одной странице работает терпимо, а весь сайт парит медленно. За удобство приходится платить?

    Думаю, это библиотека подходит для использования в условиях выделенного сервера с возможностью запуска PHP без ограничения памяти и времени исполнения, а на виртуальном хостинге — обрывается.
    • 0
      Как вариант работа с домашнего сервера с предварительной загрузкой страницы через wget.
      + прикручиваем потоки для php, время должно сократиться, а вот загрузка (как памяти так и процессорных мощностей) может возрасти, вопрос только в каких пределах.
      • 0
        А почему именно wget'ом хотите забирать, а не, например, curl'ом?
        • 0
          а) Функционала больше.
          Иногда необходимо сдёрнуть всю страницу + уровни лежащие ниже, или же отзеркалить структуру в уже сущевствующую папку для проверки произошедших изменений.

          б) привык =)

          Хотя иногда проще парсить страницы на лету perl + regex'ы
  • 0
    При выводе скаченной страницы без парсинга все выклядит нормально, после парсинга бьются русские буквы? как решить эту проблему?
    • 0
      возможно сам фаил скрипта не в той кодировке или кодировка сайта отлична от UTF-8
      • 0
        в том то и дело, что сайт который паршу — утф, скрипт — утф, все утф
        • 0
          Проверяйте настройки DOM расширения для php или попробуйте на другой установке Php
          • 0
            в локале (MacOSX Lion) и на сервере (Debian) результат одинаков
    • –1
      echo iconv("windows-1251", "utf-8", $content);
      • 0
        Да вы не просто капитан, вы подполковник очевидность. Проблема была с разной разрядностью символов.
        • 0
          как исправил?
    • 0
      уберите указание кодировки в коде

      $doc = phpQuery::newDocumentHTML($data['document']);
      

      Мне помогло

      	public static function newDocumentHTML($markup = null, $charset = null) {
      		$contentType = $charset
      			? ";charset=$charset"
      			: '';
      		return self::newDocument($markup, "text/html{$contentType}");
      	}
      

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.