Парсер на PHP – это просто
Вебмастеры часто сталкиваются с такой проблемой, когда нужно взять с какого-либо сайта определенную информацию и перенести ее на другой. Можно сначала сохранить информацию на промежуточный носитель, а уже с него загрузить куда-либо, но подобный подход не всегда удобен. В некоторых случаях гораздо быстрее залить парсер на сам сайт, поддерживающий PHP и запустить его удаленно, чтобы он автоматически спарсил информацию и загрузил ее в базу данных ресурса.
Среди уже готовых решений имеются популярные вроде Content Downloader и ZennoPoster, они конечно очень удобны и понятны любому человеку, даже незнакомому с программированием, однако имеют некоторые минусы. К примеру, они платные и не обладают достаточной гибкостью, которую можно вдохнуть в обычный php скрипт. Тем более, что разработка сложного парсера на них нисколько не уступает по времени написанию аналога на php.
Еще есть такая бесплатная вещь как iMacros – скриптовый язык, который может эмулировать действия пользователя в браузере, но тоже не везде такой подход работает лучшим образом.
Многие думают, что программирование, и уж тем более написание парсеров, – очень сложное занятие. На самом деле php – один из самых простых языков, изучить который можно на достаточном уровне за пару недель или месяц.
Парсеры тоже просты в написании, именно поэтому начинающие программисты пишут именно их, чтобы освоить язык.
Первое, что приходит на ум человеку, который решил написать подобный скрипт, — нужно использовать функции для работы со строками (strpos, substr и аналогичные) или регулярные выражения. Это совершенно верно, однако есть один нюанс. Если парсеров нужно будет писать много, то придется разрабатывать свою библиотеку, чтобы не переписывать сто раз одни и те же конструкции, но на это уйдет тонна времени, а учитывая то, что уже существуют аналогичные библиотеки, такое занятие и вовсе оказывается бессмысленным.
Идеальным вариантом для новичка станет изучение библиотеки PHP Simple HTML DOM Parser. Как можно догадаться из названия, она очень проста в освоении. Рассмотрим базовый код:
$html = file_get_html(‘http://www.yandex.ru’);
$a_links = $html->find(‘a’);
Первая строка создает объект страницы, источником которой в данном случае является Яндекс, и записывает в переменную $html, которая имеет несколько функций, например find. Find – ищет элемент по какому-либо параметру, например find (‘a’) – вернет массив всех ссылок страницы. Find(‘#myid’) – вернет массив элементов, id которых равен ‘myid’.
Доступ к параметру href первой попавшейся ссылки осуществляется так:
echo $a_links[ 0 ]->href;
Более подробно можно посмотреть на сайте:
simplehtmldom.sourceforge.net
Библиотека, как уже было сказано выше, очень проста и лучше всего подходит для начинающего программиста, плюс ко всему она работает достаточно быстро и не сильно требовательна к ресурсам сервера.
Есть у этой библиотеки один минус – далеко не все страницы ей оказываются по зубам. Если какой-либо элемент не отображается, но точно известно, что он там есть, лучше воспользоваться библиотекой DOM (Document Object Model). Она хороша во всем, кроме скорости разработки и понятности.
$doc = new DOMDocument();
$doc->loadHTML ( $data );
$searchNodes = $doc->getElementsByTagName( «a» );
echo $searchNodes[ 0 ]->getAttribute( ‘href’ );
Этот скрипт создает сначала объект типа DOM, при этом в переменной $data должен находиться код страницы. Затем находит все теги a (ссылки), с помощью вызова $doc->getElementsByTagName, затем записывает их в массив $searchNodes. Доступ к параметру href первой ссылки на странице осуществляется с помощью вызова $searchNodes[ 0 ]->getAttribute( ‘href’ ).
В итоге скрипт получается более громоздкий, и писать его уже не так удобно, но иногда приходится использовать именно эту библиотеку.
Как я html-парсер на php писал, и что из этого вышло. Вводная часть
Сегодня я хочу рассказать, как написать html парсер, а также с какими проблемами я столкнулся, разрабатывая подобный парсер на php. А проблем было много. И в первой части я расскажу о проектировании парсера, и о возникших проблемах, ведь html парсер отличается от парсера привычных всем языков программирования.
Введение
Я старался написать текст этой статьи максимально понятно, чтобы любой, кто даже не знаком с общим устройством парсеров мог понять то, как работает html парсер.
Здесь и далее в статье я буду называть документ, содержащий html просто «Документ».
Dom дерево, находящееся в элементе, будет называться «Подмассив».
Что должен делать парсер?
Давайте сначала определимся, что должен делать парсер, чтобы в будущем отталкиваться от этого при разработке. А именно, парсер должен:
- Проектировать dom-дерево на основе документа
- Если есть ошибки в документе, то он должен их решать
- Находить элементы в dom-дереве
- Находить children элементы
- Находить текст
Впрочем, это мелочи. Основного функционала вполне хватит, чтобы поломать голову пару ночей напролет.
Но тут есть проблема, с которой я столкнулся сразу же: Html — это не просто язык, это язык гипертекста. У такого языка свой синтаксис, и обычный парсер не подойдет.
Разделяй и властвуй
Для начала, нужно разделить работу парсера на два этапа:
- Отделение обычного текста от тегов
- Сортировка всех полученных тегов в dom дерево
Для описания первого этапа я нарисовал схему, которая наглядно показывает, как обрабатываются данные на первом этапе:
Я решил опустить все мелкие детали. Например, как отличить, что после открывающего »
Также тут стоит уточнить. Логично, что в документе помимо тегов есть еще и текст. Говоря простым языком, если парсер найдет открывающий тег и если в нем будет текст, он запишет его после открывающего тега в виде отдельного тега. Такой тег будет считаться как одиночный и не будет участвовать в дальнейшей работе парсера.
Ну и второй этап. Самый сложный с точки зрения проектирования, и самый простой на первый взгляд с точки зрения понимания:
В данном случаи уровень означает уровень рекурсии. То есть если парсер нашел открывающий тег, он вызывает самого себя, «входит на уровень ниже», и так будет продолжаться до тех пор, пока не будет найден закрывающий тег. В этом случаи рекурсия выдает результат, «Выходит на уровень выше». Но, как обстоят дела с одиночными тегами? Такие теги считаются рекурсией ни как открывающие, ни как закрывающие. Они просто переходят в dom «Как есть».
В итоге у нас получится что-то вроде этого:
[0] => Array ( [is_closing] => [is_singleton] => [pointer] => 215 [tag] => div [0] => Array //открывается подмассив ( [0] => Array ( [is_closing] => [is_singleton] => [pointer] => 238 [tag] => div [id] => Array ( [0] => tjojo ) [0] => Array //открывается подмассив ( [0] => Array //Текст записывается в виде отдельного тега ( [tag] => __TEXT [0] => Привет! ) [1] => Array ( [is_closing] => 1 [is_singleton] => [pointer] => 268 [tag] => div ) ) ) ) )
Что там насчет поиска элементов?
А теперь давайте поговорим про поиск элементов. Но тут не все так однозначно, как можно подумать. Сначала стоит разобраться, по каким критериям мы ищем элементы. Тут все просто, мы ищем их по тем же критериям, как это делает Javascript: теги, классы и идентификаторы. Но тут проблема. Дело в том, что тег может быть только один, а вот классов и идентификаторов у одного элемента — множество, либо вообще не быть. Поэтому, поиск элемента по тегу будет отличаться от поиска по классу или идентификатору. Я нарисовал схему поиска по тегу, но не волнуйтесь: поиск по классу или идентификатору не особо отличаются.
Немного уточнений. Под исходным значением я имел в виду название тега, «div» например. Также, если элемент не равен исходному значению, но у него есть подмассив с подходящим элементом, в результат запишется именно подходящий элемент с его подмассивом, если таковой существует.
Стоит также сказать, что у парсера будет функция, позволяющая искать определенный элемент в документе. Это заметно ускорит производительность парсера, что позволит ему выполняться быстрее. Можно будет, например, взять только первый найденный элемент, или пятый, как вы захотите. Согласитесь, в таком случаи парсеру будет гораздо проще искать элементы.
Поиск children элементов
Хорошо, с поиском элементов разобрались, а как насчет children элементов? Тут тоже все просто: наш парсер будет брать все вложенные подмассивы найденных до этого элементов, если таковые существуют. Если таковых нет, парсер выведет пустой результат и пойдет дальше:
Поиск текста
Тут говорить особо не о чем. Парсер просто будет брать весь полученный текст из подмассива и выводить его.
Ошибки
Документ может содержать ошибки, с которыми наш скрипт должен успешно справляться, либо, если ошибка критическая, выводить ее на экран. Тут будет приведен список всех возможных ошибок, о которых, в будущем, мы будем говорить:
- Символ «>» не был найден
Такая ошибка будет возникать в том случаи, если парсер дошел до конца документа и не нашел закрывающего символа «>». - Неизвестное значение атрибута
Данная ошибка сигнализирует о том, что была проведена попытка передачи значения атрибуту когда закрывающий тег был найден.
Script, style и комментарии
В парсере теги script и style будут сразу же пропускаться, поскольку я не вижу смысл их записывать. С комментариями ситуация другая. Если вы захотите из записывать, то вы сможете включить отдельную функцию скрипта, и тогда он будет их записывать. Комментарии будут записываться точно так же как и текст, то есть как отдельный тег.
Заключение
Эту статью скорее нужно считать небольшим экскурсом в тему парсеров html. Я ее написал для тех, кто задумывается над написанием своего парсера, либо для тех, кому просто интересно. Поверьте, это действительно весело!
Данная статья является первой вводной частью. В следующих частях этого цикла уже будет участвовать непосредственно код, и будет меньше картинок с алгоритмами(что прекрасно, потому что рисовать я их не умею). Stay tuned!
Простой парсер на PHP
Иногда необходимо взять информацию, которая хранится на отдельном сервере или сайте, а доступа через api к нему нет. В таких случаях пользователи пишут небольшой программный код, так называемый парсер на пхп.
Предназначение парсера PHP — забрать необходимую информацию со страниц сайта. Зачастую, нужно забирать несколько различных текстов, для этого используют циклы php.
Рассмотрим простейший пример парсинга html страницы с помощью PHP. Допустим, вам нужно забрать ссылку со страницы, которая генерируется автоматически (в данном случае ссылка будет на mp4 файл).
После генерации кода получится примерно такая строка:
5.152.200.26/temp/ijz0TrfY5U70Pk-VqQPvyg/1394502624/TwoMen/rus_per/s1/1-1.mp4
Это и будет наш результат парсинга с помощью PHP. Код будут полезен пользователям, которые имеют свой онлайн кинотеатр и ищут способ украсть ссылки на видео uppod или не знают, как написать парсер на php.
Второй пример показывает, как небольшим кодом php вытащить необходимый тест со страницы.
(.*?)/is' , $text , $title ); echo $title[1]; ?>
Результатом будет выведенный заголовок страницы:
Простой парсер на PHP
Таких парсеров на php можно написать большое количество, с различными настройками, но эти два можно назвать универсальными. В них вам придётся изменить несколько строк, и они будут работать. А если нужны качественные фотографии с Shutterstock почти бесплатно — читайте мою статью об этом.
Как написать парсер для сайта
Недавно мне была поставлена задача написать php парсер сайта. Поставленная задача была выполнена и благодаря ей появилась эта заметка. До этого я ничего подобного не делал, так что не судите строго. Это мой первый парсер php.
И так с чего начать решение вопроса «как написать парсер». Давайте для начала разберёмся что это такое. В простонародье парсер(parser) или синтаксический анализатор — это программа которая получает данные (например веб страница), как то их анализирует структурирует, делает выборку и потом проводит какие то операции с ними (пишем данные в файл, в БД или выводим на экран). Данную задачу нам нужно выполнить в рамках веб программирования.
Для заметки я придумал такую тестовую задачу. Нужно спарсить по определённому поисковому запросу ссылки на сайты с 5 первых страниц выдачи и вывести их на экран. Парсить я решил выдачу поисковой системы bing. А почему бы не написать парсер яндекса или гугла спросите вы. Такие матёрые поисковики имеют не хилую защиту от парсинго(капча, бан ip, меняющаяся разметка, куки и тд), и это тема отдельной статьи. В этом плане с бингом таких проблемм нет. И так что нам нужно будет сделать:
- Получить (спарсить) контент html страницы средствами php
- Получить интересующие нас данные(а конкретно ссылки)
- Спарсить постраничную навигацию и получить ссылку на следующую страницу
- Опять спарсить страницу по ссылке, получить данные, получить следующую ссылку
- Проделать выше описанную операцию N количество раз
- Вывести все полученные ссылки на экран
Получение и парсинг страницы
Сначала напишем функцию, потом её разберём
function getBingLink($link)< $url="https://www.bing.com/search"; //получаем контент сайта $content= file_get_contents($url.$link); //убираем вывод ошибок libxml_use_internal_errors(true); //получаем объект класса DOMDocument $mydom = new DOMDocument(); //задаём настройки $mydom->preserveWhiteSpace = false; $mydom->resolveExternals = false; $mydom->validateOnParse = false; //разбираем HTML $mydom->loadHTML($content); //получаем объект класса DOMXpath $xpath = new DOMXpath($mydom); //делаем выборку с помощью xpath $items=$xpath->query("//*[@class='b_algo']/h2/a"); //выводим в цикле полученные ссылк static $a=1; foreach ($items as $item)< $link=$item->getAttribute('href'); echo $a."-".$link."
"; $a++; > >
И так разберём функцию. Для получения контента сайта используем php функцию file_get_contents($url.$link) . В неё подставляем адрес запроса. Есть ещё много методов получения контента html страницы, например cUrl, но на мой взгляд file_get_contents самый простой. Потом вызываем объект DOMDocument и так далее. Это всё стандартно и об этом можно почитать в интернете поподробнее. Хочу заакцентировать внимание на методе выборки нужных нам элементов. Для этой цели я использую xpath. Мою xpath шпаргалку можно глянуть здесь. Есть и другие методы выборки такие как регулярные выражения, Simple HTML DOM, phpQuery. Но на мой взгляд лучше разобраться с xpath, это даст дополнительные возможности при работе с xml документами, синтаксис полегче чем с регулярными выражениями, в отличии от css селекторов можно найти элемент по находящемуся в нём тексту. Для примера прокомментирую выражение //*[@class=’b_algo’]/h2/a . Подробнее синтаксис можно посмотреть в моей шпаргалке xpath. Мы выбираем со всей страницы ссылки лежащие в теге h2 в диве с классом b_algo . Сделав выборку мы получим массив и которого в цикле выведем на экран все полученные ссылки.
Парсинг постраничной навигации и получение ссылки на следующую страницу
Напишем новую функцию и по традиции разберём её позже
function getNextLink($link)< $url="https://www.bing.com/search"; $content= file_get_contents($url.$link); libxml_use_internal_errors(true); $mydom = new DOMDocument(); $mydom->preserveWhiteSpace = false; $mydom->resolveExternals = false; $mydom->validateOnParse = false; $mydom->loadHTML($content); $xpath = new DOMXpath($mydom); $page = $xpath->query("//*[@class='sb_pagS']/../following::li[1]/a"); foreach ($page as $p)< $nextlink=$p->getAttribute('href'); > return $nextlink; >
Почти идентичная функция, изменился только xpath запрос. //*[@class=’sb_pagS’]/../following::li[1]/a получаем элемент с классом sb_pagS ( это класс активной кнопки постраничной навигации), поднимаемся на элемент вверх по dom дереву, получаем первый соседний элемент li и получаем в нём ссылку. Эта и есть ссылка на следующую страницу.
Парсим выдачу N количество раз
function getFullList($link) < static $j=1; getBingLink($link); $nlink=getNextLink($link); if($j<5)< $j++; getFullList($nlink); >>
Данная функция вызывает getBingLink($link) и getNextLink($link) пока не кончится счётчик j. Функция рекурсивная, то есть вызывает сама себя. Про рекурсию почитайте подробнее в интернете. Обратите внимание что $j статическая, то есть она не удаляется при следующем вызове функции. Если бы это было не так, то рекурсия бы была бесконечной. Ещё добавлю из опыта, если хотите пройти всю постраничную навигацию то пишите if условие пока есть переменная $nlink. Есть ещё пара подводных камней. Если парсер работает долго то это может вызвать ошибку из за времени выполнения скрипта. По умолчанию 30с. Для увеличения времени в начале файла ставте ini_set(«max_execution_time», «480»); и задавайте нужное значение. Так же может возникать ошибка из за большого количества вызовов одной функции (более 100 раз). Фиксится отключением ошибки, ставим в начало скрипта ini_set(‘xdebug.max_nesting_level’, 0);
Теперь нам осталось написать html форму для ввода запроса и собрать парсер воедино. Смотрите листинг ниже.
preserveWhiteSpace = false; $mydom->resolveExternals = false; $mydom->validateOnParse = false; $mydom->loadHTML($content); $xpath = new DOMXpath($mydom); $items=$xpath->query("//*[@class='b_algo']/h2/a"); static $a=1; foreach ($items as $item)< $link=$item->getAttribute('href'); echo $a."-".$link."
"; $a++; > > //получаем след. ссылку function getNextLink($link)< $url="https://www.bing.com/search"; $content= file_get_contents($url.$link); libxml_use_internal_errors(true); $mydom = new DOMDocument(); $mydom->preserveWhiteSpace = false; $mydom->resolveExternals = false; $mydom->validateOnParse = false; $mydom->loadHTML($content); $xpath = new DOMXpath($mydom); $page = $xpath->query("//*[@class='sb_pagS']/../following::li[1]/a"); foreach ($page as $p)< $nextlink=$p->getAttribute('href'); > return $nextlink; > //делаем запрос к N страницам function getFullList($link) < static $j=1; getBingLink($link); $nlink=getNextLink($link); if($j<5)< $j++; getFullList($nlink); >> $err=""; // Проверяем, была ли корректным образом отправлена форма //если пользователь пришол POST if (!empty($_POST)) < function clearDate($date)< $date=stripslashes($date); $date=strip_tags($date); $date=trim($date); return $date; >$search=clearDate($_POST['search']); if(!empty($search))/и пост не пустой $s=urlencode($search); $link="?q=".$s; >else < $errMsg="Заполните поле запроса"; setcookie ("err", $errMsg); header("Location: ". $_SERVER['PHP_SELF']); exit; >> $errMsg=$_COOKIE["err"]; ?>parser .wrapp"; if($link) < //вызываем ф-ю парсера getFullList($link); >?>
P.S.
Решил улучшить юзабилити парсера. Была поставлена задача во время работы парсера выводить гифку загрузки, а по окончанию загрузки её убирать. Для этих целей я решил использовать ajax. Код парсера пришлось изменить, но логика работы осталась. Подробнее о передаче данных средствами ajax и php можно прочитать в моей заметке здесь. Итак мы теперь разбили код парсера на два файла. Файл index.php содержит в себе форму из которой данные силами ajax передаются в файл get.php. Там они обрабатываются производится запуск кода парсера, формирование массива с ответами на запрос и передача их на вывод в index.php силами javascript. Листинг index.php
пример парсера php Похожие публикации: