برنامه‌نویسی

آموزش Web Scraping با Puppeteer

آموزش web scraping با puppeteer

Puppeteer کتابخانه‌ای است که با ‌Node.js توسعه داده شده و یک high-level API را برای کنترل مرورگر Chrome یا Chromium از طریق پروتکل DevTools فراهم می‌کند. اسکریپت‌هایی که با این کتابخانه توسعه داده می‌شوند به‌صورت پیش‌فرض، headless هستند اما شما می‌توانید آن‌ها را به‌گونه‌ای پیکربندی کنید که به‌صورت non-headless اجرا شوند.

در این مقاله سعی داریم تا با Puppeteer که از آن برای پروژه‌های Automated Browsing و Web Scraping استفاده می‌شود، پروژه‌ای کوچک را پیاده‌سازی کنیم. اسکریپت ما قادر خواهد بود تا با دریافت ورودی کاربر و جستجوی آن از میان متدهای مربوط به Array در مستندات زبان JavaScript سایت MDN، یک نتیجه را به ما برگرداند.

حال در کنار Puppeteer تصمیم گرفته‌ایم تا از پکیج‌های Chalk و Inquirer استفاده کنیم. پکیج Chalk به ما کمک می‌کند تا خروجی‌های خود را در Terminal سیستم‌عامل استایل‌دهی کرده و با Inquirer قادر خواهیم بود تا رابط کاربری Command line را ایجاد کنیم.

برای این پروژه یک پوشه ایجاد کرده و دستور زیر را در آن مسیر اجرا کنید:

npm init

پس از اجرای دستور فوق، مواردی از شما خواسته می‌شود که می‌توانید آن‌ها را با دقت وارد کرده یا فقط Enter بزنید و در مرحله‌ی آخر کلمه‌ی yes را تایپ کنید تا فایلی با نام package.json در مسیر پروژه ایجاد شود. در مرحله‌ی بعد بایستی پیش‌نیازهای پروژه که پکیج‌های Puppeteer، Chalk و Inquirer هستند را با اجرای دستور زیر نصب کنید:

npm i puppeteer chalk inquirer

پس از نصب پیش‌نیازهای پروژه به سراغ ساخت فایل اصلی پروژه‌ می‌رویم. به‌همین منظور یک فایل JavaScript با نام app.js در مسیر پروژه ایجاد کنید و یک دستور start در فایل package.json به‌صورت زیر تعریف کنید:

...
"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "start": "node app.js"
}
...

با تعریف این بخش از این پس می‌توانید با اجرای دستور npm start فایل app.js را با Node.js اجرا کنید.

دریافت ورودی از کاربر

برای دریافت ورودی از کاربر، پکیج inquirer را در فایل app.js فراخوانی می‌کنیم:

const inquirer = require('inquirer');

برای این پروژه از متد prompt() استفاده می‌کنیم و type آن را input قرار می‌دهیم. سپس نام ورودی دریافت شده توسط کاربر را userInput می‌گذاریم و پیغامی که در ابتدا به کاربر نمایش داده می‌شود Enter array method to search for خواهد بود:

inquirer.prompt({
  type: 'input',
  name: 'userInput',
  message: 'Enter array method to search for'
})

پس از دریافت ورودی کاربر می‌توانیم متغیر userInput که قبلا تعریف کرده بودیم را استفاده کرده و آن را در وبسایت MDN، با استفاده از Puppeteer جستجو کنیم و سپس نتایج را به کاربر برگردانیم. به‌دلیل اینکه متد prompt() یک Promise به ما برمی‌گرداند می‌توانیم از .then() استفاده کرده تا ورودی کاربر را به فانکشن getResults() که به کمک Puppeteer ایجاد شده، پاس بدهیم. همچنین می‌توانیم تمام این فرایند را در یک wrap با نام start() انجام دهیم. از آنجا که فانکشن start() برای داشتن عملکرد صحیح به فانکشن getResults() نیاز دارد بایستی در مرحله‌ی بعد آن را تعریف ‌کنیم:

const start = () => {
	inquirer.prompt({
		type: 'input',
		name: 'userInput',
		message: 'Enter array method to search for'
	})
		.then(resp => getResults(resp.userInput))
}

Web Scraping با Puppeteer

در این مرحله پیاده‌سازی فانکشن getResults() را با فراخوانی پکیج Puppeteer در ابتدای فایل app.js شروع خواهیم کرد:

