Первый взгляд на безголовый браузер Puppeteer

Node.js внешний интерфейс JavaScript
Первый взгляд на безголовый браузер Puppeteer

Об авторе Феликс Ант Технологическая команда Financial Data Experience

Шаги в нашем повседневном использовании браузера: запустить браузер, открыть веб-страницу и взаимодействовать. а также无头浏览器Относится к браузерам, сценарии которых мы используем для выполнения вышеуказанного процесса, который может имитировать реальные сценарии использования браузера.

С безголовым браузером мы можем делать такие вещи, как:

  • Делайте скриншоты веб-страниц и сохраняйте их в виде изображений или PDF-файлов.
  • Захватите выполнение и рендеринг одностраничного приложения (SPA) (решите проблему, заключающуюся в том, что традиционный сканер HTTP, сканирующий одностраничное приложение, с трудом обрабатывает асинхронные запросы)
  • Выполняйте автоматическую отправку форм, автоматическое тестирование пользовательского интерфейса, имитацию ввода с клавиатуры и т. д.
  • Используйте некоторые инструменты отладки и инструменты анализа производительности, которые поставляются с браузером, чтобы помочь нам проанализировать проблему.
  • Протестируйте в новейшей среде безголового браузера и используйте новейшие функции браузера.
  • Пишите сканеры, чтобы они делали то, что вы хотите~

Существует множество безголовых браузеров, включая, помимо прочего:

  • PhantomJS, основанный на Webkit
  • SlimerJS, основанный на Gecko
  • HtmlUnit, основанный на Rhnio
  • TrifleJS, основанный на Trident
  • Splash, основанный на Webkit

В этой статье в основном представлен безголовый браузер (безголовый Chrome), предоставляемый Google, основанный наChrome DevTools protocolПредоставляет нам множество инкапсулированных интерфейсов для управления браузером.

простой пример кода

чтобы использоватьasync/awaitи другие новые функции, вам необходимо использовать версию Node v7.6.0 или более позднюю.

Запустить/закрыть браузер, открыть страницу

    // 启动浏览器
    const browser = await puppeteer.launch({
        // 关闭无头模式,方便我们看到这个无头浏览器执行的过程
        // headless: false,
        timeout: 30000, // 默认超时为30秒,设置为0则表示不设置超时
    });

    // 打开空白页面
    const page = await browser.newPage();

    // 进行交互
    // ...

    // 关闭浏览器
    // await browser.close();

Установить размер окна страницы

    // 设置浏览器视窗
    page.setViewport({
        width: 1376,
        height: 768,
    });

Введите URL

    // 地址栏输入网页地址
    await page.goto('https://google.com/', {
        // 配置项
        // waitUntil: 'networkidle', // 等待网络状态为空闲的时候才继续执行
    });

Сохранить веб-страницу как изображение

Откройте веб-страницу и сохраните скриншот локально:

await page.screenshot({
    path: 'path/to/saved.png',
});

полный пример кода

сохранить веб-страницу в формате pdf

Откройте веб-страницу и сохраните PDF-файл локально:

await page.pdf({
     path: 'path/to/saved.pdf',
    format: 'A4', // 保存尺寸
});

полный пример кода

выполнить скрипт

Чтобы получить среду хостинга на открытой веб-странице, мы можем использоватьPage.evaluateметод:

// 获取视窗信息
const dimensions = await page.evaluate(() => {
    return {
        width: document.documentElement.clientWidth,
        height: document.documentElement.clientHeight,
        deviceScaleFactor: window.devicePixelRatio
    };
});
console.log('视窗信息:', dimensions);

// 获取 html
// 获取上下文句柄
const htmlHandle = await page.$('html');

// 执行计算
const html = await page.evaluate(body => body.outerHTML, htmlHandle);

// 销毁句柄
await htmlHandle.dispose();

console.log('html:', html);

Page.$Можно понимать как наше обычно используемоеdocument.querySelector, а такжеPage.?соответствуетdocument.querySelectorAll.

полный пример кода

Отправить форму автоматически

Откройте главную страницу Google, введите ключевое слово и нажмите Enter для поиска:

