· Admin

Web Crawler ve Scraping: Puppeteer ile Veri Toplama

Web scraping, dogru kullanildiginda inanilmaz guclu bir beceri. Projelerimde birkac kez ihtiyac duydum: bir API olmayan siteden veri cekmem, fiyat karsilastirmasi yapmam, belirli verileri duzenli olarak toplamam gerekti. Bu yazida Puppeteer ile web scraping deneyimlerimi, yasadigim sorunlari ve cozumlerimi paylasiyorum.

Web Scraping Nedir ve Ne Zaman Kullanilir?

Web scraping, web sayfalarindan programatik olarak veri cikartma islemidir. Temel kullanim alanlari sunlardir:

  • Fiyat karsilastirma ve takip
  • Arastirma verileri toplama
  • Rakip analizi
  • API sunmayan servislerden veri cekme
  • Icerik agregasyonu

Ancak onemli bir kural var: eger bir servis API sunuyorsa, scraping yerine API'yi kullanin. Scraping her zaman son care olmali.

Yasal Boyut ve Etik Kurallar

Scraping konusunda dikkat edilmesi gereken onemli noktalar var:

robots.txt: Her sitenin kokunde bulunan bu dosya, hangi sayfalarin taranmasina izin verildigini belirtir. Her zaman kontrol edin ve uyun:

# ornek robots.txt
User-agent: *
Disallow: /admin/
Disallow: /private/
Allow: /public/
Crawl-delay: 10

Rate limiting: Sunucuyu yormamak icin istekler arasinda bekleme suresi koyun. Bir siteye saniyede 100 istek atarsaniz hem etik degil hem de IP'niz banlanir.

Kisisel veri: KVKK ve GDPR kapsaminda kisisel verileri toplamak yasal sorunlara yol acabilir. Sadece kamuya acik, kisisel olmayan verileri toplayin.

Hizmet sartlari: Bazi sitelerin kullanim sartlari scraping'i acikca yasaklar. Bu sartlari ihlal etmek hukuki sorunlara neden olabilir.

Puppeteer Kurulumu ve Temel Kullanim

Puppeteer, Google Chrome'un headless versiyonunu kontrol etmenizi saglayan bir Node.js kutuphanesidir:

npm init -y
npm install puppeteer

Temel bir scraping ornegi:

const puppeteer = require('puppeteer');

async function scrape() {
  const browser = await puppeteer.launch({
    headless: 'new',
    args: ['--no-sandbox', '--disable-setuid-sandbox']
  });

  const page = await browser.newPage();

  // User-Agent ayarla (bot olarak algilanmamayi onler)
  await page.setUserAgent(
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ' +
    'AppleWebKit/537.36 (KHTML, like Gecko) ' +
    'Chrome/120.0.0.0 Safari/537.36'
  );

  await page.goto('https://example.com/products', {
    waitUntil: 'networkidle2',
    timeout: 30000
  });

  // Sayfa tamamen yuklendikten sonra veri cek
  const products = await page.evaluate(() => {
    const items = document.querySelectorAll('.product-card');
    return Array.from(items).map(item => ({
      name: item.querySelector('.product-name')?.textContent?.trim(),
      price: item.querySelector('.product-price')?.textContent?.trim(),
      url: item.querySelector('a')?.href
    }));
  });

  console.log(`${products.length} urun bulundu`);
  console.log(products);

  await browser.close();
}

scrape();

Sayfa Navigasyonu ve Selector Kullanimi

Gercek dunyada sayfalar genellikle daha karmasiktir. Sayfalama, lazy loading, dinamik icerik gibi durumlarla karsilasiriz:

// Sayfalama ile tum urunleri cekme
async function scrapeAllPages(page) {
  let allProducts = [];
  let currentPage = 1;

  while (true) {
    // Mevcut sayfadaki urunleri cek
    const products = await page.evaluate(() => {
      return Array.from(document.querySelectorAll('.product-card')).map(el => ({
        name: el.querySelector('h3')?.textContent?.trim(),
        price: el.querySelector('.price')?.textContent?.trim()
      }));
    });

    allProducts = allProducts.concat(products);
    console.log(`Sayfa ${currentPage}: ${products.length} urun`);

    // Sonraki sayfa butonu var mi?
    const nextButton = await page.$('.pagination .next:not(.disabled)');
    if (!nextButton) break;

    await nextButton.click();
    await page.waitForNavigation({ waitUntil: 'networkidle2' });
    currentPage++;

    // Rate limiting: sayfalar arasi bekleme
    await new Promise(r => setTimeout(r, 2000));
  }

  return allProducts;
}

Selector secerken CSS selector'larin yaninda XPath de kullanabilirsiniz. Ancak CSS selector genellikle yeterli ve daha okunaklidir.

Network Intercept ile API Response Yakalama

Bu teknik benim en cok kullandigim yontemlerden biri. Bircok modern site, sayfa yuklenirken arka planda kendi API'sine istek atar. Bu API response'larini yakalayarak, HTML parse etmekten cok daha temiz veri elde edebilirsiniz:

async function interceptApiCalls(page, targetUrl) {
  return new Promise((resolve, reject) => {
    const timeout = setTimeout(() => reject(new Error('Timeout')), 30000);

    page.on('response', async (response) => {
      const url = response.url();

      if (url.includes(targetUrl)) {
        clearTimeout(timeout);
        try {
          const data = await response.json();
          resolve(data);
        } catch (e) {
          reject(e);
        }
      }
    });
  });
}

// Kullanim
const page = await browser.newPage();

// Dinlemeye basla
const dataPromise = interceptApiCalls(page, '/api/v2/searchByCategories');

// Sayfaya git (bu, frontend'in API cagrisi yapmasini tetikler)
await page.goto('https://example.com/products');

// API response'unu yakala
const apiData = await dataPromise;
console.log('API verileri:', apiData);

Bu yontemle HTML parse etmek yerine yapilandirilmis JSON verisi elde edersiniz. Cok daha guvenilir ve hizlidir.

Headless vs Headed Mod

Headless mod tarayiciyi gorsek arayuz olmadan calistirir. Headed mod ise normal bir tarayici penceresi acar:

// Headless (sunucu icin, daha hizli)
const browser = await puppeteer.launch({ headless: 'new' });

// Headed (debug icin, ne oldugunu gorebilirsiniz)
const browser = await puppeteer.launch({
  headless: false,
  slowMo: 100  // Her islemi 100ms yavaslatir, izlemeyi kolaylastirir
});

Gelistirme asamasinda headed mod ile baslayin. Selector'larinizin dogru calistigini gorerek dogrulayin. Her sey yolunda olunca headless'a gecin.

Proxy Rotation ile IP Engellerini Asma

Yogun scraping yaptiginizda IP'nizin engellenmesi kacinilmazdir. Proxy rotation bu sorunu cozer:

const proxies = [
  'http://user:pass@proxy1:8080',
  'http://user:pass@proxy2:8080',
  'http://user:pass@proxy3:8080',
];

function getRandomProxy() {
  return proxies[Math.floor(Math.random() * proxies.length)];
}

async function scrapeWithProxy() {
  const proxy = getRandomProxy();

  const browser = await puppeteer.launch({
    headless: 'new',
    args: [`--proxy-server=${proxy}`]
  });

  const page = await browser.newPage();

  // Proxy authentication
  await page.authenticate({
    username: 'user',
    password: 'pass'
  });

  // Artik farkli IP'den erisiyorsunuz
  await page.goto('https://target-site.com');

  // ...
  await browser.close();
}

Proxy konusunda dikkat edilmesi gereken bir sey var: datacenter proxy'leri ucuzdur ama kolay tespit edilir. Residential proxy'ler daha pahalidir ama gercek kullanici gibi gorunur.

Anti-Bot Korumalari ve Cozumleri

Modern siteler bot tespiti icin cesitli yontemler kullanir:

Cloudflare korumasi en yayginlarindan biri. Bunu asmak icin puppeteer-extra ve stealth plugin'ini kullanabilirsiniz:

const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());

const browser = await puppeteer.launch({ headless: 'new' });

Fingerprint tespiti tarayicinin ozelliklerini analiz eder. Canvas, WebGL, font listesi gibi verilerle gercek kullanici mi bot mu ayirt edilir. Stealth plugin bu ozellikleri de manipule eder.

Rate limiting icin cozum basit: istekler arasina rastgele bekleme suresi koyun:

function randomDelay(min = 1000, max = 3000) {
  const delay = Math.floor(Math.random() * (max - min + 1)) + min;
  return new Promise(resolve => setTimeout(resolve, delay));
}

Puppeteer vs Playwright vs Cheerio

Puppeteer sadece Chrome/Chromium destekler. Google tarafindan gelistirilir. Olgun ve stabil.

Playwright Chrome, Firefox ve Safari destekler. Microsoft tarafindan gelistirilir. Auto-wait gibi ozellikleri ile daha modern. Birden fazla tarayicide test gerekiyorsa iyi secim.

Cheerio tarayici calistirmaz, sadece HTML parse eder. Cok hizli ve hafiftir ama JavaScript ile render edilen sayfalarda ise yaramaz. Statik HTML icin idealdir:

const cheerio = require('cheerio');
const axios = require('axios');

const { data } = await axios.get('https://example.com');
const $ = cheerio.load(data);

$('.product').each((i, el) => {
  console.log($(el).find('h3').text());
});

Benim tercihim: JavaScript ile render edilen siteler icin Puppeteer, statik siteler icin Cheerio.

TUBITAK Market Fiyati Verilerini Puppeteer ile Cekme Deneyimim

SmartSepet projesinde TUBITAK'in marketfiyati.org.tr API'sini kullanmam gerekti. Site bir frontend uzerinden kendi API'sine istek atiyor. Dogrudan API'ye curl ile erismeyi denedigimde calisiyordu ama belirli header'lar gerekiyordu ve ileride bot korumasinin artma ihtimali vardi.

Puppeteer ile network intercept yaklasimini kullandim. Sayfa aciliyor, frontend API'ye istek atiyor, ben de o response'u yakaliyorum. Hem direkt erisim hem de proxy uzerinden test ettim, iki turlu de basarili oldu. Tek bir calisma ile 188 urun ve 6 farkli marketten veri cekmistim.

Veri cache stratejim su sekilde calisiyor: gunde bir veya iki kez cron job ile verileri cekip kendi veritabanimda cache'liyorum. Kullanici istegi geldiginde once kendi DB'me bakiyorum. Eger veri eski veya yoksa API'ye gidiyorum. Bu hem API'yi yormamami hem de hizli cevap vermemi sagliyor.

Sonuc

Web scraping guclu bir arac ama sorumluluk gerektiriyor. robots.txt'e uymak, rate limiting uygulamak ve kisisel veri toplamamak temel kurallardirr. Puppeteer ile JavaScript render'li sitelerde bile rahatlikla veri cekebilirsiniz. Network intercept teknigi ise bircok durumda HTML parse etmekten cok daha temiz sonuclar verir. Her projede once resmi API olup olmadigini kontrol edin ve scraping'i ancak baska yol kalmadiginda kullanin.