Как сделать парсинг html

Обновлено: 07.07.2024

Примите к сведению, что при парсинге мы полагаемся на определённую DOM-структуру сайта, определённые css-селекторы в HTML-разметке страницы. Потому, держите в уме вероятность того, что структура сайта в будущем будет обновлена, и парсер, возможно перестанет работать. И, вероятно, с кодом из этой статьи случится то же самое, когда сайт-донор обновит разметку.

Большинство PHP-разработчиков, хоть раз в жизни делали парсер. Будь то с использованием file_get_contents, или curl, но, уверен, каждый хоть раз в жизни парсил информацию из Web-а. И это не удивительно, ведь очень часто появляется необходимость в копировании большого количества информации со сторонних сайтов, особенно для новых, пустых сайтов, нуждающихся в автоматическом наполнении.

Алгоритм парсинга аналогичен тому, что вы бы открыли нужный URL в браузере, просмотрели бы сайт, и скопировали бы нужную вам информацию себе. Но если таких действий нужно будет проделать несколько тысяч раз, то это становится довольно затруднительно. И, к счастью, это можно легко автоматизировать с помощью скриптов. В этой статье я покажу, как написать быстрый парсер, используя асинхронные запросы.

Зачем люди пишут парсеры

То, есть, если рассматривать роль парсера в проекте qaru.site, то, без раздумий можно утверждать то, что парсер в этом проекте - это 80% его успеха. Ведь, вместо того, чтобы развивать свой форум с нуля, просто было скопировано большое количество реальной информации с другого сайта. И из-за того, что эта информация в оригинале была на английском, то, со стороны поисковых систем её перевод расценивается как условно-полностью уникальный текст (невзирая на то, что перевод постов там сродни "я твой дом труба шатать").

Но, это только один пример, придуманный сходу. На самом же деле, парсеры используются почти в каждом проекте: для наполнения сайта данными, для актуализации и обновления. Потому, их применение достаточно широко, и интересно. Потому знать, как писать парсер на php нужно. А если вы ещё освоите многопоточный парсинг, то жизнь станет ещё проще ^^.

Задача

task

В этой статье я напишу простой WEB-парсер информации о фильме с Кинопоиска, который будет получать детали этого фильма:

Почему я использую ReactPHP для выполнения асинхронных запросов? Если кратко - это быстрее. Если представить, что мы хотим спарсить все фильмы с первой страницы популярных фильмов, то для получения данных о всех фильмах понадобится выполнить 1 запрос на получение списка, и 200 запросов на получение подробной информации о каждом фильме отдельно. В итоге, 201 запрос, выполняя его в синхронном режиме, последовательно друг за другом может занять достаточно много времени.
И в противовес этому, представьте, что есть возможность запустить обработку всех этих запросов одновременно. И в этом случае, данные будут скопированы на порядок быстрее. Давайте попробуем.

Настройка проекта

Перед тем, как начать, с помощью композера нужно установить несколько зависимостей.

Для того, чтобы из целой html-страницы получить определённые "куски" с нужной для нас информацией, используем библиотеку для парсинга по DOM-структуре. Я использую DiDOM

Теперь, можем начинать. Для начала, напишем такой код:

Делаем запросы

Для текущей задачи нам будет достаточно одного метода get($url, $headers = []) :

Как вы можете заметить, алгоритм парсинга достаточно прост:

  1. Делаем запрос и получаем промис.
  2. Пишем обработчик этого промиса.
  3. Парсим нужную информацию внутри этого обработчика.
  4. Если нужно, повторяем первый шаг.

Работа с DOM документа

Теперь, когда мы научились получать ответ (содержимое WEB-страницы), можем начать работу с DOM-ом этого документа. Для этого, как я и ранее писал, я буду использовать Didom, подробнее о котором вы можете почитать здесь.

Для начала работы, нужно создать экземпляр класса \DiDom\Document , конструктор которого принимает HTML-разметку.

Внутри обработчика мы создали экземпляр класса \DiDom\Document , передав ему HTML-ответ, приведённый к строке. Теперь, нужно выбрать нужные данные, используя соответствующие CSS-селекторы.

Заголовки (Title, Alternative Title)

Заголовок может быть получен с тега h1 (который единственнный на всей странице).

Метод first($selector) ищет первый элемент, соответствующий указанному селектору. После чего, к найденного элемента вызывается метод text() , который возвращает текст, содержащийся в этом элементе. Навигация и парсинг DOM-дерева выглядит очень похожим с jQuery:

titles

Таблица параметров

table_params

Такие параметры фильма, как год , страна , слоган и т.д. находятся в таблице с классом info .

А ещё из разметки можно увидеть, что нужные нам параметры находятся во втором столбце (td) каждой из строк таблицы (tr). Но, нам не нужно сильно запариваться по поводу парсинга информации, так как можно увидеть, что в каждой строке таблицы есть только по одной ссылке, которые, как раз таки, и содержат внутри себя текст параметров.
Для этого, напишем код, получая сначала все строки таблицы, а потом обращаясь к ним по индексу:

Время и рейтинг (time, rating)

Информация о времени находится в той же таблице, которую парсили в прошлом шаге. Для получение данных, можно, как и в прошлом коде, обратиться по индексу:

time

Однако, изучив детальнее, можно увидеть, что у блока "время" есть уникальный идентификатор runtime, которым мы и воспользуемся.

И в этом случае, код будет выглядеть:

rating

И, аналогично поступим с рейтингом. Он, правда, не имеет тега id, однако, класс блога рейтинга уникальный, и не повторяется на странице, потому будем обращаться по нему:

Итого, код парсера будет выглядеть так:

Написание класса парсера

Теперь пришло время собрать все части спаршеных данных вместе. Логина по выполнению запроса может быть помещена в отдельную функцию/класс, а так же, нужно добавить более гибкую функциональность, добавив возможность указания разных URL-адресов. Для этого, создадим класс KinopoiskParser

Класс KinopoiskParser принимает объект Browser как зависимость в свой конструктор. Функциональность этого класса достаточно проста: в нём существует 2 метода: parse() - который принимает массив URL-адресов на фильмы, и getData() , который возвращает массив всех спаршенных данных о фильмах.

Теперь, наконец-то, можем попробовать этот парсер в действии:

В этом коде мы создали объект парсера, передали его методу URL-адреса для парсинга, после чего, запустили обработчик цикла событий. События будут обрабатываться до тех пор, пока все запросы не будут выполнены, и результаты, в которых нуждаемся, не спаршены с HTML-разметки. Как результат, эти запросы выполняются параллельно, потому, итоговое время выполнения скрипта будет равно самому медленному из наших запросов.

Результат будет выглядеть примерно так:

Дальше вы можете продолжать писать парсер как угодно: разделив его на несколько файлов, добавить запись результатов в базу данных. Главная цель этой статьи была показать, как выполнять асинхронные запросы в PHP и обрабатывать ответ с помощью DOM-парсинга.

Добавление таймайта

Этот парсер может быть немного улучшим путём добавление таймаута на выполнение запроса. Просто, что будет, если самый медленный из запросов будет слишком медленным? И вместо того, чтобы ждать его завершения, мы можем указать таймаут - максимальное время, за которое он может отработать. Иначе же, если он не впишется в рамки этого таймаута, каждый из таких медленных запросов будет отменён. Для реализации такого функционала мы будем использовать встроенные возможности ReactPHP.
В чём идея:

  • Получить промис запроса.
  • Задать таймер.
  • Когда время таймера наступит - отменить выполнение промиса.

Для этого, немного модифицируем код класса парсера KinopoiskParser , добавив в конструктор зависимость от \React\EventLoop\LoopInterface :

После чего, модифицируем метод parse() так, чтобы он мог принимать таймаут.

Если аргумент $timeout не будет передан, то будет применён таймаут по-умолчанию - 5 секунд . И, в случае, когда запрос не успевает отработать за указанное время, то этот промис отменяется. В текущем случае, все запросы, которые будут занимать больше времени, чем 5 секунд будут отменены. В случае же, если промис находится в режиме settled , то есть, когда запрос успешно выполнен, метод cancel() не создаст никакого эффекта.