// 地址栏输入网页地址
await page.goto('https://google.com/', {
    waitUntil: 'networkidle', // 等待网络状态为空闲的时候才继续执行
});

// 聚焦搜索框
// await page.click('#lst-ib');
await page.focus('#lst-ib');

// 输入搜索关键字
await page.type('辣子鸡', {
   delay: 1000, // 控制 keypress 也就是每个字母输入的间隔
});

// 回车
await page.press('Enter');

полный пример кода

Пример сложного кода

Каждое простое действие связано с серией сложных взаимодействий Давайте рассмотрим еще два конкретных примера.

Сканирование одностраничного приложения: имитация заказа на вынос Ele.me

Традиционный сканер основан на протоколе HTTP, имитирующем UserAgent для отправки http-запроса и использующем регулярный синтаксический анализ для получения содержимого для сканирования после получения содержимого html.

Но при столкновении с одностраничным приложением (SPA) или проверке входа в систему этот тип сканера относительно бессилен.

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

Различные клики, ввод и другие инструкции, предоставляемые безголовым браузером, полностью имитируют человеческие клики, ввод и другие инструкции, поэтому не нужно беспокоиться об обычном письме.

Конечно, в некоторых сценариях эффективнее использовать традиционные поисковые роботы HTTP (записывающие обычные совпадения).

Я не буду здесь подробно сравнивать эти различия.Следующий пример используется только в качестве демонстрации для имитации полного взаимодействия человека с компьютером: использование мобильной версии Ele.me для заказа еды на вынос.

Сначала посмотрите на эффект:

Код длинный и не весь выложен, ключ в несколько строк:

const puppeteer = require('puppeteer');
const devices = require('puppeteer/DeviceDescriptors');
const iPhone6 = devices['iPhone 6'];

console.log('启动浏览器');
const browser = await puppeteer.launch();

console.log('打开页面');
const page = await browser.newPage();

// 模拟移动端设备
await page.emulate(iPhone6);

console.log('地址栏输入网页地址');
await page.goto(url);

console.log('等待页面准备好');
await page.waitForSelector('.search-wrapper .search');

console.log('点击搜索框');
await page.tap('.search-wrapper .search');

await page.type('麦当劳', {
    delay: 200, // 每个字母之间输入的间隔
});

console.log('回车开始搜索');
await page.tap('button');

console.log('等待搜素结果渲染出来');
await page.waitForSelector('[class^="index-container"]');

console.log('找到搜索到的第一家外卖店!');
await page.tap('[class^="index-container"]');


console.log('等待菜单渲染出来');
await page.waitForSelector('[class^="fooddetails-food-panel"]');


console.log('直接选一个菜品吧');
await page.tap('[class^="fooddetails-cart-button"]');

// console.log('===为了看清楚,傲娇地等两秒===');
await page.waitFor(2000);
await page.tap('[class^=submit-btn-submitbutton]');

// 关闭浏览器
await browser.close();

Ключевые шаги:

  • загрузить страницу
  • Подождите, пока DOM будет нажат, чтобы отобразиться, и нажмите
  • Продолжайте ждать, пока DOM, который нужно щелкнуть, будет отображен на следующем шаге, а затем нажмите

Несколько ключевых команд:

  • page.tap(илиpage.click) за клики
  • page.waitForSelectorЭто означает ожидание появления указанного элемента на веб-странице.Если он уже появился, он немедленно продолжит выполнение.Следующие параметры:selectorселектор, такой же, как наш обычно используемыйdocument.querySelectorПолученные параметры совпадают
  • page.waitForможно сдать позжеselectorСелектор,functionфункция илиtimeoutМиллисекантное время, напримерpage.waitFor(2000)Относится к ожиданию в течение 2 секунд перед продолжением выполнения. В примере эта функция используется для приостановки операции в основном для демонстрации.

Все вышеперечисленные команды могут принимать одинselectorСелектор используется как параметр, и вот несколько дополнительных методов:

  • page.$(selector)с нашим обычнымdocument.querySelector(selector)Непротиворечивый, возвращаетElementHandleдескриптор элемента
  • page.?(selector)с нашим обычнымdocument.querySelectorAll(selector)Согласован, возвращает массив

