Php парсер html страниц. Парсер на PHP – это просто
Для того, чтобы спарсить страницу сайта (то есть разобрать ее HTML код), ее для начала следует получить. А затем уже полученный код можно разобрать с помощью регулярных выражений и, либо каким-то образом его проанализировать, либо сохранить в базу данных, либо и то, и другое.
Получение страниц сайтов с помощью file_get_contentsИтак, для начала давайте поучимся получать страницы сайтов в переменную PHP. Это делается с помощью функции file_get_contents , которая чаще всего используется для получения данных из файла, однако, может быть использована для получения страницы сайта - если передать ей параметром не путь к файлу, а url страницы сайта.
Учтите, что эта функция не идеальна и существует более мощный аналог - библиотека CURL , которая позволяет работать с куками, с заголовками, позволяет отправлять формы и переходить по редиректам. Все это file_get_contents делать не умеет, однако для начала нам сойдет и она, а работу с CURL мы разберем в следующем уроке.
Итак, давайте для примера получим главную страницу моего сайта и выведем ее на экран (сделайте это):
Что вы получите в результате: у себя на экране вы увидите страницу моего сайта, однако, скорее всего без CSS стилей и картинок (будут ли работать CSS и картинки - зависит от сайта, почему так - разберем попозже).
Давайте теперь выведем не страницу сайта, а ее исходный код. Запишем его в переменную $str и выведем на экран с помощью var_dump :
Учтите, что var_dump должен быть настроен корректно в конфигурации PHP (см. предыдущий урок для этого). Корректно - это значит вы должны видеть теги и не должно быть ограничения на длину строки (код страницы сайта может быть очень большим и желательно видеть его весь).
Итак, если все сделано хорошо, и вы видите исходный код страницы сайта - самое время приступить к его парсингу с помощью регулярных выражений .
Если вы не знаете регулярных выражений или сомневаетесь в своих знаниях - самое время изучить учебник по регулярным выражениям , а затем вернутся к изучению данного руководства по парсингу.
Должна быть включена директива allow_url_fopen http://php.net/manual/ru/filesystem.configuration.php#ini.allow-url-fopen
Парсинг с помощью регулярных выраженийПри попытке разобрать HTML код с помощью регулярных выражений вас будут ждать некоторые подводные камни. Их наличие чаще всего связано с тем, что регулярные выражения не предназначены для разбора тегов - для этого есть более продвинутые инструменты, например библиотека phpQuery, которую мы будем разбирать в следующих уроках.
Однако, уметь использовать регулярные выражения для парсинга тоже важно - во-первых, регулярки это простой (если вы их уже знаете - то простой) и популярный инструмент для парсинга, во-вторых, регулярки работают на порядок быстрее, чем любые библиотеки (часто это критично), ну, и в-третьих, даже при использовании специальных библиотек нужда в регулярках все равно есть.
Подводные камниПервая неожиданность, которая ожидает вас при использовании preg_match и preg_match_all - это то, что они работают только для тегов, целиком расположенных на одной строке (то есть, в них нету нажатого энтера). Если попытаться спарсить многострочный тег - у вас ничего не получится, пока вы не включите однострочный режим с помощью модификатора s . Вот таким образом:
Вторая неожиданность ждет вас, когда вы попробуете поработать с кириллицей - в этом случае нужно не забыть написать модификатор u (u маленькое, не путать с большим), вот так:
Какие еще подводные камни вас ждут - будем разбирать постепенно в течении данного урока.
Попробуем разобрать тегиПусть мы каким-то образом (например, через file_get_contents ) получили HTML код сайта. Вот он:
Это заголовок тайтл Это основное содержимое страницы.
Давайте займемся его разбором. Для начала давайте получим содержимое тега , тега , и тега .
Итак, получим содержимое тега (в переменной $str хранится HTML код, который мы разбираем):
Содержимое :
Содержимое :
В общем-то ничего сложного нет, только обратите внимание на то, что как уголки тегов, так и слеш от закрывающего тега экранировать не надо (последнее верно, если ограничителем регулярки является не слеш /, а, например, решетка #, как у нас сейчас).
Однако, на самом деле наши регулярки не идеальны. При некоторых условиях они просто откажутся работать . Вы должны быть готовы к этому - сайты, которые вы будете парсить - разные (часто они еще и устаревшие), и то, что хорошо работает на одном сайте, вполне может перестать работать на другом.
Что же у нас не так? На самом деле тег - такой же тег, как и остальные и в нем вполне могут быть атрибуты. Чаще всего это атрибут class , но могут быть и другие (например, onload для выполнения JavaScript).
Итак, перепишем регулярку с учетом атрибутов:
Но и здесь мы ошиблись, при чем ошибок несколько. Первая - следует ставить не плюс + , а звездочку * , так как плюс предполагает наличия хотя бы одного символа - но ведь атрибутов в теге может и не быть - и в этом случае между названием тега body и уголком не будет никаких символов - и наша регулярка спасует (не понятно, что я тут написал - учите регулярки).
Поправим эту проблему и вернемся к дальнейшему обсуждению:
Вторая проблема следующая: если внутри будут другие теги (а так оно и будет в реальной жизни) - то наша регулярка зацепит лишнего. Например, рассмотрим такой код:
Это заголовок тайтл
Регулярка найдет не , как ожидалось, а
Абзац{
} - потому что мы не ограничили ей жадность. Сделаем это: место напишем - в этом случае будет все хорошо.
Но более хорошим вариантом будет написать вместо точки конструкцию [^>] (не закрывающий уголок ), вот так - ]*?> - в этом случае мы полностью застрахуем себя от проблем такого рода, так как регулярка никогда не сможет выйти за тег.
Получение блока по idДавайте рассмотрим следующий код:
Это заголовок тайтл Контент Еще див
Напишем регулярку, которая получит содержимое блока с id, равным content .
Итак, попытка номер один (не совсем корректная):
#(.+?)#su
Что здесь не так? Проблема с пробелами - ведь между названием тега и атрибутом может быть сколько угодно пробелов, так же, как и вокруг равно в атрибутах.
Все проблемы такого рода существенны - даже если ваша регулярка разбирает одну страницу сайта - это не значит, что она разберет другую подобную страницу: на ней вполне вокруг равно в атрибуте id могли поставить пробелы - и тут ваша регулярка спасует.
Поэтому, регулярки парсера нужно строить так, чтобы они обходили как можно больше проблем - в этом случае ваш парсер будет работать максимально корректно на всех страницах сайта, а не только на тех, которые вы проверили.
Давайте поправим нашу регулярку:
#(.+?)#su
Обратите внимание на то, что вокруг равно пробелы могут быть, а могут и не быть, поэтому там стоит оператор повторения звездочка * .
Кроме того, перед закрывающем уголком тега тоже могут быть пробелы (а могут и не быть) - учтем и это:
#(.+?)#su
Итак, уже лучше, но еще далеко не идеал - ведь вокруг атрибута id могут быть и другие атрибуты, например так: . В этом случае наша регулярка спасует. Давайте укажем, что могут быть еще и другие атрибуты:
#(.+?)#su
Обратите внимание, что после стоит регулярка .*? - это не ошибка, так и задумано, ведь после может вообще не быть других атрибутов (кроме нашего id) и пробела тоже может не быть.
Регулярка стала еще более хорошей, но есть проблема: лучше не использовать точку в блоках типа .*? - мы вполне можем хватануть лишнего выйдя за наш тег (помните пример выше с body?). Лучше все-таки использовать [^>] - это гарантия безопасности:
#]+? id\s*?=\s*?"content" [^>]*? >(.+?)#su
Следующая проблема: кавычки-то в атрибутах могут быть как одинарными, так и двойными (их даже может вообще не быть, если значение атрибута - одно слово, но этот случай редкий - не будем его учитывать, если вам встретится такой сайт - проще написать регулярку специально для него). Итак, учтем это:
#]+?id\s*?=\s*? ["\"] content ["\"] [^>]*?>(.+?)#su
Обратите внимание на то, что одинарная кавычка заэкранирована - мы это делаем, так как внешние кавычки от строки PHP у нас тоже одинарные, вот тут:
В общем-то регулярка достаточно хороша, но иногда идут дальше и делают так, чтобы первая кавычка от тега совпадала со второй (исключаем вариант id="content"). В этом случае делают так - первая кавычка ложится в карман, а вторая кавычка указывается карманом, чтобы совпадала с первой:
#]+?id\s*?=\s*? (["\"]) content \1 [^>]*?>(.+?)#su
Для нашей задачи это особо не нужно (можно быть точно уверенным, что такое id="content" - врядли где-то будет), но есть атрибуты, где это существенно. Например, в таком случае: и даже