В поисках выхода за пределы PhantomJS

Почему нам, наконец, нужно двигаться дальше
Почти у каждого full-stack или front-end инженера есть какой-то опыт работы с PhantomJS. PhantomJS активно используется для интеграционных тестов, мониторинга веб-сайтов, очистки данных и многого другого. Однако последний выпуск Phantom был выпущен в январе 2016 года, и он начал показывать свой возраст. Он не мог надежно запускать сайты с использованием ES6 и других современных технологий. И хотя несколько трудолюбивых участников изо всех сил старались поддерживать его в актуальном состоянии, этого стало слишком много. В августе 2017 года хранилище было официально объявлено заброшенным.

Здесь, в Quantcast, мы управляем крупнейшей в мире платформой анализа и измерения аудитории, основанной на искусственном интеллекте, которая напрямую измеряет более 100 миллионов веб-сайтов. Основой нашей платформы является Quantcast Measure. Первым шагом к началу работы с Measure является регистрация вашего сайта на quantcast.com и добавление предоставленного тега javascript на ваши веб-страницы. Этот тег javascript предоставляет нам многомерную информацию о пользователях, посещающих ваш сайт. Из-за важности этого тега нам необходимо иметь возможность быстро проверить, правильно ли он размещен на вашем сайте. Наш оригинальный сканер, написанный в 2006 году, просто брал HTML-код страницы и искал в нем тег. Это начало давать сбои с появлением менеджеров тегов. Около 2 лет назад мы создали вторую итерацию сканера с использованием PhantomJS, и теперь нам снова нужен новый сканер из-за все большего количества неудачных сканирований.

Варианты инструментов для сканера сайта
Срок действия службы менее двух лет после серьезной переделки не идеален. Как правило, мы хотели бы, чтобы такой продукт прослужил 3–4 года, прежде чем нам придется вернуться к нему снова. Одним из основных требований для следующей итерации будет использование хорошо поддерживаемого и поддерживаемого безголового браузера. Мы также хотели бы, чтобы он был максимально легким и простым в использовании. Также необходимо иметь возможность отслеживать все ресурсы страницы. После долгих поисков мы пришли к трем вариантам; Electron, Chromeless и Puppeteer.

Электрон построен на основе Chromium и NodeJS, поэтому мы надеялись, что они абстрагируются от большинства мельчайших деталей Chromium. Он выглядит хорошо обслуживаемым и простым в установке/использовании. Однако мы обнаружили, что он содержит намного больше функций, чем нам действительно нужно. Есть несколько инструментов, которые люди написали поверх него, например, NightmareJS, которые значительно упрощают использование, но в нем нет необходимого нам мониторинга ресурсов. На момент написания этой статьи в нем также отсутствует по-настоящему безголовый режим, поэтому нам потребуется дополнительная зависимость от графической среды, такая как xvfb.

Chromeless — еще один популярный автоматизированный браузерный инструмент. Это довольно новый и так в хорошем состоянии. У него очень простой API, очень похожий на «головокружительный» NightmareJS. Однако он больше ориентирован на сканирование, автоматическое тестирование и скриншоты. На самом деле это не дает нам подробных данных об уровне ресурсов, которые нам нужны для этого проекта.

Наконец мы нашли Puppeteer. Puppeteer — это то, к чему рекомендует двигаться сопровождающий PhantomJS. Он также написан поверх Chromium с использованием его нового «безголового» режима. Он поддерживается командой Chrome DevTools и используется внутри Google. Это дает нам уверенность в том, что он будет существовать долгое время. Кроме того, его API поразительно похожи на API PhantomJS, поэтому мы знаем, что он справится с тем, что нам нужно. Это зависит от довольно большой установки Chromium, но все конфигурации абстрагируются. Этот инструмент, кажется, почти полностью соответствует нашим требованиям.

Сборка/код У нас, казалось бы, простой набор требований. Получив URL-адрес, возвращайте все теги Quantcast, срабатывающие на странице. Точки доступа Puppeteer очень похожи на обычный просмотр веб-страниц. Сначала вы запускаете браузер, а затем открываете отдельные вкладки или страницы, как их называет Puppeteer. Браузер может иметь несколько страниц, и каждая из них может быть повторно использована для нескольких запросов. Поскольку они используются повторно, мы хотим, чтобы браузер ничего не кэшировал, чтобы это не влияло на загрузку страниц в будущем. Мы обнаружили, что эти варианты, кажется, делают свое дело.