В контексте браузера с заголовком мы выбираем элемент следующим образом:

const body = document.querySelector('body');
const bodyInnerHTML = body.innerHTML;
console.log('bodyInnerHTML: ', bodyInnerHTML);

В безголовом браузере нам сначала нужно получить дескриптор, а после получения информации в среде через дескриптор уничтожить дескриптор.

// 获取 html
// 获取上下文句柄
const bodyHandle = await page.$('body');
// 执行计算
const bodyInnerHTML = await page.evaluate(dom => dom.innerHTML, bodyHandle);
// 销毁句柄
await bodyHandle.dispose();
console.log('bodyInnerHTML:', bodyInnerHTML);

Кроме того, вы также можете использоватьpage.$eval:

const bodyInnerHTML = await page.$eval('body', dom => dom.innerHTML);
console.log('bodyInnerHTML: ', bodyInnerHTML);

page.evaluateЭто означает, что для выполнения скрипта в среде браузера вы можете передать второй параметр как дескриптор, иpage.$evalЗатем выполните операцию с выбранным элементом DOM.

полный пример кода

Массовый экспорт веб-страниц: загрузка книг Тьюринга

я здесьСообщество ТьюрингаЯ купил много электронных книг наmobiотформатировать вkindleили нажатьpdfФормат отправляется в почтовый ящик для чтения, но эти push-каналы часто закрыты, и их можно оставить на веб-странице только для чтения.

Для меня не очень удобно, и онлайн-эффект чтения этих книг - это рендеринг сервера (с большой меткой, не в состоянии выбрать хорошую версию типографии), лучший способ, конечно, непосредственно читать и сохранять в PDF или картинки.

Используя безголовый режим браузера, я написал простую загрузку купленных книг какpdfВ локальном скрипте поддерживается пакетная загрузка купленных книг.

Используйте метод, введите пароль учетной записи и сохраните путь, например:

$ node ./demo/download-ituring-books.js '用户名' '密码' './books'

Уведомление:puppeteerизPage.pdf()В настоящее время поддерживается только использование в безголовом режиме, поэтому, если вы хотите увидеть процесс захвата с состоянием головы, выполните дляPage.pdf()Этот шаг сначала сообщит об ошибке:

Поэтому при запуске этого скрипта он должен оставаться в безголовом режиме:

const browser = await puppeteer.launch({
    // 关闭无头模式,方便我们看到这个无头浏览器执行的过程
    // 注意若调用了 Page.pdf 即保存为 pdf,则需要保持为无头模式
    // headless: false,
});

Взгляните на эффект исполнения:

На моей книжной полке более 20 книг, после скачивания она выглядит так:

полный пример кода

Что еще может безголовый браузер?

Проще говоря, безголовый браузер может имитировать различные действия людей в безголовом браузере. Естественно, много ручной работы можно выполнить с помощью безголового браузера (например, в процессе загрузки pdf выше на самом деле человек открывает каждую страницу статьи, а затем нажимаетctrl+pилиcommand+pсохранить в локальный автоматизированный процесс).

Поскольку то, что можно решить с помощью автоматизированных средств, не требует повторного человеческого труда, кроме того, мы также можем:

  • инструмент автоматизации Например, автоматическая отправка формы, автоматическая загрузка
  • Автоматизируйте тестирование пользовательского интерфейса Если вы записываете правильную структуру DOM или снимок экрана, а затем автоматически выполняете указанную операцию, проверьте соответствие структуры DOM или снимка экрана (утверждение пользовательского интерфейса).
  • инструмент контроля времени Например, регулярно делайте снимки экрана, чтобы отправлять еженедельные отчеты, или регулярно проверяйте, доступны ли страницы важных бизнес-направлений, и сотрудничайте с оповещениями по электронной почте.
  • рептилия Например, там, где традиционные сканеры HTTP не могут сканировать, это можно сделать с помощью возможностей рендеринга безголовых браузеров.
  • etc

Заинтересованные студенты могут подписаться на колонку или отправить свое резюме на qingsheng.lqs####alibaba-inc.com'.replace('####', '@'). Приглашаются люди с высокими идеалами~

Оригинальный адрес:GitHub.com/proto team/no…