Как парсится html браузером
От автора: часто CSS представляют какой-то таинственной, причудливой силой, которая управляет всем, что мы видим в сети. Иногда стили могут быть простыми. Однако написание масштабируемого, производительного CSS, скорее, исключение, а не норма. Поэтому важно подробно разбираться в том, как работать с CSS.
Неважно, что вы думаете: CSS – это «необходимое зло» или мощный, но непонятный инструмент. С CSS приходится работать всем, кто создает веб-приложения. Глубокое понимание CSS может стать тем барьером, который отделяет красивое, отполированное веб-приложение от чего-то среднего.
Эта статья станет первой в серии, где мы подробно познакомимся с CSS и его экосистемой. Суть в том, что, заглянув под капот CSS, мы можем получить более глубокое понимание и оценку языка стилей для веба. Это позволит нам писать быстрее, чище, CSS станет красивее, а код будет масштабироваться по мере роста приложения и его сложности.
В первой статье мы разберем, как CSS проводит рендер на экран во время первой загрузки.
Причина, почему нас интересует путь, который CSS проходит, чтобы превратиться в красивые пиксели, сводится к двум словам.
Практический курс по верстке адаптивного сайта с нуля!
Изучите курс и узнайте, как верстать современные сайты на HTML5 и CSS3
Время загрузки
Если ваш сайт загружается целую вечность, есть вероятность, что пользователи не будут ждать, даже если на сайте есть ценный контент. Несколько исследований показали, что до 50% пользователей закрывают страницу после 3 секунд ожидания.
Поскольку пользователи ожидают быстрой загрузки, мы, как разработчики, не должны раздувать данные, посылаемые пользователю. К сожалению, CSS часто виноват в увеличении времени загрузки. Поэтому глубокое понимание того, как посланный CSS преобразуется в красивые пиксели, поможет оптимизировать те критические секунды, которые заставляют пользователя уйти.
Что такое критический путь рендера?
Когда мы говорим, что пользователи хотят быструю загрузку, нам необходимо провести линию между критическими и некритическими ресурсами. Может быть, вы лениво загружаете некоторые изображения или настроили разбиение роутов (спасибо, Webpack!), чтобы не посылать весь JS за раз. Такие ресурсы, загружающиеся после первичного рендера страницы, считаются не критичными. То есть они не задерживают первичный рендер страницы. Ресурсы, которые задерживают первый рендер страницы, считаются критическими.
Критический путь рендера – это минимальное количество шагов, необходимых браузеру от момента получения первого байта HTML до момента первого рендера пикселей на экране. По сути, это то, что должен сделать браузер, чтобы обработать критические ресурсы в то, что понравится пользователю. Выглядит это примерно так.
Строим DOM (Document Object Model) из полученного HTML
Если натыкаемся на блок JS (не помеченный как async) во время построения DOM, ждем построения CSSOM, останавливаем создание DOM и парсим/выполняем код. Причина такого поведения кроется в том, что выполнение JS может менять DOM и получать доступ/менять CSSOM.
В рамках статьи разберем второй шаг – как CSS влияет на критический путь рендера. Очень легко увлечься tree shake, route split и lazy load для JS и забыть про CSS. Неоптимизированный пакет CSS может сильно увеличить время загрузки.
HTML и критический путь рендера
Статья посвящена CSS, поэтому не будем долго рассказывать про построение DOM. Тем не менее, CSS – язык стилизации разметки. Поэтому нам нужно знать, как он взаимодействует с DOM.
DOM – это древоподобная структура данных, содержащая все узлы HTML страницы. Каждый узел хранит информацию об HTML элементе (атрибуты, id и классы). Если у узла есть дочерние HTML элементы, он также будет вести на их дочерние узлы. Например, по HTML ниже мы построим следующий DOM. Обратите внимание, как отступы HTML и структура DOM похожи.
В критическом пути рендера HTML считается одним из блокирующих рендер ресурсов – пока не распарсим HTML, мы не можем рендерить контент!
Создание объектной модели CSS
Когда браузер сталкивается с CSS (вставленным или внешним), ему необходимо распарсить текст во что-то, что моно использовать для стилизации макетов и отрисовок. Структура данных, в которую браузер преобразует CSS называется CSSOM – объектная модель CSS.
Как выглядит CSSOM? По CSS ниже браузер построит следующую CSSOM.
По сути, мы пробегаемся по всем CSS селекторам и размещаем их в дереве. Если селектор один, он прикрепляется к корневому узлу дерева. Вложенные селекторы крепятся к узлу, под которым они вложены. CSS парсер должен читать вложенные селекторы справа налево, чтобы разместить их под правильными узлами.
Перевод CSS в CSSOM считается этапом, блокирующим рендер, как и построение DOM из HTML. Если бы стили рендерились в пиксели постепенно без ожидания CSSOM, мы получили бы мигание нестилизованного контента (страшно!) на время парсинга CSSOM. После применения стилей все бы съехало. Это точно не хороший UX.
Практический курс по верстке адаптивного сайта с нуля!
Изучите курс и узнайте, как верстать современные сайты на HTML5 и CSS3
Дерево рендера
Браузер использует построенные CSSOM и DOM для создания дерева рендера. Вкратце дерево рендера хранит всю информацию, необходимую браузеру для создания пикселей на странице. Браузер сливает воедино DOM и CSSOM, удаляя все, что не повлияет на рендер.
Сперва браузер удаляет все невидимые элементы. Среди таких элементов head, script и meta, а также HTML элементы с атрибутом hidden. Эти элементы используются другими частями приложения и не будут рендериться на странице. Поэтому браузер может безопасно приступить к рендеру, зная, что все элементы в дереве рендера – это видимые HTML элементы.
Далее мы проходим по CSSOM и ищем совпадения CSS селекторов с элементами в текущем дереве рендера. Совпадающие правила для любого селектора будут применены к узлу дерева рендера.
Но есть одно CSS правило-исключение. display: none; в CSS полностью удаляет элемент из дерева рендера. Это касается только видимых элементов в дереве рендера. Другие способы сокрытия элемента типа opacity: 0; не удаляют элемент из дерева рендера, а рендерят его, не показывая.
У нас есть дерево рендера, все готово! После совмещения CSSOM и DOM в дерево рендера браузер может использовать его и думать, что оно содержит только информацию, необходимую для отрисовки тех первых пикселей – ни больше, ни меньше.
Макет и отрисовка
Имея готовое дерево рендера, браузер может приступить к простановке пикселей на странице. Последний этап критического пути рендера состоит из 2 шагов: макет и отрисовка.
Макет – то, где браузер решает, где будет расположен элемент, и сколько пространства он будет занимать. Браузер принимает правила, влияющие на margin, padding, width и position. При вычислении макета браузер должен пробежаться сверху дерева рендера вниз, так как положение всех элементов, их ширина и высота вычисляются относительно положения родительских узлов.
Если вы знакомы с блоковой моделью CSS, то, по сути, браузер рисует кучу CSS блоков на странице.
Важно помнить, что пока что еще на странице ничего не отображается. Представьте, что рисуете трафаретные линии во вьюпорте, подготавливая их для заливки.
Отрисовка происходит сразу после этапа макетирования, и мы наконец видим что-то на странице! Если представить, что время до первого пикселя это конец гонки, то это финишная черта. Браузер проходит по всем CSS блокам и заполняет их немакетными правилами. Если вы используете несколько слоев, браузер проверит, чтобы все было на своих слоях.
Важно помнить, что одни CSS свойства могут сильнее влиять на вес страницы, чем другие (например, radial-gradient намного сложнее для отрисовки, чем просто color). Если во время отрисовки вы испытываете «подергивания», уменьшите количество «дорогих» CSS правил. Это сильно повысит воспринимаемую производительность приложения.
Зачем думать о CSS в критическом пути рендера?
Вы можете сколько угодно тратить времени на оптимизацию скорости отрисовки кадров в секунду для приложения, улучшать его вид, проводить A-B тесты для повышения конверсии, но это неважно, если пользователи уходят, а страница еще даже не загрузилась.
Если вы хотите улучшить время загрузки, критично (без каламбура) знать шаги, которые браузер проходит до столь важного первого пикселя. Браузер блокирует рендер, пока не распарсит весь CSS. Поэтому можно сильно улучшить время загрузки, удалив весь CSS, который не применяется при первой отрисовке из первичного HTML документа. Это сильно сократит время, необходимое браузеру на построение CSSOM и дерева рендера.
CSS не обязательный для первой загрузки можно назвать «не критичным» и лениво загружать после первой отрисовки (особенно важно для одностраничных приложений. Посылать CSS для страниц, которые еще даже не видны, это сильный удар по производительности!).
Еще одно преимущество построения CSSOM – более глубокое понимание производительности селекторов. Вложенные селекторы должны проверять родительские узлы CSSOM, поэтому они, как правило, немного медленнее, чем плоский CSSOM, избегающий вложенных селекторов. Тем не менее, скажу, что в большинстве приложений это не самое узкое место в производительности. Скорее всего, можно оптимизировать что-то другое, прежде чем переписывать CSS селекторы.
Прежде чем начать полную перестройку CSS, оцените время загрузки через профайлер (это касается всего, что связано с веб-производительностью). Если вы используете Chrome, откройте DevTools и перейдите на вкладку Performance. Здесь сразу видно, сколько времени тратится на создание CSSOM, макетирование и отрисовку. Для этого взгляните на события Recalculate Styles, Layout и Paint. После этого уже можно исправлять узкие места и проводить оптимизацию.
LogRocket, DVR для веб-приложений
LogRocket – front end инструмент для логирования, позволяющий воспроизводить проблемы, как если бы они были в вашем браузере. Чтобы не гадать, почему произошла ошибка и не просить у пользователей скриншоты и дампы с логами, LogRocket позволяет воспроизводить сессию, чтобы быстро понять, что пошло не так. Инструмент замечательно работает с любым приложением, независимо от фреймворка, и имеет плагины для логирования дополнительного контекста из Redux, Vuex и @ngrx/store.
Помимо логирования действий и состояний Redux LogRocket записывает логи консоли, JS ошибки, трассировки стека, сетевые запросы/ответы с заголовками + тело запроса/ответа, метаданные браузера и кастомные логи. Он также позволяет DOM записывать HTML и CSS на странице, воссоздавая идеальное видео для самых сложных одностраничных приложений. Попробуйте бесплатно.
Цель статьи, если я собираюсь создавать быстрые и надежные веб-сайты, мне нужно действительно понимать механику каждого шага, который браузер выполняет для отображения веб-страницы, чтобы каждый шаг был обдуман и оптимизирован во время разработки. Этот пост представляет собой краткое изложение моих знаний о процессе отображения страниц на довольно высоком уровне.
Много идей основано на фантастическом (и БЕСПЛАТНОН!) курсе по оптимизации производительности веб-сайта Website Performance Optimization Ilya Grigorik и Cameron Pittman на Udacity. Я очень рекомендую это посмотреть.
Также очень полезной оказалась статья Пола Айриша и Тали Гарсиэль How Browsers Work: Behind the scenes of modern web browsers. Хотя эта статья 2011 года, но многие основы работы браузеров остаются актуальными до сих пор.
И так, поехали. Процесс отображения страниц можно разбить на следующие основные этапы:
- Начало разбора HTML
- Получение внешних ресурсов
- Разбор CSS и создание CSSOM
- Выполнение JavaScript
- Объединение DOM и CSSOM, для построения дерево рендеринга
- Расчет макета и отрисовка результата
1. Начало разбора HTML
Когда браузер начинает получать данные HTML страницы по сети, он немедленно запускает свой синтаксический анализатор parser для преобразования HTML в объектную модель документа (DOM) Document Object Model (DOM).
2. Получение внешних ресурсов
Когда парсер встречает внешний ресурс, такой как файл CSS или JavaScript, он пытается, получить его. Синтаксический анализатор будет продолжать работу по мере загрузки файла CSS, но он заблокирует рендеринг до тех пор, пока файл не будет загружен и проанализирован (подробнее об этом чуть позже).
defer означает, что выполнение файла будет отложено до завершения синтаксического анализа документа. Если несколько файлов имеют атрибут defer, то они будут выполняться в том порядке, в котором они были обнаружены в HTML.
async означает, что файл будет выполнен, как только он загрузится, это может быть во время или после процесса синтаксического анализа, и поэтому порядок, в котором выполняются асинхронные сценарии, не может быть гарантирован.
Предварительная загрузка ресурсов
3. Разбор CSS и создание CSSOM
Возможно, вы слышали о DOM, но слышали ли вы о CSSOM (CSS Object Model) (объектной модели CSS)? До того, как я начал исследовать эту тему, я об этом ни чего не знал!
Чем CSSOM отличается от DOM, так это тем, что он не может быть построен постепенно, поскольку правила CSS могут перезаписывать друг друга в разных точках из-за specificity (порядка применения свойства). Вот почему загрузка CSS блокирует рендеринг, поскольку до тех пор, пока весь CSS не будет проанализирован и не будет построен CSSOM, браузер не может знать, где и как разместить каждый элемент на экране.
4. Выполнение JavaScript
Как и когда ресурсы JavaScript будут загружены, определяет, в какой-то момент они будут проанализированы, скомпилированы и выполнены. В разных браузерах для выполнения этой задачи используются разные механизмы JavaScript. Анализ JavaScript может быть дорогостоящим процессом с точки зрения ресурсов компьютера, в большей степени, чем другие типы ресурсов, поэтому его оптимизация так важна для достижения хорошей производительности. Прочтите этот фантастический пост, чтобы подробнее узнать, как работает движок JavaScript.
События загрузки
После того, как синхронно загруженный JavaScript и DOM будут полностью проанализированы и готовы, будет сгенерировано событие document.DOMContentLoaded. Для любых сценариев, которым требуется доступ к DOM, например, для управления им или прослушивания событий взаимодействия с пользователем, рекомендуется сначала дождаться этого события перед выполнением сценариев.
После того, как все остальное, например асинхронный JavaScript, изображения и т. д., завершили загрузку, запускается событие window.load.
5. Объединение DOM и CSSOM, для построения дерево рендеринга
Дерево рендеринга представляет собой комбинацию DOM и CSSOM и представляет все, что будет отображаться на странице. Это не обязательно означает, что все узлы в дереве рендеринга будут визуально присутствовать, например узлы со стилями opacity: 0 или visibility: hidden будут включены и могут быть прочитаны программой чтения с экрана и т. д., тогда как те, которые настроены на display: none будет исключены. Кроме того, такие теги, как <head>, не содержащие визуальной информации, всегда будут пропущены.
Как и в случае с движками JavaScript, разные браузеры имеют разные механизмы рендеринга.
6. Расчет макета и отрисовка результата
Теперь, когда у нас есть полное дерево рендеринга, браузер знает, что рендерить, но не знает, где рендерить. Следовательно, необходимо рассчитать макет страницы (то есть положение и размер каждого узла). Механизм рендеринга проходит дерево рендеринга, начиная с вершины и идя вниз, вычисляет координаты, в которых должен отображаться каждый узел.
И вуаля! В конце концов, у нас есть полностью отрисованная веб-страница!
Вы вводите название сайта в адресную строку браузера, нажимаете enter, и по привычке видите запрашиваемую страницу. Все просто: ввел название сайта — сайт отобразился. Однако для более любознательных хочу рассказать, что происходит между тем как браузер начинает получать куски сайта (да, сайт приходит кусками, по-другому — чанками) и отображает полностью нарисованную страницу.
Как устроен браузер?
Перед историей о том, как браузер рисует страницу, важно понять как он устроен, какие процессы и на каком уровне выполняются. При знакомстве с процессом рендеринга мы не раз вспомним о компонентах браузера. Итак, под капотом браузер выглядит примерно следующим образом:
User Interface — это все что видит пользователь: адресная строка, кнопки вперед/назад, меню, закладки — за исключением области где отображается сайт.
Browser Engine отвечает за взаимодействие между User Interface и Rendering Engine. Например клик по кнопке назад должен сказать компоненте RE что нужно отрисовать предыдущее состояние.
Rendering Engine отвечает за отображение веб-страницы. В зависимости от типа файла, эта компонента может парсить и рендерить как HTML/XML и CSS, так и PDF .
Network выполняет xhr запросы за ресурсами, и в целом, общение браузера с остальным интернетом происходит через эту компоненту, включая проксирование, кэширование и так далее.
JS Engine место где парсится и исполняется js код.
UI Backend используется чтобы рисовать стандартные компоненты типа чекбоксов, инпутов, кнопок.
Data Persistence отвечает за хранение локальных данных, например в куках, SessionStorage, indexDB и так далее.
Далее узнаем как рассмотренные компоненты браузера взаимодействуют между собой и разберем подробнее, что происходит внутри Rendering Engine. Другими словами …
Как браузер переводит html в пиксели на экране?
Итак, с помощью компонента Network браузер начал получать html-файл чанками обычно по 8кб, что дальше? А далее идет процесс парсинга (спецификация процесса) и рендеринга этого файла в компоненте, как вы уже догадались — Rendering Engine.
Важно! Для повышения юзабилити, браузер не дожидается пока загрузится и распарсится весь html. Вместо этого браузер сразу пытается отобразить пользователю страницу (далее рассмотрим как).
Сам процесс парсинга выглядит так:
Результатом парсинга является DOM дерево. Возьмем к примеру такой html:
DOM дерево такого html файла будет выглядеть так:
По мере того как браузер парсит html файл, он встречает теги содержащие ссылки на сторонние ресурсы ( <link>, <script>, <img> и так далее) — по мере их обнаружения происходит запрос за этими ресурсами.
Таким образом, отправив запрос по адресу прописанному в атрибуте href тега <link rel="stylessheet"> и получив файл css стилей, браузер парсит этот файл и строит так называемый CSS Object Model — CSSOM.
Представим что у нас есть такой файл стилей:
Из которого получим такой CSSOM:
Attention: тут построено дерево из стилей нашего css-файла. Кроме того, также есть user agent's styles — дефолтные стили браузера и инлайновые стили — прописанные в html тегах.
Подробнее об алгоритме парсинга css стилей можно прочитать в спецификации.
Теперь у нас есть DOM и CSSOM - первый отвечает на вопрос «что?», второй на вопрос «как?». Если думаете, что следующим этапом является соединение DOM и CSSOM'а, то вы совершенно правы! DOM + CSSOM = Render Tree.
Render Tree — это дерево видимых (!) элементов построенных в том порядке, в котором они должны рендериться на странице. Обратите внимание, что элементы имеющие css правило display: none или другие, отрицательно влияющие на отображение — не будут находится в render tree.
Браузер строит Render Tree чтобы точно определить что ему нужно отрисовать и в каком порядке. Построение Render дерева происходит примерно так: начиная с рутового элемента (html), парсер проходит по всем видимым элементам (пропуская link, script, meta, скрытые через css элементы) и для каждого видимого элемента находит соответствующее css правило из CSSOM.
В движке firefox'a элементы Render Tree называются фреймами (frames). Webkit использует термин renderer или render object. Render object знает как разместить себя на странице, а так же содержит информацию о своих дочерних элементах. И для самых любознательных, если заглянуть в исходники webkit'a — можно найти класс который так и называется — RenderObject.
Продолжая наш пример мы получим такой Render Tree:
На данный момент мы имеем в некотором состоянии Render Tree — дерево содержащее информацию о том что и как нужно отрисовать. Теперь браузер должен понять на каком месте и с какими размерами будет отображаться элемент. Процесс вычисления позиции и размеров называется Layout.
Layout — это рекурсивный процесс определения положения и размеров элементов из Render Tree. Он начинается от корневого Render Object, которым является , и проходит рекурсивно вниз по части или всей иерархии дерева высчитывая геометрические размеры дочерних render object'ов. Корневой элемент имеет позицию (0,0) и его размеры равны размерам видимой части окна, то есть размеру viewport'a.
В Html используется поточная модель компоновки (flow based layout), другими словами геометрические размеры элементов в некоторых случаях можно рассчитать за один проход (если элементы, встречающиеся в потоке позже, не влияют на позицию и размеры уже пройденных элементов).
Layout может быть глобальный, когда требуется рассчитать положение render object'ов всего дерева, и инкрементальный, когда требуется рассчитать только часть дерева. Глобальный layout происходит, например, при изменении размеров шрифта или при событии resize'a. Инкрементальный layout происходит только для render object'ов, помеченных как «dirty».
Пара слов о «системе грязных битов (dirty bit system)». Эта система используется браузерами для оптимизации процесса, чтобы не пересчитывать весь layout. При добавлении нового или изменении существующего render object — он сам и его дочерние элементы помечаются флагом «dirty». Если render object не изменяется, но его дочерние элементы были изменены или добавлены, то этот render object помечается как «children are dirty».
К концу процесса layout каждый render object имеет свое положение и размеры.
Подводя промежуточный итог: браузер знает что, как и где рисовать. Следовательно — осталось только нарисовать. Этот процесс, как ни странно, называется Paint.
Paint — этап, где пиксель монитора заполняется цветом указанным в свойствах render object'а и белый экран превращается в картину задуманную автором (разработчиком). На всем пути рендеринга — это самый дорогой процесс (не то чтобы предыдущее дешевые).
Также, как и процесс layout, отрисовка (paint) может быть глобальной — дерево перерисовывается полностью, и инкрементальной — дерево перерисовывается частично. Для частичного перерисовывания render object помечает свой rectangle как невалидный. Операционная система расценивает эту область как требующую перерисовки и вызывает событие paint. При этом браузер умеет объединять области, чтобы выполнить разом перерисовку для всех мест, где это необходимо.
Определение размеров и положения элементов дерева (layout) и перерисовка (paint) являются дорогостоящими процессами. Они выполняются на уровне CPU. Разрабатывая динамические веб приложения, в которых эти процессы будут запускаться очень часто — мы никогда не достигнем плавных анимаций.
Значит, должно быть что-то, что помогло бы создавать сайты с богатой анимацией, при этом не нагружая CPU и рисуя каждый кадр менее чем за 16,6мс (60 fps). Действительно, браузер выполняет еще один этап, который помогает оптимизировать динамику сайтов — Composite (композиция).
Перед композицией, все нарисованные элементы находятся на одном слое (memory layer). То есть, изменение параметров (например, геометрических размеров или положения) одних элементов повлекут перерасчет параметров соседних элементов. Но если распределить элементы на композиционные слои — изменение параметров элемента вызовут перерасчет только на определенном слое, не затрагивая при этом элементы на других слоях. Таким образом, этот процесс является самым дешевым по производительности, поэтому нужно стараться вносить изменения вызывающие только composite.
Резюмируя вышесказанное, получаем такой процесс рендеринга веб страницы:
TLDR;
Браузер получает html файл, парсит его и строит DOM. Встречая css стили, браузер их подгружает, парсит, строит CSSOM и объединяет вместе с DOM'ом — получаем Render Tree. Осталось выяснить где расположить элементы из Render Tree — этим занимается задача layout. После расположения элементов, можно начать рисовать их — это задача paint, этап на котором заполняются пиксели экрана.
Динамика
При добавлении новой ноды в dom дерево — очевидно браузеру нужно добавить новый объект в дерево, посчитать его положение на странице, посчитать положения других элементов на странице (если они были аффектнуты новым элементом), и в конце все это нарисовать — звучит дорого. Поэтому делая такие операции необходимо иметь в виду производительность, ведь не каждый пользователь интернета запускает ваше веб-приложение на самой последней модели устройства.
Подводя итог, мы рассмотрели из каких компонентов состоит браузер, как они взаимодействуют друг с другом и как Rendering Engine рисует страницу пользователю.
Посмотреть вышеописанное можно в devtools'ах хрома, но чтобы не выходить за рамки названия статьи — на этом пока все.
Шаг 1. Подготовка
В архиве для загрузки хранятся несколько файлов, но вам нужен только один simple_html_dom.php. Все остальные файлы - это примеры и документация.
Шаг 2. Основы парсинга
Данную библиотеку очень просто использовать, но есть несколько основных моментов, которые следует изучить до того, как вы начнете приводить ее в действие.
Загрузка HTML
Вы можете создать исходный объект загрузив HTML либо из строки, либо из файла. Загрузка из файла может быть выполнена либо через указание URL, либо из вашей локальной файловой системы.
Примечания: Метод load_file() делегирует работу функции PHP file_get_contents. Если allow_url_fopen не установлен в значение true в вашем файле php.ini, то может отсутствовать возможность открывать удаленные файлы таким образом. В этом случае вы можете вернуться к использованию библиотеки CURL для загрузки удаленных страниц, а затем прочитать с помощью метода load().
Доступ к информации
Как только у вас будет объект DOM, вы сможете начать работать с ним, используя метод find() и создавая коллекции. Коллекция - это группа объектов, найденных по селектору. Синтаксис очень похож на jQuery.
В данном примере HTML мы собираемся разобраться, как получить доступ к информации во втором параграфе, изменить ее и затем вывести результат действий.
Строки 2-4: Загружаем HTML из строки, как объяснялось выше.
Строка 6: Находим все тэги <p> в HTML, и возвращаем их в массив. Первый параграф будет иметь индекс 0, а последующие параграфы индексируются соответственно.
Строка 8: Получаем доступ ко второму элементу в нашей коллекции параграфов (индекс 1), добавляем текст к его атрибуту innertext. Атрибут innertext представляет содержимое между тэгами, а атрибут outertext представляет содержимое включая тэги. Мы можем заменить тэг полностью, используя атрибут outertext.
Теперь добавим одну строку и модифицируем класс тэга нашего второго параграфа.
Окончательный вид HTML после команды save будет иметь вид:
Другие селекторы
Несколько других примеров селекторов. Если вы использовали jQuery, все покажется вам знакомым.
Первый пример требует пояснений. Все запросы по умолчанию возвращают коллекции, даже запрос с ID, который должен вернуть только один элемент. Однако, задавая второй параметр, мы говорим “вернуть только первый элемент из коллекции”.
Это означает, что $single - единичный элемент, а не не массив элементов с одним членом.
Остальные примеры достаточно очевидны.
Документация
Полная документация по библиотеке доступна на странице проекта.
Шаг 3. Пример из реального мира
Начнем с подключения библиотеки и вызова функции getArticles с указанием страницы, с которой мы хотим начать парсинг.
Так же объявим глобальный массив, чтобы сделать проще сбор все информации о статьях в одном месте. Прежде чем начинать парсинг взглянем, как описывается статья на сайте Nettuts+.
Так представлен основой формат поста на сайте, включая комментарии исходного кода. Почему важны комментарии? Они подсчитываются парсером как узлы.
Шаг 4. Начало функции парсинга
Начинаем с объявления глобального массива, создаем новый объект simple_html_dom, и затем загружаем страницу для парсинга. Данная функция будет рекурсивно вызываться, поэтому устанавливаем для нее в качестве параметра URL страницы.
Шаг 5. Находим ту информацию, которая нам нужна
Это суть функции getArticles. Нужно разобраться более детально, чтобы понять, что происходит.
Строка 1: Создаем массив элементов – тег div с классом preview. Теперь у нас есть коллекция статей, сохраненная в $items.
Строка 4: $post теперь ссылается на единичный div класса preview. Если мы взглянем в оригинальный HTML, то увидим, что третий элемент потомок - это тег H1, который содержит заголовок статьи. Мы берем его и присваиваем $articles[index][0].
Помните о начале отсчета с 0 и учете комментариев исходного кода, когда будете определять правильный индекс узла.
Строка 5: Шестой потомок $post - это <div Нам нужен текст описания из него, поэтому мы используем outertext – в описание будет включен тег параграфа. Единичная запись в массиве статей будет выглядеть примерно так:
Шаг 6, Работа со страницами
первым делом нужно определить, как найти следующую страницу. На сайте Nettuts+ о номере страницы очень легко догадаться по URL, но нам нужно получать ссылку в парсинге.
Если посмотреть на HTML, то можно найти следующее:
Это сслыка на следующую страницу, и мы можем легко ее найти по классу ‘nextpostslink’. Теперь эта информация может быть использована.
В первой строке мы проверяем, можно ли найти ссылку с классом nextpostslink. Отметим использование второго параметра в функции find(). Таким образом мы указываем, что хотим получить первый элемент (индекс 0) в возвращаемой коллекции. $next содержит единичный элемент, а не коллекцию.
Затем мы присваиваем ссылку HREF переменной $URL. Это важно, потому, что далее мы удаляем объект HTML. Чтобы предотвратить утечку памяти в php5, текущий объект simple_html_dom должен быть очищен и разустановлен, прежде чем другой объект будет создан. Если этого не сделать, то вся доступная память может быть поглощена.
В завершение, мы вызываем функцию getArticles с URL следующей страницы. Рекурсия прерывается, когда не остается страниц для парсинга.
Шаг 7. Вывод результатов
Первое, мы собираемся установить несколько основных стилей. Все абсолютно произвольно - вы можете устанавливать то, что нравится.
Затем мы пишем маленькую функцию на PHP в странице для вывода предварительно сохраненной информации.
Окончательный результат - это одна страница HTML со списком всех статей со страниц Nettuts+, начиная с той, которая была указана в первом вызове getArticles().
Шаг 8. Заключение
Если Вы запускаете парсинг для большого количества страниц (скажем, весь сайт), то это может занять много времени. На таком сайте как Nettuts+, который имеет боле 86страниц, процесс парсинга может длиться более минуты.
Данный урок открывает для вас тему парсинга HTML. Существуют другие методы методы работы с DOM, которые позволяют работать с селектором xpath для поиска элементов. Описанная в данном уроке библиотека проста для использования и отлично подходит для быстрого старта. Помните, что нужно спрашивать разрешения, прежде проводить скрепинг сайта.
5 последних уроков рубрики "PHP"
Фильтрация данных с помощью zend-filter
Когда речь идёт о безопасности веб-сайта, то фраза "фильтруйте всё, экранируйте всё" всегда будет актуальна. Сегодня поговорим о фильтрации данных.
Контекстное экранирование с помощью zend-escaper
Обеспечение безопасности веб-сайта — это не только защита от SQL инъекций, но и протекция от межсайтового скриптинга (XSS), межсайтовой подделки запросов (CSRF) и от других видов атак. В частности, вам нужно очень осторожно подходить к формированию HTML, CSS и JavaScript кода.
Подключение Zend модулей к Expressive
Expressive 2 поддерживает возможность подключения других ZF компонент по специальной схеме. Не всем нравится данное решение. В этой статье мы расскажем как улучшили процесс подключение нескольких модулей.
Совет: отправка информации в Google Analytics через API
Подборка PHP песочниц
Подборка из нескольких видов PHP песочниц. На некоторых вы в режиме online сможете потестить свой код, но есть так же решения, которые можно внедрить на свой сайт.
Итак мы хотим получить конкретную информацию с сайта. Пошагово разберем как это сделать. Для начала нам нужно получить объект Document . Это представление нашей html страницы. В Jsoup есть несколько способов превратить сайт в объект Document . Подключиться к серверу Jsoup сам подключается к сайту. Данный способ самый простой, но он годится только для тестирования. Есть более удобные и гибкие http клиенты. Также имейте ввиду, каким бы http клиентом вы не пользовались, добавляйте в запрос заголовок User-Agent с значением например Chrome/81.0.4044.138 . По этому заголовку сервер определяет с какого устройстрва вы подключились. Без этого заголовка сервер считает вас ботом и может забанить. Из файла; Это основной способ получения объекта Document . Последний аргумент "hh.ru" - базовый URI. Это нужно для создания абсолютных ссылок из относительных, которые присутствуют на сайте. Из строки Далее я буду демонстрировать библиотеку на этом html, который представляет упрощенный сайт. Получение тега Основная задача при парсинге - получить нужный тег. Делать мы это будем при помощи метода select . Обратите внимание, что он всегда возвращает список тегов. Если теги не найдены, то список будет пустым. В аргумент метода нужно передать css селектор, по которому ищутся теги. На селекторах я остановлюсь подробнее, потому что вся работа сводится к написанию правильного селектора. Обычно нам нужно составить его так, чтобы он возвращал один тег. Получить теги теги h1 Вывод: Получить теги title . Знак > выбирает теги title вложенные в тег head Получить теги div вложенные в body Получить первый тег div вложенный в body . Получать тег по порядковому номеру плохой способ, потому что его положение на сайте может поменяться. Лучше определить тег по абсолютным параметрам. Такими параметрами являются атрибуты class и id Получить тег div c классом "content", вложенный в body Получить теги c id "123" Получить теги div c классом "header" и "main", вложенные в body , но без тегов h1 Вывод: Методы Elements Когда мы получили список Elements можно извлечь данные из него. Напомню, что обычно селектором ищется один тег, т.е. у Elements должен быть размер 1. количество найденных тегов получить первый тег из списка найденных текст вложенный в тег значение атрибута "href" строковое представление тега Если вам нужно быстро получить селектор элемента - в браузере откройте панель разработчика (f12), нажмите правой кнопкой на элемент, "просмотреть код", нажмите правой кнопкой на тег, далее "Copy" "Copy selector". Такой селектор будет не оптимальным, но для быстрого результата вполне подходит. Заключение Это основы работы с библиотекой Jsoup. Но этого вполне достаточно, чтобы парсить сайты. Для уверенной работы вам нужна только практика написания селекторов в реальных сайтах. P.s. Данная библиотека используется для решения большой задачи на 38 уровнеЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
очень хорошо написана статья, коротко, но по сути. спасибо большое. она и небольшой задаче на 19м уровне 1918 - очень помогла)
Читайте также: