Pengikisan Web dengan Browser Tanpa Kepala: Tutorial Dalang

Diterbitkan: 2022-03-11

Dalam artikel ini, kita akan melihat betapa mudahnya melakukan pengikisan web (otomatisasi web) dengan metode yang agak non-tradisional menggunakan browser tanpa kepala .

Apa itu Peramban Tanpa Kepala dan Mengapa Diperlukan?

Beberapa tahun terakhir telah melihat web berkembang dari situs web sederhana yang dibangun dengan HTML dan CSS kosong. Sekarang ada lebih banyak aplikasi web interaktif dengan UI yang indah, yang sering dibuat dengan kerangka kerja seperti Angular atau React. Dengan kata lain, saat ini JavaScript mengatur web, termasuk hampir semua hal yang berinteraksi dengan Anda di situs web.

Untuk tujuan kami, JavaScript adalah bahasa sisi klien. Server mengembalikan file atau skrip JavaScript yang disuntikkan ke dalam respons HTML, dan browser memprosesnya. Sekarang, ini adalah masalah jika kita melakukan semacam pengikisan web atau otomatisasi web karena lebih sering daripada tidak, konten yang ingin kita lihat atau kikis sebenarnya dirender oleh kode JavaScript dan tidak dapat diakses dari respons HTML mentah bahwa server memberikan.

Seperti yang kami sebutkan di atas, browser tahu cara memproses JavaScript dan membuat halaman web yang indah. Sekarang, bagaimana jika kita dapat memanfaatkan fungsi ini untuk kebutuhan scraping kita dan memiliki cara untuk mengontrol browser secara terprogram? Di situlah otomatisasi browser tanpa kepala masuk!

Tanpa kepala? Permisi? Ya, ini hanya berarti tidak ada antarmuka pengguna grafis (GUI). Alih-alih berinteraksi dengan elemen visual seperti yang biasa Anda lakukan—misalnya dengan mouse atau perangkat sentuh—Anda mengotomatiskan kasus penggunaan dengan antarmuka baris perintah (CLI).

Chrome dan Dalang Tanpa Kepala

Ada banyak web scraping tools yang bisa digunakan untuk headless browsing, seperti Zombie.js atau headless Firefox menggunakan Selenium. Tapi hari ini kita akan menjelajahi Chrome tanpa kepala melalui Puppeteer, karena ini adalah pemain yang relatif baru, dirilis pada awal 2018. Catatan editor: Perlu disebutkan Browser Jarak Jauh Intoli, pemain baru lainnya, tetapi itu harus menjadi subjek untuk yang lain artikel.

Apa sebenarnya dalang itu? Ini adalah pustaka Node.js yang menyediakan API tingkat tinggi untuk mengontrol Chrome atau Chromium tanpa kepala atau untuk berinteraksi dengan protokol DevTools. Ini dikelola oleh tim Chrome DevTools dan komunitas sumber terbuka yang mengagumkan.

Cukup bicara—mari masuk ke kode dan jelajahi dunia tentang cara mengotomatiskan pengikisan web menggunakan penjelajahan tanpa kepala Dalang!

Mempersiapkan Lingkungan

Pertama-tama, Anda harus menginstal Node.js 8+ di mesin Anda. Anda dapat menginstalnya di sini, atau jika Anda pecinta CLI seperti saya dan suka bekerja di Ubuntu, ikuti perintah berikut:

 curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - sudo apt-get install -y nodejs

Anda juga memerlukan beberapa paket yang mungkin tersedia atau tidak tersedia di sistem Anda. Untuk amannya, coba instal itu:

 sudo apt-get install -yq --no-install-recommends libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 libnss3

Siapkan Chrome dan Dalang Tanpa Kepala

Saya akan merekomendasikan menginstal Puppeteer dengan npm , karena itu juga akan menyertakan versi Chromium terbaru yang stabil yang dijamin berfungsi dengan perpustakaan.

Jalankan perintah ini di direktori root proyek Anda:

 npm i puppeteer --save

Catatan: Ini mungkin memakan waktu cukup lama karena Dalang perlu mengunduh dan memasang Chromium di latar belakang.

Oke, sekarang kita sudah siap dan terkonfigurasi, biarkan kesenangan dimulai!

Menggunakan Puppeteer API untuk Pengikisan Web Otomatis

Mari kita mulai tutorial Dalang kita dengan contoh dasar. Kami akan menulis skrip yang akan menyebabkan browser tanpa kepala kami mengambil tangkapan layar dari situs web pilihan kami.

Buat file baru di direktori proyek Anda bernama screenshot.js dan buka di editor kode favorit Anda.

Pertama, mari impor pustaka Dalang di skrip Anda:

 const puppeteer = require('puppeteer');