Для примера, если мы не желаем ждать дольше, чем 3 секунды, напишем код:

Некоторые сайты не любят людей, парсящие их ресурс, и пытаются бороться с ними. Когда вы делаете парсинг для личных целей, при небольшом количестве запросов - ничего страшного. Но, если попробовать выполнить сотни параллельных запросов с одного IP - вы можете натолкнуться на проблемы. Сайту может не понравиться то, что вы делаете запросы слишком часто и много, и, скорее всего, заблокирует вас. В этом случае вам очень пригодятся прокси. В следующей статье я как раз и опишу процесс работы с прокси с этим клиентом в асинхронном режиме.

Резюме

В этой статье я показал, как работать с ReactPHP, показал примеры работы с ним, реализовав пример простого php парсера кинопоиска. Так же, в этой статье было рассмотрено, как парсить html на php, с помощью php dom парсера DiDOM, который является лучшим DOM-парсером на PHP. К слову, DiDOM - это отличная замена всем известного php simple dom parser-а. Надеюсь, что теперь вы без проблем сможете написать парсер контента собственными руками на php. И, полностью освоив материал этой статьи, выполняя запросы асинхронно, вы значительно прибавите в скорости и качестве парсеров.
Хорошего парсинга.

Честно говоря, кинопоиск - не самый удачный ресурс для демонстрации парсинга данных, ввиду того, что он огрантчивает IP при частых запросах. Но, хочу подчеркнуть, что концепция этой статьи как раз не приследует цель написать парсер кинопоиска. А главная цель - демонстрация алгоритма, по которому разрабатываются парсеры, а так же, как разработка парсеров ложится на асинхронный код.

Постоянно в Интернете, ничего не успеваете? Парсинг сайта спешит на помощь! Разбираемся, как автоматизировать получение нужной информации.

Осваиваем парсинг сайта: короткий туториал на Python

Чтобы быть в курсе, кто получит кубок мира в 2019 году, или как будет выглядеть будущее страны в ближайшие 5 лет, приходится постоянно зависать в Интернете. Но если вы не хотите тратить много времени на Интернет и жаждете оставаться в курсе всех событий, то эта статья для вас. Итак, не теряя времени, начнём!

Доступ к новейшей информации получаем двумя способами. Первый – с помощью API, который предоставляют медиа-сайты, а второй – с помощью парсинга сайтов (Web Scraping).

Использование API предельно просто, и, вероятно, лучший способ получения обновлённой информации – вызвать соответствующий программный интерфейс. Но, к сожалению, не все сайты предоставляют общедоступные API. Таким образом, остаётся другой путь – парсинг сайтов.

Парсинг сайта

Последовательность действий

Эта последовательность помогает пройти по URL-адресу нужной страницы, получить HTML-содержимое и проанализировать необходимые данные. Но иногда требуется сперва войти на сайт, а затем перейти по конкретному адресу, чтобы получить данные. В этом случае добавляется ещё один шаг для входа на сайт.

Пакеты

Для анализа HTML-содержимого и получения необходимых данных используется библиотека Beautiful Soup. Это удивительный пакет Python для парсинга документов формата HTML и XML.

Для входа на веб-сайт, перехода к нужному URL-адресу в рамках одного сеанса и загрузки HTML-содержимого будем использовать библиотеку Selenium. Selenium Python помогает при нажатии на кнопки, вводе контента и других манипуляциях.

Погружение в код

Сначала импортируем библиотеки, которые будем использовать:

Затем укажем драйверу браузера путь к Selenium, чтобы запустить наш веб-браузер (Google Chrome). И если не хотим, чтобы наш бот отображал графический интерфейс браузера, добавим опцию headless в Selenium.

Браузеры без графического интерфейса (headless) предоставляют автоматизированное управление веб-страницей в среде, аналогичной популярным веб-браузерам, но выполняются через интерфейс командной строки или с использованием сетевых коммуникаций.

После настройки среды путём определения браузера и установки библиотек приступаем к HTML. Перейдём на страницу входа и найдём идентификатор, класс или имя полей для ввода адреса электронной почты, пароля и кнопки отправки, чтобы ввести данные в структуру страницы.

