Вчимося парсувати сайти з бібліотекою PHP Simple HTML DOM Parser
Микити Синиці
Ті, хто хоч раз писав парсер, знає, що не варто цього робити за допомогою регулярних виразів. Проілюструвати це твердження допоможе наступний приклад.
Візьмемо HTML код:
Наприклад, з нього нам потрібно отримати опис та url сайту. Якщо брати тільки цей шматок коду, то все вирішується досить просто:
Проблеми починаються тоді, коли опис сайту заповнюють користувачі, і він не має певного шаблону.
Такий код регулярному виразу не по зубах.
Зазвичай у вузах на цей випадок вчать писати кінцевий автомат. Суть його в тому, що ми перебираємо, посимвольно, весь HTML текст, знаходимо початок тега, і будуємо дерево документа. Так зване DOM (Document Object Model)
Зараз писати таке самому немає потреби.
У php, починаючи з версії 5 є вбудовані методи роботи з деревом документа (клас DOMDocument), але заснований він на XML парсері.
А HTML і XML це хоч і дуже схожі, але водночас абсолютно різні технології.
Наприклад, неодмінна вимога до XML – це закриті теги та відсутність помилок.
Звідси випливає умова: помилок у html, який ми паримо за допомогою нативних засобів php, не повинно бути.
На жаль, на сайтах донорах, помилки не рідкісні, а отже, цей метод відпадає.
Для коректного аналізу таких сайтів, на допомогу прийдуть php бібліотеки PHPQuery, Simple HTML DOM, Zend DOM Query, Nokogiri.
Деякі з них, після невеликих маніпуляцій, згодовують html того ж DOMDocument. Ми не розглядатимемо їх.
У цій статті я розповім про SimpleHTMLDOM. Цією бібліотекою я користуюся вже кілька років, і вона мене ще жодного разу не підводила.
Завантажуємо останню версію тут.
Нехай Вас не бентежить те,що вона не оновлювалася з 2008 року, те, що вона вміє повністю покриє Ваші потреби в розборі html текстів.
В архіві, який ви завантажили, дві папки (приклади роботи та документація) та файл simple_html_dom.php.
simple_html_dom.php це і є вся бібліотека, більше нічого для роботи не потрібно. Кидаємо цей файл у папку із проектом і у своєму скрипті просто підвантажуємо його.
Окрім документації, яку ви завантажили з архівом, доступна ще online версія, її ви знайдете тут
Файл підключений та готовий до роботи.
Щоб почати розбирати HTML, його спершу потрібно отримати. Як правило, я роблю це за допомогою бібліотеки CURL.
У simplehtmldom є методи для віддаленого завантаження сторінок. Після підключення файлу бібліотеки нам доступні 2 функції для обробки HTML рядків.
str_get_html(str) та file_get_html(url)
Вони роблять одне й теж, перетворюють HTML текст на DOM дерево, розрізняються лише джерела.
str_get_htm – на вхід отримує нормальний рядок, тобто. якщо ви отримали HTML вдавшись до curl, або file_get_contents, то ви просто передаєте отриманий текст цієї функції.
file_get_html - сама вміє завантажувати дані з віддаленого URL або з локального файлу
На жаль, file_get_html завантажує сторінки звичайним file_get_contents. Це означає, якщо хостер, виставив у php.ini allow_url_fopen = false (тобто заборонив віддалено відкривати файли), то завантажити щось віддалено, не вийде. Та й серйозні веб-сайти таким способом парсить не варто, краще використовувати CURL з підтримкою proxy та ssl. Однак для наших дослідів цілком вистачить і file_get_html.
в результаті в змінній $html буде об'єкт типу simple_html_dom.
При великих обсягах даних у бібліотеці відбувається витік пам'яті. Тому після закінченняодного циклу треба її чистити.
Робить це метод clear.
Наприклад вантажимо 5 разів сайт www.yandex.ru з різними пошуковими запитами
Ці два рядки $html->clear(); та unset($html); краще писати відразу після того, як Ви створили об'єкт. Інакше забудете, і скрипт відвалиться, забивши всю пам'ять.
Після того, як html текст упакований в об'єкт, можна приступати безпосередньо до пошуку потрібних елементів.
Більшість пошукових функцій виконують метод find(selector, [index]). Якщо другий аргумент не заданий, метод повертає масив елементів. Якщо ж заданий елемент цього масиву з індексом index.
Приклад: скачаємо головну сторінку мого блогу і виведемо всі посилання, які зустрінемо на своєму шляху.
Пошук за назвою тега ви вже бачили
пошук за класом
або комбінований варіант
в даному випадку спочатку знайдеться елемент з >
Якщо метод find нічого не знайшов і index не заданий, він повертає порожній масив. Якщо ж index заданий, то метод повертає null.
Тому вірним рішенням буде перевірити
Пошук за наявності атрибуту
або конкретніший пошук за значенням атрибута
Така нотація дозволяє шукати за двома та більш суміжними класами.
Пошук кількох тегів
Пошук вкладених тегів
Кожен знайдений елемент також має метод find
якщо нам потрібно знайти все li тільки першого div'а то ми можемо написати так
Пошук за значенням атрибута не обмежується лише рівністю. Ось доступні умови
[атрибут] – перевіряє, чи є у елемента даний атрибут
[атрибут=величина] - перевіряє, чи є в елемента даний атрибут і чи його значення величині.( div[ >
[атрибут!=величина] - перевіряє, чи має елемент цей атрибуті чи його значення величині.( div[ >
[атрибут^=величина] - перевіряє, чи є в елемента даний атрибут і чи починається його значення з величини (div[>
[атрибут$=величина] - перевіряє, чи є в елемента даний атрибут і чи закінчується значення величиною( div[ >
[атрибут*=величина] - перевіряє, чи є в елемента даний атрибут і чи містить його значення у собі величину, будь-де(div[ >
Звичайний текст можна шукати як тег
Коментарі знаходимо за тегом comment
Кожен знайдений елемент і сам $html мають 5 полів
$e->tag Читає або записує ім'я елемента.
$e->outertext Читає або записує весь HTML елемент, включаючи його самого.
$e->innertext Читає або записує внутрішній HTML елемент
$e->plaintext Читає або записує простий текст елемента, це еквівалентно функції strip_tags($e->innertext). Хоча поле доступне для запису, запис у нього нічого не дасть, і вихідний HTML не змінить
Як Ви могли здогадатися, для видалення непотрібного елемента з HTML можна затерти його поле outertext
Тут слід пам'ятати, що хоч елемент і не видно в html, з дерева DOM він нікуди не подівся
за бажання ми навіть можемо повернути елемент на місце
Для більш ефективної навігації по дереву документа доступні методи
$e->children ( [int $index] ) Повертає об'єкт N-го прямого нащадка, якщо індекс встановлено, інакше повертає масив усіх дочірніх елементів
$e->parent() Повертає батьківський елемент.
$e->first_child() Повертає перший дочірній елемент, або null, якщо нічого не знайдено
$e->last_child() Повертає останній дочірній елемент або null, якщо нічого не знайдено
$e->next_sibling() Повертає наступний споріднений елемент, або null, якщо нічого не знайдено
$e->prev_sibling() Повертає попередній споріднений елемент, або null, якщо нічого не знайдено
Усі дочірні елементи різні, якось підібрати до них селектор проблематично. Тому скористаємося описаними методами.
Дані методи корисні при аналізі таблиць, елементи яких, як правило, структуровані, але не мають атрибутів, що ідентифікують.
Ну і остання фішка - це виклик callback функції на знайдений елемент
На екрані ми побачимо
Доступ до атрибутів елементів здійснюється безпосередньо
Досить теорії, перейдемо до практики
Завантажимо n фотографій із пошукової видачі Yandex Картинок. http://images.yandex.ru/
Як бути, якщо нам потрібно більше фото, ніж лежить на одній сторінці?
Відповідь проста: Код, наведений вище, полягає в функцію, в html крім фото знаходимо ще й URL всіх сторінок, і рекурсивно викликаємо цю функцію для цих сторінок.
Все добре, 200 картинок лежать у папці data. Але їх розмір занадто малий.
Тому завершальним акордом нашої практики буде завантаження збільшеної фотографії.
Для цього визначимо ще одну функцію
і трохи поправимо getYandexImages
Ось і все, насолоджуємося фото чудової Джесіки Альби. Сподіваюся, що мені простить Яндекс, адже по суті фото грабується не з їхніх серверів, а з прямо з сайтів, де вони лежать.
Крім того, це лише демонстрація роботи. Думаю нікому в здоровому глузді, не спаде на думку парсити Яндекс за допомогою file_get_content. Цю бібліотеку можна використовувати й у мирному програмуванні. Наприклад як шаблонизатор для CMS. Чому ні, із гарним кешуванням буде дуже зручна штука.
При великих обсягах сайтівдонорів, непогано було б розбити все на потоки.
А використовуючи описаний мій скрипт сортування зображень за кольором, можна зібрати непогану відсортовану базу фотографій знаменитостей.