Как только мы это сделаем, мы можем просто указать странице перейти к URL-адресу, который мы хотим проверить. После нескольких пробных сканирований мы стали замечать, что наша метка не всегда ловится. Мы быстро поняли, что, поскольку наш тег является асинхронным, чтобы не замедлять время загрузки страниц пользователей, иногда событие загрузки страницы запускалось до завершения работы нашего тега. К счастью, в Puppeteer есть способ подождать, пока сеть не станет бездействующей или полубездействующей. Чтобы страницы, которые автоматически обновляются (например, Twitter), никогда не завершались, мы будем использовать событие полубездействия. Итак, попробуем еще раз
page.goto(url, {waitUntil: ['load', 'networkidle2']});

Это оказалось намного лучше. Сканирование выполняется регулярно, но следующая проблема, которую мы заметили, заключается в том, что некоторые сканирования будут немного медленными или будут занимать большой объем памяти. Быстрое исправление, которое мы можем реализовать, — фактически не загружать никакие изображения (кроме нашего тега изображения). Puppeteer позволяет нам сделать это, перехватывая любой запрос до того, как он произойдет, и прерывая его.
page.setRequestInterception(true); page.on('request', request => { if( request ) { if (request.resourceType === 'image' && request.url.indexOf('pixel.quantserve.com/pixel') === -1) { request.abort(); } else { request.continue(); } } });

Последняя проблема, с которой мы столкнулись, заключалась в том, что иногда страницы просто не загружались вовремя. Мы даем страницам загрузку целую минуту, но время — деньги, и мы не можем просто ждать вечно. В этом случае нам нужно поймать случай, когда страница выдает тайм-аут, и посмотреть, не сработал ли какой-либо из наших тегов. Если это так, мы можем просто вернуть их, а если нет, то мы должны зафиксировать факт истечения времени ожидания страницы для дальнейшего расследования.
try { const resp = await page.goto(url, {waitUntil: ['load', 'networkidle2']}); result = {tags: tags, code: resp.status}; } catch (err) { // tags ended up loading, but page technically did not load successfully if(tags.length > 0){ result = {tags: tags, code: -1}; }else { result = {error: err.message}; } }

Если вы хотите увидеть полный скрипт сканирования, мы разместили его как Github Gist здесь.

Результаты
Результаты нового сканера сайтов оказались лучше, чем мы ожидали. Из 300 невыполненных сканирований 25 ранее пропущенных тегов были найдены сразу. Это были 25 потенциальных клиентов, которых мы бы упустили, и, скорее всего, это привело бы к тому, что они не получили бы максимальную отдачу от нашего продукта. Большинство сканирований также проходят менее чем за 2 секунды по сравнению с 8 секундами от PhantomJS. Недостатком является то, что Puppeteer намного тяжелее с точки зрения процессора и его сложнее настроить в облаке, чем PhantomJS, из-за его зависимости от Chromium. Это было ожидаемо, и это небольшая цена за полнофункциональный браузер, позволяющий нам наиболее точно сканировать. С момента его выпуска мы значительно сократили количество писем в службу поддержки, связанных со сканированием сайта. Нашим инженерам снова не нужно тратить время на утомительную задачу ручной проверки и добавления пропущенных сайтов. Будем надеяться, что Puppeteer продолжит поддерживаться в будущем.

Куда мы идем дальше
Как вы понимаете, есть еще много возможностей для улучшений. Во-первых, мы можем изучить возможность повторного использования объектов страницы, чтобы попытаться снизить нагрузку на ЦП и выжать из коробки еще несколько сканирований. Еще один шаг — перевести сканер на бессерверную модель с помощью AWS Lambdas или аналогичной технологии. Это позволило бы нам распараллеливать сканирование почти до бесконечности, не тратя никаких средств на простаивание оборудования. Независимо от того, в каком направлении мы идем, Puppeteer отлично подходит для наших нужд и очень прост в использовании. Поскольку его API были очень похожи на PhantomJS, мы могли быстро заменить наш существующий скрипт сканирования сайта на этот за считанные дни. Кроме того, это дает нам уверенность в том, что он будет продолжать работать и поддерживаться в течение многих лет.

Первоначально опубликовано на www.quantcast.com 18 января 2018 г.