После успешного входа в систему перейдём на нужную страницу и получим HTML-содержимое страницы.

Когда получили HTML-содержимое, единственное, что остаётся, – парсинг. Распарсим содержимое с помощью библиотек Beautiful Soup и html5lib.

html5lib – это пакет Python, который реализует алгоритм парсинга HTML5, на который сильно влияют современные браузеры. Как только получили нормализованную структуру содержимого, становится доступным поиск данных в любом дочернем элементе тега html . Искомые данные присутствуют в теге table , поэтому ищем этот тег.

Один раз находим родительский тег, а затем рекурсивно проходим по дочерним элементам и печатаем значения.

выведет значения в консоль.

Так парсятся данные с любого сайта.

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

Примите к сведению, что при парсинге мы полагаемся на определённую DOM-структуру сайта, определённые css-селекторы в HTML-разметке страницы. Потому, держите в уме вероятность того, что структура сайта в будущем будет обновлена, и парсер, возможно перестанет работать. И, вероятно, с кодом из этой статьи случится то же самое, когда сайт-донор обновит разметку.

Большинство PHP-разработчиков, хоть раз в жизни делали парсер. Будь то с использованием file_get_contents, или curl, но, уверен, каждый хоть раз в жизни парсил информацию из Web-а. И это не удивительно, ведь очень часто появляется необходимость в копировании большого количества информации со сторонних сайтов, особенно для новых, пустых сайтов, нуждающихся в автоматическом наполнении.

Алгоритм парсинга аналогичен тому, что вы бы открыли нужный URL в браузере, просмотрели бы сайт, и скопировали бы нужную вам информацию себе. Но если таких действий нужно будет проделать несколько тысяч раз, то это становится довольно затруднительно. И, к счастью, это можно легко автоматизировать с помощью скриптов. В этой статье я покажу, как написать быстрый парсер, используя асинхронные запросы.

Зачем люди пишут парсеры

То, есть, если рассматривать роль парсера в проекте qaru.site, то, без раздумий можно утверждать то, что парсер в этом проекте - это 80% его успеха. Ведь, вместо того, чтобы развивать свой форум с нуля, просто было скопировано большое количество реальной информации с другого сайта. И из-за того, что эта информация в оригинале была на английском, то, со стороны поисковых систем её перевод расценивается как условно-полностью уникальный текст (невзирая на то, что перевод постов там сродни "я твой дом труба шатать").

Но, это только один пример, придуманный сходу. На самом же деле, парсеры используются почти в каждом проекте: для наполнения сайта данными, для актуализации и обновления. Потому, их применение достаточно широко, и интересно. Потому знать, как писать парсер на php нужно. А если вы ещё освоите многопоточный парсинг, то жизнь станет ещё проще ^^.

Задача

task

В этой статье я напишу простой WEB-парсер информации о фильме с Кинопоиска, который будет получать детали этого фильма:

Почему я использую ReactPHP для выполнения асинхронных запросов? Если кратко - это быстрее. Если представить, что мы хотим спарсить все фильмы с первой страницы популярных фильмов, то для получения данных о всех фильмах понадобится выполнить 1 запрос на получение списка, и 200 запросов на получение подробной информации о каждом фильме отдельно. В итоге, 201 запрос, выполняя его в синхронном режиме, последовательно друг за другом может занять достаточно много времени.
И в противовес этому, представьте, что есть возможность запустить обработку всех этих запросов одновременно. И в этом случае, данные будут скопированы на порядок быстрее. Давайте попробуем.

Настройка проекта

Перед тем, как начать, с помощью композера нужно установить несколько зависимостей.

Для того, чтобы из целой html-страницы получить определённые "куски" с нужной для нас информацией, используем библиотеку для парсинга по DOM-структуре. Я использую DiDOM

Теперь, можем начинать. Для начала, напишем такой код:

Делаем запросы

Для текущей задачи нам будет достаточно одного метода get($url, $headers = []) :

Как вы можете заметить, алгоритм парсинга достаточно прост:

  1. Делаем запрос и получаем промис.
  2. Пишем обработчик этого промиса.
  3. Парсим нужную информацию внутри этого обработчика.
  4. Если нужно, повторяем первый шаг.

