На этой неделе я решил взглянуть на Кукольника. Я знал, что это способ создавать небольшие автоматизированные сценарии браузера, и был рад попробовать. Я сделал простой скрипт для поиска на веб-сайте документации JavaScript MDN метода Array, возврата определения метода и ссылки на веб-страницу метода Array. Это было доказательством концепции, просто чтобы я разобрался с Кукловодом.
Если вы с ним не знакомы, Puppeteer - это библиотека Node, которая предоставляет высокоуровневый API для управления Chrome или Chromium через DevTools Protocol. По умолчанию он работает без головы, но может быть настроен для работы в полном объеме (без головы).
Для этого проекта я решил также использовать пакеты Chalk и Inquirer. Мел позволит мне легко отформатировать вывод в терминале, а Inquirer позволит мне легко создать пользовательский интерфейс командной строки.
Я создал новую папку с именем searchMDN, перешел к ней в терминале и установил три зависимости.
$ npm i puppeteer chalk inquirer
Создайте новый файл JavaScript с именем app.js, и для начала я создам пользовательское приглашение, которое будет отображаться в терминале. Я также зашел в файл package.json и установил команду запуска на «node app.js»…
Создание пользовательской подсказки
Сначала потребуйте пакет inquirer в нашем файле.
const inquirer = require('inquirer')
Теперь, когда он доступен, я могу использовать его для создания подсказки пользователю. Я просто хочу, чтобы подсказка была «Введите метод массива для поиска». У Inquirer есть много вариантов того, как подсказывать пользователю, но мне не нужно много - достаточно его самого основного запроса, метода «prompt ()». Этот метод потребует от меня указать тип приглашения. Я хочу, чтобы пользователь написал имя метода, который он хочет найти. Для такого типа пользовательского ввода я буду использовать запросчик типа «ввод». Затем я должен назвать ответ, который я получаю от пользователя. Я назову его userInput.
inquirer.prompt({ type: 'input', name: 'userInput', message: 'Enter array method to search for' })
Затем мне нужно взять userInput и отправить его функции, которая использует Puppeteer для поиска на веб-сайте MDN и возврата результатов. Поскольку метод prompt () возвращает обещание, я воспользуюсь методом .then (), возьму результат userInput и передам его функции «getResults ()», которую я создам с помощью Puppeteer. Я также заключу это в функцию под названием start (). Поскольку эта функция должна быть первой, что вызывается и запускает функцию getResults (), имеет смысл вызвать start () в конце файла для инициализации этого сценария поиска метода массива.
const start = () => { inquirer.prompt({ type: 'input', name: 'userInput', message: 'Enter array method to search for' }) .then(resp => getResults(resp.userInput)) }
Автоматизация с Puppeteer
Как и выше, мне нужен пакет Puppeteer в файле app.js.
const puppeteer = require('puppeteer')
Все функции, которые мне нужны от Puppeteer, будут асинхронными, что имеет смысл. Мне нужно создать экземпляр браузера Chrome, открыть новую страницу, загрузить веб-сайт, найти идентификатор строки поиска, ввести поисковый запрос, дождаться загрузки новой страницы, затем найти первый результат, сохранить результат url в переменной, перейдите на веб-страницу результата, найдите определение, сохраните определение в переменной и выведите его в последнюю очередь на консоль. Мне нужен код, чтобы дождаться завершения каждой из этих задач, прежде чем двигаться дальше. Поэтому я собираюсь обернуть все это асинхронной функцией.
async function getResults(term) { const browser = await puppeteer.launch() const page = await browser.newPage() }
Выше я создал функцию async getResults (), которая принимает параметр, отправляемый функцией start () поискового запроса, который я буду использовать для запроса на сайте MDN. Я создаю экземпляр браузера Chrome без головы с помощью puppeteer.launch (). Как только это будет завершено, код создаст новую страницу. Затем нам нужно предоставить Puppeteer URL-адрес, по которому я хочу начать поиск.
Когда я разрабатывал этот сценарий, я включил возможность создания полноценного браузера (без заголовка). Таким образом, продвигаясь к каждому шагу, я мог быстро понять в браузере, какой следующий шаг мне нужно сделать. Я сделал это, вызвав опцию {headless: false} в puppeteer.launch ().
Я использовал домашнюю страницу разработчика и установил локаль на en-US, чтобы получить результаты только на английском языке. Используя инструменты разработчика Chrome, я обнаружил, что строка поиска на сайте имеет html-идентификатор «main-q».
const mdnUrl = 'https://developer.mozilla.org/en-US/search?locale=en-US' async function getResults(term) { const browser = await puppeteer.launch({ headless: false }) const page = await browser.newPage() await page.goto(mdnUrl) await page.type('#main-q', `Array.prototype.${term}`) await page.keyboard.down('Enter') }
Используя page.type
, я выбираю элемент, в который хочу ввести текст, и во втором параметре текст, который хочу ввести. Я ищу только методы массива, поэтому я введу термин в конце строки «Array.prototype.». После нажатия клавиши ВВОД отправляется поисковый запрос.
Теперь нам нужно дождаться загрузки новой страницы и найти первый результат. Все результаты на странице имеют класс «результат», а каждый заголовок имеет класс «заголовок-результат». Если бы я просто вызвал querySelector для «заголовка результата», он бы получил первый результат, а затем все, что осталось сделать, это получить ссылку из заголовка результата.
const mdnUrl = 'https://developer.mozilla.org/en-US/search?locale=en-US' async function getResults(term) { const browser = await puppeteer.launch({ headless: false }) const page = await browser.newPage() await page.goto(mdnUrl) await page.type('#main-q', `Array.prototype.${term}`) await page.keyboard.down('Enter') await page.waitForSelector('.result') const resultUrl = await page.evaluate(() => { const topResult = document.querySelector('.result-title') return topResult.href }) }
Хорошо, почти готово! У меня есть URL-адрес определения метода, теперь все, что мне нужно, это захватить определение для вывода на терминал.
Следуя аналогичной схеме, я открываю страницу с URL-адресом, который мы сохранили в константе resultUrl. Определение находится в самом верхнем теге ‹p›, поэтому я выбираю его с помощью querySelector, а затем сохраняю innerText в константу resultDef. После этого мы закончили с браузером, и нам нужно будет закрыть его, чтобы он больше не работал в фоновом режиме.
const mdnUrl = 'https://developer.mozilla.org/en-US/search?locale=en-US' async function getResults(term) { const browser = await puppeteer.launch({ headless: false }) const page = await browser.newPage() await page.goto(mdnUrl) await page.type('#main-q', `Array.prototype.${term}`) await page.keyboard.down('Enter') await page.waitForSelector('.result') const resultUrl = await page.evaluate(() => { const topResult = document.querySelector('.result-title') return topResult.href }) await page.goto(resultUrl) const resultDef = await page.evaluate(() => { const termDef = document.querySelector('p') return termDef.innerText }) browser.close() }
Теперь у меня есть все необходимые данные, и единственное, что мне нужно сделать, это вывести результаты на консоль.
Вывод на консоль
Затем мне нужно добавить пакет Chalk в файл app.js.
const chalk = require('chalk')
У Chalk есть масса вариантов форматирования текста, и самое приятное то, что все они на простом английском языке, без шестнадцатеричных значений или значений RGB. Что ж, вы можете использовать значения RGB с Chalk, если хотите, но я буду просто. Я просто хочу добавить немного цвета, чтобы определение и URL-адрес казались разделенными, а вывод было легче читать.
console.log(chalk.magentaBright(resultDef)) console.log(chalk.underline.dim(resultUrl))
Я делаю текст resultDef ярким пурпурным цветом, а для URL-адреса я подчеркиваю его и затемняю его цвет по сравнению с цветом по умолчанию для консоли, в которой он отображается.
Теперь я удаляю опцию {headless: false} из puppeteer.launch (), и на этом этот простой сценарий проверки концепции завершен.
Для получения дополнительной информации о любом из этих пакетов ознакомьтесь с соответствующими ссылками ниже.