MODx и парсер таблички с чужого сайта

    Наверное любой программист слэш администратор сайта сталкивается с проблемой импорта данных с чужих сайтов. Задача эта очень тривиальна, и не требует каких-то особых знаний… вопрос только в обертке. Чтобы дополнить коллекцию статей по MODx пишу эту статью, быть может кому и пригодится.
    Внимание! Никакой практической ценности данная запись не несет, только теоретическую нагрузку а-ля «Простой пример работы с back-end'ом MODx».
    А задача была: таблицу со страницы example.com распарсить, переоформить и себе на сайт засунуть.

    Пять копеек


    Собственно пользоваться мы будем cURL'ом, поскольку для этой цели оптимальнее инструмента не найти. Перво-наперво мы создадим два шаблона parserTplOuter и parserTplInner — соответственно обертка и её «внутренности». Лично я делал таблицу, поэтому в примере будем на это ориентироваться, но никто не запрещает сделать дивы с заданными стилями.

    parserTplOuter
    <table>
        [+content+]
    </table>
    

    Обертка вынесена отдельно специально, чтобы можно было прописать внешний вид, расположение элементов или еще какие-то верстальские штучки не залазя в код. Придерживаемся модели MVC свято!

    parserTplInner
    <tr>
         <td>[+0+]</td><td>[+1+]</td><td>[+2+]</td><td>[+3+]</td>
    </tr>
    

    Тут хочется пояснить почему я сделал числовые идентификаторы: во-первых, при разработке ассоциативные массивы просто не потребовались :) а во-вторых, хоть и существует шанс запутаться, но зато получается некая стандартизация, когда очень просто добавить еще один элемент в «ряд».

    Ну что ж, получили банки, давайте же их наполним соком и создадим сниппет, который назовем parser:

    <?php
    
    if (empty($url)) return false; //если сниппету не дать адреса - завершить исполнение
    // тут надо сделать еще кучу проверок на валидность урла, но мы то знаем что зла не напишем сюда 
    // и балбесам не дадим доступа ;)
    
    $tplInner = (empty($tplInner)) ? 'parserTplInner' : $tplInner;        // Зададим умолчательные
    $tplOuter = (empty($tplOuter)) ? 'parserTplOuter' : $tplOuter;   // чанки для сниппета
    
    $c = (empty($count) || (!is_numeric($count)))? 6 : $count;        // Сделаем некое ограничение по кол-ву записей
    $c = ($c>100) ? 100 : $c; // максимальное кол-во
    $c = ($c<1) ? 1 : $c;         // минимальное кол-во
    
    // инициализация сеанса курла
    $ch = curl_init();
    
    // установка URL и других необходимых параметров
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_TIMEOUT, 5);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    
    // загрузка страницы и выдача её в переменную
    $html = curl_exec($ch);
    
    // завершение сеанса и освобождение ресурсов
    curl_close($ch);
    
    if (mb_strlen($html) < 100) {return '';} //если ответ слишком короткий для обработки выходим.
    //тут надо быть осторожным, поскольку вернутся может самый разнообразный код, но стандартные обрамления HTML в сумме дают примерно 100 символов..
    
    $pattern = "/<table(?:[^>]+)>([\s\S]+)<\/table>/i";  //эта часть вырежет нам все таблицы с сайта. 
    // теоретически этот шаблон можно запихнуть в чанк и там его менять, но как показывает практика все равно парсинг будет более-менее уникальный для каждого случая, и проще на основе кода написать свой, чем подгонять параметры так, чтобы получился корректный результ...
    
    preg_match($pattern, $html, $matches);
    unset($matches[0]); // кто не помнит - в 0 элемент записывается найденая строка целиком и она нам не нужна
    
    $array = explode('</tr>', $matches[1]); //тут я пошел очень хитрым способом и преобразовал все ряды в элементы массива 
    
    $separator = '|==|'; $table = array();   // подготовился и...
    
    foreach ($array as &$value) {
      //(быдлокод в действии) 
        $value = str_replace('</td><td', '</td>'.$separator.'<td', $value); // ... сделал финт ушами :) 
        $value = strip_tags($value); //регулярками убирать тэги таблицы было лениво, проще разделить ячейки служебным набором символов и..
        $table[] = explode($separator, $value); //... разбить оставшееся от strip_tags по этому разделителю
    }
    
    $i = 0; //здесь стоит отметить, что 0й элемент это заголовки таблицы. если они не нужны, то следует поставить 1 и unset($table[0]); прописать
    $rows = '';
    
    foreach ($table as $row) {  
    //ну теперь пробегаем по каждому ряду 
      if ($i++ > $c) break; //сверяемся со счетчиком записей
      $rows .= $modx->parseChunk($tplRow, $row, '[+', '+]'); //записываем ряд
    
    }
    
    echo $modx->parseChunk($tplTable, array('content'=>$rows), '[+', '+]'); // вставляем ряд и выводим результат
    ?>
    

    Собственно это все что и требовалось, теперь вставляем в нужном месте на сайте такое:
     [[parser?tplInner=`parserTplInner` &tplOuter=`parserTplOuter` &url=`http://example.com` &count=`10`]] 

    и видеть сформированную аккуратную табличку.

    Стопроцентно ЭТОТ скрипт никому не понадобится, но при наличии головы и серого вещества в ней очень легко адаптировать данный сниппет под свои нужды. Сменить шаблон или механизм обработки вполне просто, вместо обработки таблицы прописать вырезание дива — все равно любой парсинг подоразумевает, что сайт-цель имеет какую-либо статичную структуру. И уже ориентируясь по ней мы получаем данные для обработки, а как их отобразить для решения поставленной задачи уже совсем другая головная боль.
    Поделиться публикацией

    Похожие публикации

    Комментарии 9

      0
      Было бы хорошо, если бы вы для примера что-нибудь полезное распарсили, что бы пример хоть кому-то но пригодился ;)

      А также добавили бы пояснений по структуре MODx API.
        0
        Что например? С удовольствием добавлю в статью, а про MODx API тут используется всего 1 метод и 1 подход с передачей данных в сниппет, причем про метод написано в документации.

        Так уж получилось, что задача очень простая, но даже такой не рассматривается на хабре (да и вообще нигде в МОДх сообществах). По своему опыту могу сказать, что мне подобные записи помогли бы в начале пути очень сильно.
        +1
        Поправил под себя, если в ячейке встречался тег br то он схлопывался, ну и трим функцию добавил — малоли что там в табличках нагородили

        	foreach ($array as &$value) {
        	  //(быдлокод в действии) 
        		$value = str_replace('<br />', ' ', $value);
        		$value = str_replace('</td><td', '</td>'.$separator.'<td', $value); // ... сделал финт ушами :) 
        		$value = trim(strip_tags($value)); //регулярками убирать тэги таблицы было лениво, проще разделить ячейки служебным набором символов и..
        		$table[] = explode($separator, $value); //... разбить оставшееся от strip_tags по этому разделителю
        	}
        

          0
          Тогда уж, по правильному надо заменять на \n, а потом после обработки делать nl2br(); иначе переводы строк пропадают…
          0
          Всю жизнь для выдирания пользовался domxml, имхо, без таких извратов(а если сложные вложения и т.п.), и в 90% правильно обрабатывает неправильный html.

          $array = explode('', $matches[1]); А ЕСЛИ ЗАБЫЛИ TR ЗАКРЫТЬ???

          Курл работает через пятую точку, нет обработки 404 и прочих ошибок, а хорошо бы логировать ошибки операций.

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

          Для нормальной работы надо:
          1) Вынести в отдельные классы работу с url и парсер
          2) Сделать более гибкую возможность настройки парсинга
          3) Учесть, что код может быть невалидным

          P.S. я повторюсь -домом парсинг делается на порядок легче.
            –1
            я придерживаюсь пути «наименьшего сопротивления» и сторонник теории, что для выполнения операции 2+2 не надо писать сложный класс математических операций :)

            Так или иначе, любой парсинг страницы достаточно уникален для каждой конкретной задачи, он просто не может быть шаблонным в принципе — даже если разбирать страницу форума vbulletin для разных стилей может быть разное html-окружение поста. Поэтому в процессе разработки проводится «пилотный» режим настройки.
              0
              Такие пути наименьшего сопротивления называются Индийский код. У меня есть наработка, кторая достаточно быстро настраивается под любой сайт и парсит ну оочень много страниц. Только я вам ее не дам, потому что у вас документов нет т.к. такая корова нужна самому т.к. не хочу палить некоторые идеи :)
              И снова повторюсь: xpatch намного проще пишется чем регулярки.
              0
              Вы плохо читаете документацию по курлу, есть ф-я curl_getinfo которая возращает http_code и массу другой полезной инфы, а дальше обрабатывайте как хочете.
                0
                alecksey, возможно я плохо выразился, я не критикую курл, я критикую написанный выше код, я как раз и говорю про то, что надо обрабатывать 404, 500 и прочие ошибки, а так же учитывать редиректы

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

            Самое читаемое