Работа с DOM документа

Теперь, когда мы научились получать ответ (содержимое WEB-страницы), можем начать работу с DOM-ом этого документа. Для этого, как я и ранее писал, я буду использовать Didom, подробнее о котором вы можете почитать здесь.

Для начала работы, нужно создать экземпляр класса \DiDom\Document , конструктор которого принимает HTML-разметку.

Внутри обработчика мы создали экземпляр класса \DiDom\Document , передав ему HTML-ответ, приведённый к строке. Теперь, нужно выбрать нужные данные, используя соответствующие CSS-селекторы.

Заголовки (Title, Alternative Title)

Заголовок может быть получен с тега h1 (который единственнный на всей странице).

Метод first($selector) ищет первый элемент, соответствующий указанному селектору. После чего, к найденного элемента вызывается метод text() , который возвращает текст, содержащийся в этом элементе. Навигация и парсинг DOM-дерева выглядит очень похожим с jQuery:

titles

Таблица параметров

table_params

Такие параметры фильма, как год , страна , слоган и т.д. находятся в таблице с классом info .

А ещё из разметки можно увидеть, что нужные нам параметры находятся во втором столбце (td) каждой из строк таблицы (tr). Но, нам не нужно сильно запариваться по поводу парсинга информации, так как можно увидеть, что в каждой строке таблицы есть только по одной ссылке, которые, как раз таки, и содержат внутри себя текст параметров.
Для этого, напишем код, получая сначала все строки таблицы, а потом обращаясь к ним по индексу:

Время и рейтинг (time, rating)

Информация о времени находится в той же таблице, которую парсили в прошлом шаге. Для получение данных, можно, как и в прошлом коде, обратиться по индексу:

time

Однако, изучив детальнее, можно увидеть, что у блока "время" есть уникальный идентификатор runtime, которым мы и воспользуемся.

И в этом случае, код будет выглядеть:

rating

И, аналогично поступим с рейтингом. Он, правда, не имеет тега id, однако, класс блога рейтинга уникальный, и не повторяется на странице, потому будем обращаться по нему:

Итого, код парсера будет выглядеть так:

Написание класса парсера

Теперь пришло время собрать все части спаршеных данных вместе. Логина по выполнению запроса может быть помещена в отдельную функцию/класс, а так же, нужно добавить более гибкую функциональность, добавив возможность указания разных URL-адресов. Для этого, создадим класс KinopoiskParser

Класс KinopoiskParser принимает объект Browser как зависимость в свой конструктор. Функциональность этого класса достаточно проста: в нём существует 2 метода: parse() - который принимает массив URL-адресов на фильмы, и getData() , который возвращает массив всех спаршенных данных о фильмах.

Теперь, наконец-то, можем попробовать этот парсер в действии:

В этом коде мы создали объект парсера, передали его методу URL-адреса для парсинга, после чего, запустили обработчик цикла событий. События будут обрабатываться до тех пор, пока все запросы не будут выполнены, и результаты, в которых нуждаемся, не спаршены с HTML-разметки. Как результат, эти запросы выполняются параллельно, потому, итоговое время выполнения скрипта будет равно самому медленному из наших запросов.

Результат будет выглядеть примерно так:

Дальше вы можете продолжать писать парсер как угодно: разделив его на несколько файлов, добавить запись результатов в базу данных. Главная цель этой статьи была показать, как выполнять асинхронные запросы в PHP и обрабатывать ответ с помощью DOM-парсинга.

Добавление таймайта

Этот парсер может быть немного улучшим путём добавление таймаута на выполнение запроса. Просто, что будет, если самый медленный из запросов будет слишком медленным? И вместо того, чтобы ждать его завершения, мы можем указать таймаут - максимальное время, за которое он может отработать. Иначе же, если он не впишется в рамки этого таймаута, каждый из таких медленных запросов будет отменён. Для реализации такого функционала мы будем использовать встроенные возможности ReactPHP.
В чём идея:

  • Получить промис запроса.
  • Задать таймер.
  • Когда время таймера наступит - отменить выполнение промиса.