const puppeteer = require('puppeteer')

فانکشن getResults() را به‌صورت asynchronous تعریف خواهیم کرد. به‌همین منظور یک instance از مرورگر Chromium ایجاد کرده و یک صفحه‌ی جدید در آن مرورگر باز می‌کنیم:

async function getResults(term) {
	const browser = await puppeteer.launch()
	const page = await browser.newPage()
}

متغیر term همان ورودی کاربر است که توسط فانکشن start() دریافت می‌شود.puppeteer.launch() یک instance از مرورگر Chromium به‌صورت headless در اختیار ما قرار می‌دهد که می‌توانیم با browser.newPage() یک تب جدید باز کنیم.

در مرحله‌ی بعد گزینه‌ی headless را در puppeteer.launch() برابر با false قرار می‌دهیم تا روند کار اسکریپت را بهتر متوجه شویم. در انتهای آدرس URL سایت MDN مقدار locale=en-US را تنظیم کرده‌ایم تا مطمئن شویم حتما نتایج جستجو به زبان انگلیسی نمایش داده می‌شوند.

پس از باز شدن URL سایت MDN بایستی ورودی کاربر را در نوار جستجو که id آن را در اسکریپت تعریف کرده‌ایم، تایپ کرده و دکمه‌ی Enter را وارد کنیم تا جستجو انجام شود:

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 محدود می‌شود بنابراین بخشی از جستجو‌ی ما ثابت است. به‌همین دلیل مقدار Array.prototype را به‌صورت ثابت قرار داده‌ایم و سپس ورودی کاربر را در انتهای آن اضافه می‌کنیم.

پس از بارگیری نتایج جستجو بایستی اولین نتیجه را دریافت کنیم. تمام نتایج موجود در صفحه دارای یک class با نام result هستند و هر عنوان دارای یک class با نام result-title است. اگر یک querySelector بر روی result-title پیاده‌سازی کنیم می‌توانیم اولین نتیجه را همراه با URL آن دریافت کنیم:

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
	})
}

حال بایستی یک تعریف ساده از متد جستجو شده را در Console به کاربر نمایش دهیم. به همین منظور URL اولین عنوانی که در جستجو دریافت کرده‌ایم را در یک صفحه‌ی جدید مرورگر باز کرده و اولین تگ <p> که در صفحه وجود دارد را با استفاده از querySelector انتخاب می‌کنیم و innerText آن را در Console به کاربر نمایش می‌دهیم:

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()
}

نمایش خروجی در Console

برای استایل‌دهی اسکریپت در Console، از پکیج Chalk استفاده خواهیم کرد به‌همین منظور در ابتدای فایل app.js پکیج Chalk را فراخوانی می‌کنیم:

const chalk = require('chalk')

این پکیج گزینه‌های زیادی را برای استایل‌دهی به متن‌ها در اختیار ما قرار می‌دهد و برترین مزیت آن نسبت به دیگر پکیج‌ها، استفاده آسان و بدون نیاز به تعریف مقادیر hex یا RGB است. البته اگر خودتان بخواهید می‌توانید از مقادیر RGB استفاده کنید ولی ما برای ساده نگه داشتن آموزش از مقادیر RGB استفاده نخواهیم کرد:

console.log(chalk.magentaBright(resultDef))
console.log(chalk.underline.dim(resultUrl))

با استفاده از کدهای فوق، خروجی متنی ما به رنگ magenta روشن و URL با رنگ پیش‌فرض Console اما کم رنگ‌تر همراه با underline نمایش داده می‌شود.

اکنون می‌توانید { headless: false } را از puppeteer.launch() حذف کرده و اگر همه‌ی مراحل را به‌درستی انجام داده باشید، کدهای شما به‌صورت زیر خواهند بود:

const puppeteer = require('puppeteer');
const chalk = require('chalk');
const inquirer = require('inquirer')

const mdnUrl = 'https://developer.mozilla.org/en-US/search?locale=en-US'

const start = async () => {
	inquirer.prompt({
		type: 'input',
		name: 'userInput',
		message: 'Enter array method to search for'
	})
		.then(res => getResults(res.userInput))
}

async function getResults(term) {
	const browser = await puppeteer.launch();
	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();

	console.log(chalk.magentaBright(resultDef));
	console.log(chalk.underline.dim(resultUrl));

}

start()
web scraping با puppeteer

منبع: https://levelup.gitconnected.com/playing-with-puppeteer-1438af6bf95e