Selanjutnya, mari kita ambil URL dari argumen baris perintah:

 const url = process.argv[2]; if (!url) { throw "Please provide a URL as the first argument"; }

Sekarang, kita perlu mengingat bahwa Puppateer adalah library berbasis janji: Ini melakukan panggilan asinkron ke instance Chrome tanpa kepala di bawah tenda. Mari jaga kebersihan kode dengan menggunakan async/await. Untuk itu, kita perlu mendefinisikan fungsi async terlebih dahulu dan memasukkan semua kode Dalang di sana:

 async function run () { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(url); await page.screenshot({path: 'screenshot.png'}); browser.close(); } run();

Secara keseluruhan, kode terakhir terlihat seperti ini:

 const puppeteer = require('puppeteer'); const url = process.argv[2]; if (!url) { throw "Please provide URL as a first argument"; } async function run () { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(url); await page.screenshot({path: 'screenshot.png'}); browser.close(); } run();

Anda dapat menjalankannya dengan menjalankan perintah berikut di direktori root proyek Anda:

 node screenshot.js https://github.com

Tunggu sebentar, dan boom! Peramban tanpa kepala kami baru saja membuat file bernama screenshot.png dan Anda dapat melihat beranda GitHub dirender di dalamnya. Bagus, kami memiliki pengikis web Chrome yang berfungsi!

Mari kita berhenti sejenak dan menjelajahi apa yang terjadi pada fungsi run() kita di atas.

Pertama, kami meluncurkan instance browser headless baru, lalu kami membuka halaman (tab) baru dan menavigasi ke URL yang disediakan dalam argumen baris perintah. Terakhir, kami menggunakan metode bawaan Dalang untuk mengambil tangkapan layar, dan kami hanya perlu menyediakan jalur tempat penyimpanannya. Kami juga perlu memastikan untuk menutup browser tanpa kepala setelah kami selesai dengan otomatisasi kami.

Sekarang setelah kita membahas dasar-dasarnya, mari beralih ke sesuatu yang sedikit lebih kompleks.

Contoh Scraping Dalang Kedua

Untuk bagian selanjutnya dari tutorial Dalang kita, katakanlah kita ingin mengikis artikel terbaru dari Hacker News.

Buat file baru bernama ycombinator-scraper.js dan tempel di cuplikan kode berikut:

 const puppeteer = require('puppeteer'); function run () { return new Promise(async (resolve, reject) => { try { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto("https://news.ycombinator.com/"); let urls = await page.evaluate(() => { let results = []; let items = document.querySelectorAll('a.storylink'); items.forEach((item) => { results.push({ url: item.getAttribute('href'), text: item.innerText, }); }); return results; }) browser.close(); return resolve(urls); } catch (e) { return reject(e); } }) } run().then(console.log).catch(console.error);

Oke, ada sedikit lagi yang terjadi di sini dibandingkan dengan contoh sebelumnya.

Hal pertama yang mungkin Anda perhatikan adalah bahwa fungsi run() sekarang mengembalikan janji sehingga awalan async telah pindah ke definisi fungsi janji.

Kami juga telah membungkus semua kode kami dalam blok try-catch sehingga kami dapat menangani kesalahan apa pun yang menyebabkan janji kami ditolak.

Dan terakhir, kita menggunakan metode bawaan dalang yang disebut evaluate() . Metode ini memungkinkan kita menjalankan kode JavaScript khusus seolah-olah kita menjalankannya di konsol DevTools. Apa pun yang dikembalikan dari fungsi itu akan diselesaikan dengan janji. Metode ini sangat berguna dalam hal menggores informasi atau melakukan tindakan kustom.

Kode yang diteruskan ke metode evaluate() adalah JavaScript yang cukup mendasar yang membangun larik objek, masing-masing memiliki bidang url dan text yang mewakili URL cerita yang kita lihat di https://news.ycombinator.com/.

Output dari skrip terlihat seperti ini (tetapi dengan 30 entri, awalnya):

 [ { url: 'https://www.nature.com/articles/d41586-018-05469-3', text: 'Bias detectives: the researchers striving to make algorithms fair' }, { url: 'https://mino-games.workable.com/jobs/415887', text: 'Mino Games Is Hiring Programmers in Montreal' }, { url: 'http://srobb.net/pf.html', text: 'A Beginner\'s Guide to Firewalling with pf' }, // ... { url: 'https://tools.ietf.org/html/rfc8439', text: 'ChaCha20 and Poly1305 for IETF Protocols' } ]

Cukup rapi, menurutku!

Oke, mari kita maju. Kami hanya memiliki 30 item yang dikembalikan, sementara masih banyak lagi yang tersedia—hanya ada di halaman lain. Kita perlu mengklik tombol "Lainnya" untuk memuat halaman hasil berikutnya.

Mari ubah skrip kita sedikit untuk menambahkan dukungan untuk pagination:

 const puppeteer = require('puppeteer'); function run (pagesToScrape) { return new Promise(async (resolve, reject) => { try { if (!pagesToScrape) { pagesToScrape = 1; } const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto("https://news.ycombinator.com/"); let currentPage = 1; let urls = []; while (currentPage <= pagesToScrape) { let newUrls = await page.evaluate(() => { let results = []; let items = document.querySelectorAll('a.storylink'); items.forEach((item) => { results.push({ url: item.getAttribute('href'), text: item.innerText, }); }); return results; }); urls = urls.concat(newUrls); if (currentPage < pagesToScrape) { await Promise.all([ await page.click('a.morelink'), await page.waitForSelector('a.storylink') ]) } currentPage++; } browser.close(); return resolve(urls); } catch (e) { return reject(e); } }) } run(5).then(console.log).catch(console.error);

Mari kita tinjau apa yang kita lakukan di sini:

  1. Kami menambahkan satu argumen yang disebut pagesToScrape ke fungsi run() utama kami. Kami akan menggunakan ini untuk membatasi berapa banyak halaman yang akan digores oleh skrip kami.
  2. Ada satu lagi variabel baru bernama currentPage yang mewakili jumlah halaman hasil yang kita lihat saat ini. Ini disetel ke 1 pada awalnya. Kami juga membungkus fungsi evaluate() kami dalam loop while , sehingga tetap berjalan selama currentPage kurang dari atau sama dengan pagesToScrape .
  3. Kami menambahkan blok untuk pindah ke halaman baru dan menunggu halaman dimuat sebelum memulai ulang while loop.

Anda akan melihat bahwa kami menggunakan metode page.click() agar browser tanpa kepala mengklik tombol "Lainnya". Kami juga menggunakan metode waitForSelector() untuk memastikan logika kami dijeda hingga konten halaman dimuat.

Keduanya adalah metode API Dalang tingkat tinggi yang siap digunakan secara langsung.

Salah satu masalah yang mungkin akan Anda temui saat mengikis dengan Dalang adalah menunggu halaman dimuat. Hacker News memiliki struktur yang relatif sederhana dan cukup mudah untuk menunggu penyelesaian pemuatan halamannya. Untuk kasus penggunaan yang lebih kompleks, Puppeteer menawarkan berbagai fungsionalitas bawaan, yang dapat Anda jelajahi dalam dokumentasi API di GitHub.

Ini semua sangat keren, tetapi tutorial Dalang kami belum mencakup pengoptimalan. Mari kita lihat bagaimana kita bisa membuat Dalang berjalan lebih cepat.

Mengoptimalkan Naskah Dalang Kita

Ide umumnya adalah untuk tidak membiarkan browser tanpa kepala melakukan pekerjaan ekstra. Ini mungkin termasuk memuat gambar, menerapkan aturan CSS, mengaktifkan permintaan XHR, dll.

Seperti alat lainnya, pengoptimalan Dalang bergantung pada kasus penggunaan yang tepat, jadi ingatlah bahwa beberapa ide ini mungkin tidak cocok untuk proyek Anda. Misalnya, jika kami menghindari memuat gambar dalam contoh pertama kami, tangkapan layar kami mungkin tidak terlihat seperti yang kami inginkan.

Bagaimanapun, pengoptimalan ini dapat dicapai baik dengan menyimpan aset pada permintaan pertama, atau membatalkan permintaan HTTP langsung seperti yang diprakarsai oleh situs web.

Mari kita lihat cara kerja caching terlebih dahulu.

Anda harus menyadari bahwa ketika Anda meluncurkan instans browser tanpa kepala baru, Dalang membuat direktori sementara untuk profilnya. Itu dihapus saat browser ditutup dan tidak tersedia untuk digunakan saat Anda menjalankan instance baru—sehingga semua gambar, CSS, cookie, dan objek lain yang disimpan tidak akan dapat diakses lagi.

Kita dapat memaksa Dalang untuk menggunakan jalur khusus untuk menyimpan data seperti cookie dan cache, yang akan digunakan kembali setiap kali kita menjalankannya lagi—sampai habis masa berlakunya atau dihapus secara manual.

 const browser = await puppeteer.launch({ userDataDir: './data', });

Ini akan memberi kami peningkatan kinerja yang bagus, karena banyak CSS dan gambar akan di-cache di direktori data pada permintaan pertama, dan Chrome tidak perlu mengunduhnya lagi dan lagi.

Namun, aset tersebut akan tetap digunakan saat merender halaman. Dalam memenuhi kebutuhan artikel berita Y Combinator, kami tidak perlu khawatir tentang visual apa pun, termasuk gambar. Kami hanya peduli dengan keluaran HTML biasa, jadi mari kita coba memblokir setiap permintaan.

Untungnya, Dalang cukup keren untuk digunakan, dalam hal ini, karena dilengkapi dengan dukungan untuk kait khusus. Kami dapat menyediakan pencegat pada setiap permintaan dan membatalkan yang tidak benar-benar kami butuhkan.

Interseptor dapat didefinisikan dengan cara berikut:

 await page.setRequestInterception(true); page.on('request', (request) => { if (request.resourceType() === 'document') { request.continue(); } else { request.abort(); } });

Seperti yang Anda lihat, kami memiliki kontrol penuh atas permintaan yang dimulai. Kami dapat menulis logika khusus untuk mengizinkan atau membatalkan permintaan tertentu berdasarkan resourceType mereka. Kami juga memiliki akses ke banyak data lain seperti request.url sehingga kami hanya dapat memblokir URL tertentu jika kami mau.

Dalam contoh di atas, kami hanya mengizinkan permintaan dengan jenis sumber daya "document" untuk melewati filter kami, artinya kami akan memblokir semua gambar, CSS, dan yang lainnya selain respons HTML asli.

Inilah kode terakhir kami:

 const puppeteer = require('puppeteer'); function run (pagesToScrape) { return new Promise(async (resolve, reject) => { try { if (!pagesToScrape) { pagesToScrape = 1; } const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.setRequestInterception(true); page.on('request', (request) => { if (request.resourceType() === 'document') { request.continue(); } else { request.abort(); } }); await page.goto("https://news.ycombinator.com/"); let currentPage = 1; let urls = []; while (currentPage <= pagesToScrape) { await page.waitForSelector('a.storylink'); let newUrls = await page.evaluate(() => { let results = []; let items = document.querySelectorAll('a.storylink'); items.forEach((item) => { results.push({ url: item.getAttribute('href'), text: item.innerText, }); }); return results; }); urls = urls.concat(newUrls); if (currentPage < pagesToScrape) { await Promise.all([ await page.waitForSelector('a.morelink'), await page.click('a.morelink'), await page.waitForSelector('a.storylink') ]) } currentPage++; } browser.close(); return resolve(urls); } catch (e) { return reject(e); } }) } run(5).then(console.log).catch(console.error);

Tetap Aman dengan Batas Tarif

Browser tanpa kepala adalah alat yang sangat kuat. Mereka dapat melakukan hampir semua jenis tugas otomatisasi web, dan Dalang membuatnya lebih mudah. Terlepas dari semua kemungkinan, kami harus mematuhi persyaratan layanan situs web untuk memastikan kami tidak menyalahgunakan sistem.

Karena aspek ini lebih terkait arsitektur, saya tidak akan membahasnya secara mendalam dalam tutorial Dalang ini. Yang mengatakan, cara paling dasar untuk memperlambat skrip Dalang adalah dengan menambahkan perintah tidur ke dalamnya:

js await page.waitFor(5000);

Pernyataan ini akan memaksa skrip Anda untuk tidur selama lima detik (5000 ms). Anda dapat meletakkan ini di mana saja sebelum browser.close() .

Sama seperti membatasi penggunaan Anda atas layanan pihak ketiga, ada banyak cara lain yang lebih kuat untuk mengontrol penggunaan Anda atas Dalang. Salah satu contohnya adalah membangun sistem antrian dengan jumlah pekerja yang terbatas. Setiap kali Anda ingin menggunakan Dalang, Anda akan memasukkan tugas baru ke dalam antrian, tetapi hanya ada sejumlah pekerja yang dapat mengerjakan tugas di dalamnya. Ini adalah praktik yang cukup umum ketika berhadapan dengan batas tingkat API pihak ketiga dan dapat diterapkan pada pengikisan data web Dalang juga.

Tempat Dalang di Web yang Bergerak Cepat

Dalam tutorial Dalang ini, saya telah mendemonstrasikan fungsionalitas dasarnya sebagai alat pengikis web. Namun, ia memiliki kasus penggunaan yang jauh lebih luas, termasuk pengujian browser tanpa kepala, pembuatan PDF, dan pemantauan kinerja, di antara banyak lainnya.

Teknologi web bergerak maju dengan cepat. Beberapa situs web sangat bergantung pada rendering JavaScript sehingga hampir tidak mungkin untuk mengeksekusi permintaan HTTP sederhana untuk mengikisnya atau melakukan semacam otomatisasi. Untungnya, browser tanpa kepala menjadi semakin mudah diakses untuk menangani semua kebutuhan otomatisasi kami, berkat proyek seperti Puppeteer dan tim hebat di belakangnya!