Description
Заметка посвящается парсингу, в частности парсинг сайтов, парсинг страниц, парсинг в веб-среде, парсинг html-контента сайта.
В процессе разработки различных веб-сервисов очень часто приходится сталкиваться с задачами, в которых требуется быстро получить различного рода информацию в больших объемах. В основном это связано с граббингом, кражей информации, как хотите это называйте. Дело в том, что информация доступна и открыта. Особенность парсинга - это быстрый и автоматизированный сбор данных, контента со страниц сайта.
Сейчас очень популярно парсить в веб-среде, а именно парсить сайта, который содержать хоть какую-нибудь ценность и актуальность для людей. Особой ценностью является каталог товаров, включая картинки, базы данных справочников и многое другое, что может пригодиться для конкурентов.
Давайте попробуем спарсить нужную информацию в html, попробуем достать все ссылки с нескольких страниц нашего сайта.
Для начала нам необходимо получить контент сайта в формате html. Для этого нам достаточно знать адреса нужных страниц.
Хочу показать 2 основных способа получения контента со страницы сайта:
В первую очередь приготовим массив с нужными адресами страниц:
//3 ссылки нашего сайта: $urls = array("http://hello-site..ru/games/");
1 вариант - php функция file_get_contents . Функция возвращает html-строку, которую мы будем парсить на ссылки:
//помещаем каждую ссылку в функцию file_get_contents foreach($urls as $urlsItem){ $out .= file_get_contents($urlsItem); //и добавляем содержание каждой страницы в строку } echo $out; //здесь контент всех трех страниц
2 вариант - CURL . Библиотека, которая поддерживается php и имеет большой набор настроек, от POST-запросов до работы с FTP. Рассмотрим стандартный вызов библиотеки curl, который отдаст нам контент сайта:
foreach($urls as $urlsItem){ //пропускаем каждую ссылку в цикле $output = curl_init(); //подключаем курл curl_setopt($output, CURLOPT_URL, $urlsItem); //отправляем адрес страницы curl_setopt($output, CURLOPT_RETURNTRANSFER, 1); curl_setopt($output, CURLOPT_HEADER, 0); $out .= curl_exec($output); //помещаем html-контент в строку curl_close($output); //закрываем подключение } echo $out; //здесь контент всех трех страниц
Теперь в нашей строке $out находится контент всех трех страниц. Итак, переходим непосредственно к парсингу нашей строки.
Опять же хочу показать 3 варианта решения нашей задачи: "нативный" способ на php, с помощью встроенной библиотеки DOMDocument и библиотеки SimpleHTMLDOM.
1. php функция explode . Функция находит искомый символ или часть строки и делит целую строку на элементы массива.
Повторюсь, нам необходимо получить значения всех атрибутов href у тегов a, для этого будем делить общую строку на некоторые части\отрезки:
// explode $hrefs = explode("Если распечатать наш массив, будет примерно следующее:
Array ( => / => /hello => /timer/ => /leftmenu/ => /faq/ => /blog/ => /web-notes/ => /ordersite/ => /games)2. встроенная библиотека DOMDocument . Работаем с классом примерно следующим образом:
//domelement $dom = new DOMDocument; //создаем объект $dom->loadHTML($out); //загружаем контент $node = $dom->getElementsByTagName("a"); //берем все теги a for ($i = 0; $i < $node->length; $i++) { $hrefText = $node->item($i)->getAttribute("href"); //вытаскиваем из тега атрибут href } foreach($hrefText as $hrefTextItem){ //избавляемся от ссылок с пустым атрибутом href if($hrefTextItem!=""){ $clearHrefs=$hrefTextItem; } } $clearHrefs = array_unique($clearHrefs); //избавляемся от одинаковых ссылок print_r($clearHrefs); // в итоге у нас массив со всем ссылками с 3х страницРезультат такого кода ровно такой же, что и с помощью функции explode.
3. библиотека SimpleHTMLDOM . Ее необходимо подключать из файла. Работа примерно схожа с DOMDocument. Работаем с классом:
//simplehtml include("simple_html_dom.php"); //подключаем файл с классом SimpleHTMLDOM $html = new simple_html_dom(); //создаем объект $html->load($out); //помещаем наш контент $collection = $html->find("a"); //собираем все теги a foreach($collection as $collectionItem) { $articles = $collectionItem->attr; //массив всех атрибутов, href в том числе } foreach($articles as $articlesItem){ $hrefText = $articlesItem["href"]; //собираем в массив значения подмассива с ключом href } foreach($hrefText as $hrefTextItem){ //избавляемся от ссылок с пустым атрибутом href if($hrefTextItem!=""){ $clearHrefs=$hrefTextItem; } } $clearHrefs = array_unique($clearHrefs); //избавляемся от одинаковых ссылок print_r($clearHrefs); // в итоге у нас массив со всем ссылками с 3х страницПовторюсь, результат в массив ровно такой же как и выше в двух вышеперечисленных.
Теперь, имея массив со всеми ссылками, собранными с трех страниц сайта, можно отправить ссылки в нужное русло, все зависит от задачи и фантазии. Имея такие возможности, можно спарсить большое количество данных самого разного вида информации, картинки, тексты, логи и т.д. Чужая информация в ваших руках, распоряжайтесь как вам угодно, но сами защищайтесь, хотя это невозможно)
Вы узнаете, как получить список всех статей, опубликованных на сайте.
Шаг 1. Подготовка
В первую очередь нужно скопировать библиотеку simpleHTMLdom , которая доступна на сайте
В архиве для загрузки хранятся несколько файлов, но вам нужен только один simple_html_dom.php . Все остальные файлы - это примеры и документация.
Шаг 2. Основы парсинга
Данную библиотеку очень просто использовать, но есть несколько основных моментов, которые следует изучить до того, как вы начнете приводить ее в действие.
$html = new simple_html_dom();
// Загрузка из строки
$html->load("Hello World!
");
// Загрузка файла
$html->load_file("http://net.tutsplus.com/");Вы можете создать исходный объект загрузив HTML либо из строки, либо из файла. Загрузка из файла может быть выполнена либо через указание URL , либо из вашей локальной файловой системы.
Примечания: Метод load_file() делегирует работу функции PHP file_get_contents . Если allow_url_fopen не установлен в значение true в вашем файле php.ini , то может отсутствовать возможность открывать удаленные файлы таким образом. В этом случае вы можете вернуться к использованию библиотеки CURL для загрузки удаленных страниц, а затем прочитать с помощью метода load() .
Доступ к информации
Как только у вас будет объект DOM, вы сможете начать работать с ним, используя метод find() и создавая коллекции. Коллекция - это группа объектов, найденных по селектору. Синтаксис очень похож на jQuery.
Hello World!
We"re Here.
В данном примере HTML мы собираемся разобраться, как получить доступ к информации во втором параграфе, изменить ее и затем вывести результат действий.
1. # создаем и загружаем HTML
2. include("simple_html_dom.php");
3. $html = new simple_html_dom();
4. $html->load(“Hello World!
“);
5. # получаем элемент представляющий второй параграф
6. $element = $html->find(“p“);
7. # модифицируем его
8. $element->innertext .= “ and we"re here to stay.“;
9. # Выводим!
10. echo $html->save();Строки 2-4 : Загружаем HTML из строки, как объяснялось выше.
Строка 6 : Находим все тэги
В HTML, и возвращаем их в массив. Первый параграф будет иметь индекс 0, а последующие параграфы индексируются соответственно.
Строка 8 : Получаем доступ ко второму элементу в нашей коллекции параграфов (индекс 1), добавляем текст к его атрибуту innertext. Атрибут innertext представляет содержимое между тэгами, а атрибут outertext представляет содержимое включая тэги. Мы можем заменить тэг полностью, используя атрибут outertext.
Теперь добавим одну строку и модифицируем класс тэга нашего второго параграфа.
$element->class = "class_name";
echo $html->save();Окончательный вид HTML после команды save будет иметь вид:
Hello World!
We"re here and we"re here to stay.
Другие селекторы
Несколько других примеров селекторов. Если вы использовали jQuery, все покажется вам знакомым.
# получаем первый найденный элемент с id=“foo“
$single = $html->find("#foo", 0);
# получаем все элементы с классом “foo“
$collection = $html->find(".foo");
# получаем все теги ссылок на странице
$collection = $html->find("a");
# получаем все теги ссылок, которые расположены внутри тега H1
$collection = $html->find("h1 a");
# получаем все теги img с title="himom"
$collection = $html->find("img");Первый пример требует пояснений. Все запросы по умолчанию возвращают коллекции, даже запрос с ID, который должен вернуть только один элемент. Однако, задавая второй параметр, мы говорим “вернуть только первый элемент из коллекции”.
Это означает, что $single - единичный элемент, а не не массив элементов с одним членом.
Остальные примеры достаточно очевидны.
Документация
Полная документация по библиотеке доступна на .
Шаг 3. Пример из реального мира
Для демонстрации библиотеки в действии мы напишем скрипт для скрепинга содержимого сайта net.tutsplus.com и формирования списка заголовков и описания статей, представленных на сайте….только в качестве примера. Скрепинг относится к области трюков в веб, и не должен использоваться без разрешения владельца ресурса.
Include("simple_html_dom.php");
$articles = array();
getArticles("http://net.tutsplus.com/page/76/");Начнем с подключения библиотеки и вызова функции getArticles с указанием страницы, с которой мы хотим начать парсинг.
Так же объявим глобальный массив, чтобы сделать проще сбор все информации о статьях в одном месте. Прежде чем начинать парсинг взглянем, как описывается статья на сайте Nettuts+.
Так представлен основой формат поста на сайте, включая комментарии исходного кода. Почему важны комментарии? Они подсчитываются парсером как узлы.
Шаг 4. Начало функции парсинга
function getArticles($page) {
global $articles;
$html = new simple_html_dom();
$html->load_file($page);
// ... Дальше...
}Начинаем с объявления глобального массива, создаем новый объект simple_html_dom , и затем загружаем страницу для парсинга. Данная функция будет рекурсивно вызываться, поэтому устанавливаем для нее в качестве параметра URL страницы.
Шаг 5. Находим ту информацию, которая нам нужна
1. $items = $html->find("div");
2. foreach($items as $post) {
3. # помним про учет комментариев в качестве узлов
4. $articles = array($post->children(3)->outertext,
5. $post->children(6)->first_child()->outertext);
6. }Это суть функции getArticles . Нужно разобраться более детально, чтобы понять, что происходит.
Строка 1 : Создаем массив элементов - тег div с классом preview. Теперь у нас есть коллекция статей, сохраненная в $items .
Строка 4 : $post теперь ссылается на единичный div класса preview. Если мы взглянем в оригинальный HTML, то увидим, что третий элемент потомок - это тег H1 , который содержит заголовок статьи. Мы берем его и присваиваем $articles .
Помните о начале отсчета с 0 и учете комментариев исходного кода, когда будете определять правильный индекс узла.
Строка 5 : Шестой потомок $post - это
. Нам нужен текст описания из него, поэтому мы используем outertext - в описание будет включен тег параграфа. Единичная запись в массиве статей будет выглядеть примерно так:$articles = “Заголовок статьи“;
$articles = “This is my article description“Шаг 6, Работа со страницами
первым делом нужно определить, как найти следующую страницу. На сайте Nettuts+ о номере страницы очень легко догадаться по URL, но нам нужно получать ссылку в парсинге.
Если посмотреть на HTML, то можно найти следующее:
Это сслыка на следующую страницу, и мы можем легко ее найти по классу ‘nextpostslink ’. Теперь эта информация может быть использована.
If($next = $html->find("a", 0)) {
$URL = $next->href;
$html->clear();
unset($html);
getArticles($URL);
}В первой строке мы проверяем, можно ли найти ссылку с классом nextpostslink . Отметим использование второго параметра в функции find() . Таким образом мы указываем, что хотим получить первый элемент (индекс 0) в возвращаемой коллекции. $next содержит единичный элемент, а не коллекцию.
Затем мы присваиваем ссылку HREF переменной $URL. Это важно, потому, что далее мы удаляем объект HTML. Чтобы предотвратить утечку памяти в php5, текущий объект simple_html_dom должен быть очищен и разустановлен, прежде чем другой объект будет создан. Если этого не сделать, то вся доступная память может быть поглощена.
В завершение, мы вызываем функцию getArticles с URL следующей страницы. Рекурсия прерывается, когда не остается страниц для парсинга.
Шаг 7. Вывод результатов
Первое, мы собираемся установить несколько основных стилей. Все абсолютно произвольно - вы можете устанавливать то, что нравится.
#main {
margin:80px auto;
width:500px;
}
h1 {
font:bold 40px/38px helvetica, verdana, sans-serif;
margin:0;
}
h1 a {
color:#600;
text-decoration:none;
}
p {
background: #ECECEC;
font:10px/14px verdana, sans-serif;
margin:8px 0 15px;
border: 1px #CCC solid;
padding: 15px;
}
.item {
padding:10px;
}Затем мы пишем маленькую функцию на PHP в странице для вывода предварительно сохраненной информации.
foreach($articles as $item) {
echo "";";
echo $item;
echo $item;
echo "
}
?>Окончательный результат - это одна страница HTML со списком всех статей со страниц Nettuts+, начиная с той, которая была указана в первом вызове getArticles() .
Шаг 8. Заключение
Если Вы запускаете парсинг для большого количества страниц (скажем, весь сайт), то это может занять много времени. На таком сайте как Nettuts+, который имеет боле 86страниц, процесс парсинга может длиться более минуты.
Данный урок открывает для вас тему парсинга HTML. Существуют другие методы методы работы с DOM, которые позволяют работать с селектором xpath для поиска элементов. Описанная в данном уроке библиотека проста для использования и отлично подходит для быстрого старта. Помните, что нужно спрашивать разрешения, прежде проводить скрепинг сайта.
Потихоньку изучаю возможности PHP для создания парсеров. Я уже писала о том, как парсить . Сейчас расскажу об одном из способов парсинга html (он подойдет и для xml тоже, кстати). Повторю, что в php я не гуру, поэтому буду очень признательна, если вы оставите свои комментарии к поднятой теме.
Побродив по нашим и англоязычным форумам, поняла, что спор о том, лучше ли парсить html регулярными выражениями или использовать для этих целей возможности PHP DOM , является холиваром. Сама же я пришла к выводу, что все зависит от сложности структуры данных. Ведь если структура достаточно сложная, то с помощью регулярок приходится парсить в несколько этапов: сначала выделить большой кусок, потом разделить его на более маленькие и т.д.. В итоге, если данные сложные (или их очень много), то процесс парсинга может значительно затянуться. Ресурсоемкость в этом случае еще будет зависеть, конечно же, от самих регулярных выражений. Если в регэкспах много ".*" (они являются самыми ресурсоемкими, т.к. "прочесывают" исходный код с максимальной жадностью), то замедление будет заметным.
И вот как раз в этом-то случае как нельзя кстати приходится PHP DOM. Это удобный инструмент для парсинга как XML, так и HTML. Некоторые придерживаются мнения, что парсить html регэкспами вообще нельзя, и яростно защищают PHP DOM.
В свою очередь я ознакомилась с этим расширением, написав простенький скрипт. Который и привожу здесь, чтобы наглядно показать, как это все легко и просто. В примере разбирается html с частью карты сайта этого блога. Он присвоен переменной прямо внутри кода. В "боевых" же условиях исходные данные следует получать, например, через file_get_contents().
$html = "
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">сайт Map Последние темы блога
http://сайт/2009/08/blog-post_06.html
Базы
MySQL и Delphi. Express-метод
http://сайт/2009/08/blog-post.html
Пост о том, что лучше сто раз проверить