Başsız Tarayıcı ile Web Kazıma: Bir Kuklacı Eğitimi

Yayınlanan: 2022-03-11

Bu yazıda, biraz geleneksel olmayan bir başsız tarayıcı kullanma yöntemiyle web kazıma (web otomasyonu) gerçekleştirmenin ne kadar kolay olduğunu göreceğiz.

Başsız Tarayıcı Nedir ve Neden Gereklidir?

Son birkaç yılda, web'in yalın HTML ve CSS ile oluşturulmuş basit web sitelerinden geliştiğini gördük. Artık, genellikle Angular veya React gibi çerçevelerle oluşturulmuş güzel kullanıcı arayüzlerine sahip çok daha etkileşimli web uygulamaları var. Başka bir deyişle, günümüzde JavaScript, web sitelerinde etkileşimde bulunduğunuz hemen hemen her şey dahil olmak üzere web'i yönetmektedir.

Amaçlarımız için JavaScript, istemci tarafı bir dildir. Sunucu, bir HTML yanıtına enjekte edilen JavaScript dosyalarını veya komut dosyalarını döndürür ve tarayıcı bunu işler. Şimdi, bir tür web kazıma veya web otomasyonu yapıyorsak bu bir sorundur çünkü çoğu zaman görmek veya kazımak istediğimiz içerik aslında JavaScript kodu tarafından oluşturulur ve ham HTML yanıtından erişilebilir değildir. sunucunun sunduğu.

Yukarıda bahsettiğimiz gibi, tarayıcılar JavaScript'i nasıl işleyeceklerini ve güzel web sayfalarını nasıl oluşturacaklarını biliyorlar. Şimdi, kazıma ihtiyaçlarımız için bu işlevsellikten yararlanabilseydik ve tarayıcıları programlı olarak kontrol etmenin bir yolunu bulsaydık? İşte tam bu noktada başsız tarayıcı otomasyonu devreye giriyor!

Başsız mı? Affedersiniz? Evet, bu sadece grafiksel kullanıcı arabirimi (GUI) olmadığı anlamına gelir. Görsel öğelerle normalde yaptığınız gibi (örneğin bir fare veya dokunmatik cihazla) etkileşim kurmak yerine, bir komut satırı arabirimi (CLI) ile kullanım senaryolarını otomatikleştirirsiniz.

Başsız Krom ve Kuklacı

Zombie.js veya Selenium kullanan başsız Firefox gibi başsız tarama için kullanılabilecek birçok web kazıma aracı vardır. Ancak bugün, 2018'in başında piyasaya sürülen nispeten daha yeni bir oyuncu olduğu için başsız Chrome'u Puppeteer aracılığıyla keşfedeceğiz. Editörün notu: Başka bir yeni oyuncu olan Intoli'nin Remote Browser'ından bahsetmeye değer, ancak bu başka bir konunun konusu olmak zorunda. makale.

Kuklacı tam olarak nedir? Başsız Chrome veya Chromium'u kontrol etmek veya DevTools protokolü ile etkileşim kurmak için üst düzey bir API sağlayan bir Node.js kitaplığıdır. Chrome DevTools ekibi ve harika bir açık kaynak topluluğu tarafından sürdürülür.

Yeterince konuşma - hadi koda geçelim ve Puppeteer'ın başsız taramasını kullanarak web kazıma işlemini nasıl otomatikleştireceğimizin dünyasını keşfedelim!

Ortamın Hazırlanması

Her şeyden önce, makinenizde Node.js 8+ kurulu olması gerekir. Buradan kurabilirsiniz veya benim gibi CLI aşığıysanız ve Ubuntu üzerinde çalışmayı seviyorsanız şu komutları takip edin:

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

Ayrıca sisteminizde bulunabilecek veya bulunmayabilecek bazı paketlere de ihtiyacınız olacak. Güvende olmak için şunları yüklemeyi deneyin:

 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

Headless Chrome ve Puppeteer Kurulumu

Puppeteer'ı npm ile yüklemenizi tavsiye ederim, çünkü kitaplıkla çalışması garanti edilen kararlı, güncel Chromium sürümünü de içerir.

Bu komutu proje kök dizininizde çalıştırın:

 npm i puppeteer --save

Not: Kuklacının arka planda Chromium'u indirip yüklemesi gerekeceğinden bu işlem biraz zaman alabilir.

Tamam, şimdi hepimiz hazır ve yapılandırıldık, eğlence başlasın!

Otomatik Web Kazıma için Puppeteer API'sini Kullanma

Kuklacı eğitimimize basit bir örnekle başlayalım. Başsız tarayıcımızın seçtiğimiz bir web sitesinin ekran görüntüsünü almasına neden olacak bir komut dosyası yazacağız.

Proje dizininizde screenshot.js adlı yeni bir dosya oluşturun ve onu favori kod düzenleyicinizde açın.

İlk önce, komut dosyanızdaki Kuklacı kitaplığını içe aktaralım:

 const puppeteer = require('puppeteer');

Sırada, komut satırı argümanlarından URL'yi alalım:

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

Şimdi, Puppeteer'ın söze dayalı bir kitaplık olduğunu unutmamalıyız: Başlık altındaki başsız Chrome örneğine eşzamansız çağrılar yapar. async/await kullanarak kodu temiz tutalım. Bunun için önce bir async işlevi tanımlamamız ve tüm Puppeteer kodunu buraya koymamız gerekiyor:

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

Toplamda, son kod şöyle görünür:

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

Projenizin kök dizininde aşağıdaki komutu yürüterek çalıştırabilirsiniz:

 node screenshot.js https://github.com

Bir saniye bekleyin ve bum! Başsız tarayıcımız, screenshot.png adlı bir dosya oluşturdu ve içinde oluşturulan GitHub ana sayfasını görebilirsiniz. Harika, çalışan bir Chrome web kazıyıcımız var!

Bir dakikalığına duralım ve yukarıdaki run() fonksiyonumuzda neler olduğunu keşfedelim.

İlk önce yeni bir başsız tarayıcı örneği başlatıyoruz, ardından yeni bir sayfa (sekme) açıyoruz ve komut satırı argümanında sağlanan URL'ye gidiyoruz. Son olarak, ekran görüntüsü almak için Puppeteer'ın yerleşik yöntemini kullanıyoruz ve yalnızca kaydedilmesi gereken yolu sağlamamız gerekiyor. Otomasyonumuzu bitirdikten sonra başsız tarayıcıyı da kapattığımızdan emin olmamız gerekiyor.

Şimdi temelleri ele aldığımıza göre, biraz daha karmaşık bir şeye geçelim.

İkinci Bir Kuklacı Kazıma Örneği

Kuklacı eğitimimizin bir sonraki bölümünde, diyelim ki Hacker News'deki en yeni makaleleri sıyırmak istiyoruz.

ycombinator-scraper.js adlı yeni bir dosya oluşturun ve aşağıdaki kod parçacığını yapıştırın:

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

Tamam, önceki örneğe kıyasla burada biraz daha fazla şey oluyor.

Fark edebileceğiniz ilk şey, run() işlevinin artık bir söz döndürmesidir, bu nedenle zaman async önek, söz işlevinin tanımına taşınır.

Ayrıca, sözümüzün reddedilmesine neden olan hataların üstesinden gelebilmemiz için tüm kodumuzu bir try-catch bloğuna sardık.

Ve son olarak, Puppeteer'in evaluate() adlı yerleşik yöntemini kullanıyoruz. Bu yöntem, özel JavaScript kodunu DevTools konsolunda yürütüyormuşuz gibi çalıştırmamızı sağlar. Bu işlevden döndürülen her şey söz tarafından çözülür. Bu yöntem, bilgileri kazımak veya özel eylemler gerçekleştirmek söz konusu olduğunda çok kullanışlıdır.

evaluate() yöntemine iletilen kod, her biri https://news.ycombinator.com/ adresinde gördüğümüz hikaye URL'lerini temsil eden url ve text alanlarına sahip bir dizi nesne oluşturan oldukça basit JavaScript'tir.

Komut dosyasının çıktısı şuna benzer (ancak orijinal olarak 30 girişle):

 [ { 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' } ]

Oldukça temiz, söyleyebilirim!

Tamam, ilerleyelim. Geri dönen yalnızca 30 öğemiz vardı, ancak daha birçok ürün var - bunlar yalnızca diğer sayfalarda. Bir sonraki sonuç sayfasını yüklemek için “Daha Fazla” düğmesine tıklamamız gerekiyor.

Sayfalandırma için bir destek eklemek için komut dosyamızı biraz değiştirelim:

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

Burada yaptıklarımızı gözden geçirelim:

  1. Ana run() fonksiyonumuza pagesToScrape adında tek bir argüman ekledik. Bunu, betiğimizin kaç sayfa kazıyacağını sınırlamak için kullanacağız.
  2. Şu anda baktığımız sonuç sayfasının sayısını temsil eden currentPage adında bir yeni değişken daha var. Başlangıçta 1 olarak ayarlanmıştır. Ayrıca, currentPage değerinden küçük veya buna eşit olduğu sürece, evaluate() işlevimizi bir while döngüsüne pagesToScrape .
  3. Yeni bir sayfaya geçmek ve while döngüsünü yeniden başlatmadan önce sayfanın yüklenmesini beklemek için blok ekledik.

Başsız tarayıcının “Diğer” düğmesine tıklamasını sağlamak için page.click() yöntemini kullandığımızı fark edeceksiniz. Sayfa içerikleri yüklenene kadar mantığımızın duraklatıldığından emin olmak için waitForSelector() yöntemini de kullandık.

Her ikisi de kullanıma hazır, üst düzey Kuklacı API yöntemleridir.

Puppeteer ile kazıma yaparken muhtemelen karşılaşacağınız sorunlardan biri, bir sayfanın yüklenmesini beklemektir. Hacker News nispeten basit bir yapıya sahip ve sayfa yüklemesinin tamamlanmasını beklemek oldukça kolaydı. Daha karmaşık kullanım durumları için Puppeteer, GitHub'daki API belgelerinde keşfedebileceğiniz çok çeşitli yerleşik işlevler sunar.

Bunların hepsi oldukça güzel, ancak Kuklacı öğreticimiz henüz optimizasyonu kapsamadı. Bakalım Puppeteer'ı nasıl daha hızlı çalıştırabiliriz.

Kuklacı Komut Dosyamızı Optimize Etme

Genel fikir, başsız tarayıcının fazladan iş yapmasına izin vermemektir. Bu, resimlerin yüklenmesini, CSS kurallarının uygulanmasını, XHR isteklerinin tetiklenmesini vb. içerebilir.

Diğer araçlarda olduğu gibi, Puppeteer'ın optimizasyonu tam kullanım durumuna bağlıdır, bu nedenle bu fikirlerden bazılarının projeniz için uygun olmayabileceğini unutmayın. Örneğin, ilk örneğimizde resim yüklemekten kaçınmış olsaydık, ekran görüntüsü istediğimiz gibi görünmeyebilirdi.

Her halükarda, bu optimizasyonlar, varlıkları ilk istekte önbelleğe alarak veya web sitesi tarafından başlatıldıkları anda HTTP isteklerini doğrudan iptal ederek gerçekleştirilebilir.

Önce önbelleğe almanın nasıl çalıştığını görelim.

Yeni bir başsız tarayıcı örneği başlattığınızda, Puppeteer'ın profili için geçici bir dizin oluşturduğunu bilmelisiniz. Tarayıcı kapatıldığında kaldırılır ve yeni bir örneği başlattığınızda kullanılamaz; bu nedenle saklanan tüm resimler, CSS, tanımlama bilgileri ve diğer nesnelere artık erişilemez.

Puppeteer'ı, çerezler ve önbellek gibi verileri depolamak için özel bir yol kullanmaya zorlayabiliriz; bu, onu her çalıştırışımızda yeniden kullanılacak, süreleri dolana veya manuel olarak silinene kadar.

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

İlk istek üzerine çok sayıda CSS ve resim veri dizininde önbelleğe alınacağından ve Chrome'un bunları tekrar tekrar indirmesine gerek olmayacağından, bu bize performansta güzel bir artış sağlayacaktır.

Ancak, bu varlıklar sayfa oluşturulurken kullanılmaya devam edecektir. Y Combinator haber makalelerinin kazıma ihtiyaçlarımızda, görseller de dahil olmak üzere herhangi bir görsel için endişelenmemize gerek yok. Yalnızca çıplak HTML çıktısını önemsiyoruz, bu yüzden her isteği engellemeye çalışalım.

Neyse ki, bu durumda Puppeteer ile çalışmak oldukça havalı çünkü özel kancalar için destekle geliyor. Her istekte bir önleyici sağlayabilir ve gerçekten ihtiyacımız olmayanları iptal edebiliriz.

Engelleyici aşağıdaki şekilde tanımlanabilir:

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

Gördüğünüz gibi, başlatılan istekler üzerinde tam kontrole sahibiz. resourceType göre belirli isteklere izin vermek veya bunları iptal etmek için özel mantık yazabiliriz. Ayrıca request.url gibi birçok başka veriye de erişimimiz var, böylece istersek yalnızca belirli URL'leri engelleyebiliriz.

Yukarıdaki örnekte, yalnızca "document" kaynak türündeki isteklerin filtremizden geçmesine izin veriyoruz, yani tüm resimleri, CSS'yi ve orijinal HTML yanıtının yanı sıra diğer her şeyi engelleyeceğiz.

İşte son kodumuz:

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

Oran Limitleriyle Güvende Kalın

Başsız tarayıcılar çok güçlü araçlardır. Neredeyse her türlü web otomasyon görevini gerçekleştirebilirler ve Puppeteer bunu daha da kolaylaştırır. Tüm olasılıklara rağmen, sistemi kötüye kullanmadığımızdan emin olmak için bir web sitesinin hizmet şartlarına uymalıyız.

Bu yön daha çok mimariyle ilgili olduğundan, bu Kuklacı eğitiminde bunu derinlemesine ele almayacağım. Bununla birlikte, bir Kuklacı betiğini yavaşlatmanın en temel yolu, ona bir uyku komutu eklemektir:

js await page.waitFor(5000);

Bu ifade, betiğinizi beş saniye (5000 ms) uyumaya zorlar. Bunu browser.close() önce herhangi bir yere koyabilirsiniz.

Üçüncü taraf hizmetleri kullanımınızı sınırlamak gibi, Puppeteer kullanımınızı kontrol etmenin daha birçok sağlam yolu vardır. Bir örnek, sınırlı sayıda işçi ile bir kuyruk sistemi oluşturmak olabilir. Kuklacı'yı her kullanmak istediğinizde, kuyruğa yeni bir görev gönderirsiniz, ancak içindeki görevler üzerinde çalışabilecek yalnızca sınırlı sayıda işçi olurdu. Bu, üçüncü taraf API hız limitleriyle uğraşırken oldukça yaygın bir uygulamadır ve Puppeteer web veri kazıma işlemine de uygulanabilir.

Hızlı hareket eden Web'de Kuklacının Yeri

Bu Kuklacı eğitiminde, bir web kazıma aracı olarak temel işlevselliğini gösterdim. Bununla birlikte, diğerleri arasında, başsız tarayıcı testi, PDF oluşturma ve performans izleme dahil olmak üzere çok daha geniş kullanım örneklerine sahiptir.

Web teknolojileri hızla ilerliyor. Bazı web siteleri JavaScript oluşturmaya o kadar bağımlıdır ki, onları kazımak veya bir tür otomasyon gerçekleştirmek için basit HTTP isteklerini yürütmek neredeyse imkansız hale gelir. Neyse ki, başsız tarayıcılar, Puppeteer gibi projeler ve onların arkasındaki harika ekipler sayesinde tüm otomasyon ihtiyaçlarımızı karşılamak için giderek daha erişilebilir hale geliyor!