Для этого, немного модифицируем код класса парсера KinopoiskParser , добавив в конструктор зависимость от \React\EventLoop\LoopInterface :

После чего, модифицируем метод parse() так, чтобы он мог принимать таймаут.

Если аргумент $timeout не будет передан, то будет применён таймаут по-умолчанию - 5 секунд . И, в случае, когда запрос не успевает отработать за указанное время, то этот промис отменяется. В текущем случае, все запросы, которые будут занимать больше времени, чем 5 секунд будут отменены. В случае же, если промис находится в режиме settled , то есть, когда запрос успешно выполнен, метод cancel() не создаст никакого эффекта.

Для примера, если мы не желаем ждать дольше, чем 3 секунды, напишем код:

Некоторые сайты не любят людей, парсящие их ресурс, и пытаются бороться с ними. Когда вы делаете парсинг для личных целей, при небольшом количестве запросов - ничего страшного. Но, если попробовать выполнить сотни параллельных запросов с одного IP - вы можете натолкнуться на проблемы. Сайту может не понравиться то, что вы делаете запросы слишком часто и много, и, скорее всего, заблокирует вас. В этом случае вам очень пригодятся прокси. В следующей статье я как раз и опишу процесс работы с прокси с этим клиентом в асинхронном режиме.

Резюме

В этой статье я показал, как работать с ReactPHP, показал примеры работы с ним, реализовав пример простого php парсера кинопоиска. Так же, в этой статье было рассмотрено, как парсить html на php, с помощью php dom парсера DiDOM, который является лучшим DOM-парсером на PHP. К слову, DiDOM - это отличная замена всем известного php simple dom parser-а. Надеюсь, что теперь вы без проблем сможете написать парсер контента собственными руками на php. И, полностью освоив материал этой статьи, выполняя запросы асинхронно, вы значительно прибавите в скорости и качестве парсеров.
Хорошего парсинга.

Честно говоря, кинопоиск - не самый удачный ресурс для демонстрации парсинга данных, ввиду того, что он огрантчивает IP при частых запросах. Но, хочу подчеркнуть, что концепция этой статьи как раз не приследует цель написать парсер кинопоиска. А главная цель - демонстрация алгоритма, по которому разрабатываются парсеры, а так же, как разработка парсеров ложится на асинхронный код.

Интернет, пожалуй, самый большой источник информации (и дезинформации) на планете. Самостоятельно обработать множество ресурсов крайне сложно и затратно по времени, но есть способы автоматизации этого процесса. Речь идут о процессе скрейпинга страницы и последующего анализа данных. При помощи этих инструментов можно автоматизировать сбор огромного количества данных. А сообщество Python создало несколько мощных инструментов для этого. Интересно? Тогда погнали!

И да. Хотя многие сайты ничего против парсеров не имеют, но есть и те, кто не одобряет сбор данных с их сайта подобным образом. Стоит это учитывать, особенно если вы планируете какой-то крупный проект на базе собираемых данных.

С сегодня я предлагаю попробовать себя в этой интересной сфере при помощи классного инструмента под названием Beautiful Soup (Красивый суп?). Название начинает иметь смысл если вы хоть раз видели HTML кашу загруженной странички.

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

Цель: Fake Python Job Site

Этот сайт прост и понятен. Там есть список данных, которые нам и нужно будет вытащить из загруженной странички.


Понятное дело, что обработать так можно любой сайт. Буквально все из тех, которые вы можете открыть в своём браузере. Но для разных сайтов нужен будет свой скрипт, сложность которого будет напрямую зависеть от сложности самого сайта.

Главным инструментом в браузере для вас станет Инспектор страниц. В браузерах на базе хромиума его можно запустить вот так:



Он отображает полный код загруженной странички. Из него же мы будем извлекать интересующие нас данные. Если вы выделите блоки html кода, то при помощи подсветки легко сможете понять, что за что отвечает.

Ладно, на сайт посмотрели. Теперь перейдём в редактор.

Пишем код парсера для Fake Python

Для работы нам нужно будет несколько библиотек: requests и beautifulsoup4. Их устанавливаем через терминал при помощи команд:

Читайте также: