← Kursa Dön
📄 Text · 18 min

Web Scraping: BeautifulSoup ve Selenium

İnternetteki bilginin büyük çoğunluğu API olarak sunulmuyor. Haber siteleri, e-ticaret platformları, istatistik sayfaları — hepsi bilgiyi HTML formatında, insanların okuması için sunuyor. Peki bu veriyi programatik olarak çekmek istersen ne yaparsın? İşte tam burada web scraping devreye giriyor.

Bu derste Python'un iki büyük scraping aracını — BeautifulSoup ve Selenium — sıfırdan öğreneceksin. Statik sayfalardan veri çekecek, JavaScript ile render edilen dinamik sayfalarla başa çıkacak ve bunu yaparken etik sınırları bileceksin.


1. Web Scraping Nedir?

🕷️ Analoji: Kütüphanedeki Stajyer

Bir kütüphanede çalışan stajyer düşün. Müdür ona diyor ki: "Git tüm kitapların adını, yazarını ve raf numarasını bir Excel tablosuna yaz." Stajyer her rafa gidiyor, kitap bilgilerini okuyor ve tabloya yazıyor. Yorucu ama sistematik bir iş.

Web scraping tam olarak bu. Bir program, web sayfalarını ziyaret eder, HTML yapısını okur ve istediğin bilgiyi çıkarıp yapılandırılmış hale getirir. Stajyer yerine bir Python scripti çalışıyor — hem çok daha hızlı, hem 7/24 çalışabilir.

Ne Zaman Kullanılır?

Scraping'e başvurmanın meşru nedenleri var:

  • API yok: Site veriyi sadece HTML olarak sunuyor, bir REST API sağlamıyor

  • Veri toplama: Fiyat karşılaştırma, haber takibi, akademik araştırma

  • Otomasyon: Belirli bir sayfadaki değişiklikleri takip etme

  • Veri göçü: Eski bir sistemdeki veriyi yeni sisteme aktarma

Ama her zaman önce API var mı diye kontrol et. API varsa scraping yapma — API hem daha güvenilir hem daha hızlıdır.

Etik Kurallar ve robots.txt

Scraping güçlü bir araç ama sorumluluk gerektirir. Birkaç temel kural:

robots.txt: Her ciddi site kök dizininde bir robots.txt dosyası barındırır. Bu dosya hangi sayfaların taranabileceğini, hangilerinin yasaklandığını belirtir.

# https://example.com/robots.txt
User-agent: *
Disallow: /admin/
Disallow: /private/
Crawl-delay: 10

Bu dosya "lütfen /admin/ ve /private/ dizinlerini taramayın, istekler arasında 10 saniye bekleyin" diyor. Yasal olarak bağlayıcılığı tartışmalı olsa da, etik bir scraper robots.txt'e uyar.

Rate limiting: Sunucuyu boğma. İstekler arasında 1-2 saniye bekle. Bir siteye saniyede yüzlerce istek atarsan, DoS saldırısı yapmış olursun — hem etik dışı hem de IP'nin banlanır.

Kullanım şartları: Sitenin Terms of Service (ToS) sayfasını kontrol et. Bazı siteler scraping'i açıkça yasaklar. Bu yasaklara uymamak hukuki sorunlara yol açabilir.


2. Kurulum

Bu derste birkaç kütüphaneye ihtiyacın olacak:

# Temel scraping
pip install requests beautifulsoup4

# Tablo verisi için
pip install pandas lxml

# Dinamik sayfalar için
pip install selenium

beautifulsoup4 paketi kurulur ama import ederken bs4 olarak kullanılır. Bu küçük detay birçok kişiyi şaşırtır.


3. requests + BeautifulSoup ile Statik Scraping

Statik sayfalar, sunucu tarafında tamamen render edilmiş HTML döndüren sayfalardır. JavaScript çalışmasına gerek yoktur — HTML'i indirdiğinde tüm veri zaten oradadır.

İlk Scraping: Basit Bir Sayfa

import requests
from bs4 import BeautifulSoup

# Sayfayı indir
url = "https://quotes.toscrape.com/"
response = requests.get(url)

# HTTP hatasını kontrol et
response.raise_for_status()

# HTML'i parse et
soup = BeautifulSoup(response.text, "html.parser")

# Sayfa başlığını al
print(f"Sayfa: {soup.title.string}")

# Tüm alıntıları bul
quotes = soup.find_all("div", class_="quote")

for quote in quotes[:5]:
    text = quote.find("span", class_="text").get_text()
    author = quote.find("small", class_="author").get_text()
    print(f"  {author}: {text[:60]}...")

İş akışı her zaman aynıdır: İndir → Parse et → Ara → Çıkar. requests indirme işini, BeautifulSoup ise parse ve arama işini yapar.

HTML Parser Seçimi

BeautifulSoup birden fazla parser destekler:

ParserKurulumHızTolerans
html.parserYerleşikOrtaİyi
lxmlpip install lxmlHızlıÇok iyi
html5libpip install html5libYavaşMükemmel

Çoğu durumda html.parser yeterli. Hız önemliyse veya bozuk HTML ile uğraşıyorsan lxml kullan. html5lib en yavaş ama tarayıcının parse edeceği gibi parse eder — son çare olarak düşün.


4. HTML Parse: find(), find_all() ve CSS Selectors

BeautifulSoup'un gücü arama yeteneklerinde yatıyor. HTML ağacında istediğin elemana ulaşmanın birden fazla yolu var.

find() ve find_all()

find() ilk eşleşeni, find_all() tüm eşleşenleri döndürür:

from bs4 import BeautifulSoup

html = """
<html>
<body>
    <h1>Başlık</h1>
    <div class="container">
        <p class="intro">İlk paragraf</p>
        <p class="content">İkinci paragraf</p>
        <p class="content" id="special">Üçüncü paragraf</p>
    </div>
    <a href="https://example.com">Link 1</a>
    <a href="https://python.org" class="external">Link 2</a>
</body>
</html>
"""

soup = BeautifulSoup(html, "html.parser")

# Tag adıyla arama
h1 = soup.find("h1")
print(h1.string)  # "Başlık"

# Class ile arama
intro = soup.find("p", class_="intro")
print(intro.get_text())  # "İlk paragraf"

# Birden fazla sonuç
all_content = soup.find_all("p", class_="content")
print(len(all_content))  # 2

# ID ile arama
special = soup.find("p", id="special")
print(special.string)  # "Üçüncü paragraf"

# Attribute ile arama
external_links = soup.find_all("a", attrs={"class": "external"})
print(external_links[0]["href"])  # "https://python.org"

class_ parametresindeki alt çizgiye dikkat — class Python'da ayrılmış bir kelime olduğu için BeautifulSoup class_ kullanır.

CSS Selectors ile Arama

CSS selector'lara aşinaysan select() metodu çok daha doğal gelecektir:

from bs4 import BeautifulSoup

html = """
<div class="products">
    <div class="product" data-price="99">
        <h3>Laptop</h3>
        <span class="price">99 TL</span>
    </div>
    <div class="product" data-price="49">
        <h3>Mouse</h3>
        <span class="price">49 TL</span>
    </div>
    <div class="product featured" data-price="199">
        <h3>Monitor</h3>
        <span class="price">199 TL</span>
    </div>
</div>
"""

soup = BeautifulSoup(html, "html.parser")

# Tag seçimi
titles = soup.select("h3")  # Tüm h3'ler

# Class seçimi
prices = soup.select(".price")  # class="price" olanlar

# İç içe seçim (descendant)
product_titles = soup.select(".products .product h3")

# Direkt çocuk (child)
direct_divs = soup.select(".products > div")

# Attribute seçimi
featured = soup.select(".product.featured")
print(featured[0].select_one("h3").string)  # "Monitor"

# data-* attribute
expensive = soup.select('[data-price="199"]')
print(expensive[0].select_one("h3").string)  # "Monitor"

select() tüm eşleşenleri liste olarak döndürür, select_one() ilk eşleşeni döndürür. CSS selector söz dizimi web geliştirme deneyimin varsa çok tanıdık gelecektir.

Ağaçta Gezinme (Navigating the Tree)

Bazen bir elemandan başlayıp komşu veya üst/alt elemanlara gitmen gerekir:

from bs4 import BeautifulSoup

html = """
<table>
    <tr>
        <td>Ali</td>
        <td>90</td>
    </tr>
    <tr>
        <td>Ayşe</td>
        <td>95</td>
    </tr>
</table>
"""

soup = BeautifulSoup(html, "html.parser")
first_td = soup.find("td")

# Kardeşe git
print(first_td.find_next_sibling("td").string)  # "90"

# Üst elemana git
row = first_td.parent  # <tr> elementi
print(row.name)  # "tr"

# Tüm çocukları gez
for child in row.children:
    if child.name == "td":
        print(child.string)

# Sonraki elemana atla
next_row = row.find_next_sibling("tr")
print(next_row.find("td").string)  # "Ayşe"

Bu navigasyon yöntemleri, karmaşık HTML yapılarında hedef elemana dolaylı yoldan ulaşman gerektiğinde kurtarıcıdır.


5. Tablo Verisi Çekme → pandas DataFrame

Scraping'in en yaygın kullanım alanlarından biri web sayfalarındaki tabloları veri analizi için çekmektir. Güzel haber: pandas bu işi neredeyse tek satırda yapabilir.

pandas.read_html() ile Hızlı Yol

import pandas as pd

# Wikipedia'dan tablo çekme
url = "https://en.wikipedia.org/wiki/List_of_countries_by_population_(United_Nations)"

# Sayfadaki TÜM tabloları çek
tables = pd.read_html(url)
print(f"Sayfada {len(tables)} tablo bulundu")

# İlk tabloyu incele
df = tables[0]
print(df.head())
print(f"Sütunlar: {list(df.columns)}")

pd.read_html() sayfadaki tüm <table> elementlerini bulur ve her birini DataFrame'e çevirir. Çoğu zaman ihtiyacın olan tablo ilk birkaç sonuçtan biridir.

BeautifulSoup + pandas: Tam Kontrol

Bazen read_html() yeterli olmaz — tablo yapısı karmaşıktır veya özel bir parse mantığı gerekir:

import requests
from bs4 import BeautifulSoup
import pandas as pd

url = "https://quotes.toscrape.com/tableful/"
response = requests.get(url)
soup = BeautifulSoup(response.text, "html.parser")

# Tabloyu bul
table = soup.find("table")

# Başlıkları çek
headers = []
header_row = table.find("thead")
if header_row:
    headers = [th.get_text(strip=True) for th in header_row.find_all("th")]

# Satırları çek
rows = []
for tr in table.find("tbody").find_all("tr"):
    cells = [td.get_text(strip=True) for td in tr.find_all("td")]
    rows.append(cells)

# DataFrame oluştur
df = pd.DataFrame(rows, columns=headers if headers else None)
print(df.head())
print(f"\nSatır sayısı: {len(df)}")

Bu yaklaşım daha fazla kod gerektiriyor ama tam kontrol sağlıyor. Hücrelerdeki linkleri, resimleri veya özel attribute'ları da çekebilirsin.

💡 İpucu: get_text(strip=True) whitespace'leri temizler. Tablo hücrelerinde sıkça gereksiz boşluklar ve satır sonları olur — strip=True bunları otomatik temizler.


6. Selenium ile Dinamik Sayfa Scraping

Modern web'in büyük kısmı JavaScript ile render ediliyor. Bir e-ticaret sitesinde ürün listesi sayfa yüklendiğinde gelmiyor — JavaScript çalışıyor, bir API'a istek atıyor ve sonuçları DOM'a ekliyor. requests ile bu sayfanın HTML'ini indirsen, boş bir iskelet görürsün.

Selenium gerçek bir tarayıcıyı programatik olarak kontrol eder. JavaScript çalışır, sayfalar render edilir, tıklama ve form doldurma yapabilirsin.

WebDriver Kurulumu

Selenium bir tarayıcı sürücüsüne (WebDriver) ihtiyaç duyar. Modern Selenium (4.6+) sürücüyü otomatik indirir:

pip install selenium

Selenium 4.6 ve üzeri sürümlerde webdriver-manager gibi harici paketlere gerek yok. Selenium'un kendi SeleniumManager'ı sürücüyü otomatik bulur ve indirir.

Temel Kullanım

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options

# Chrome ayarları
options = Options()
options.add_argument("--headless=new")  # Tarayıcı penceresi açma

# Tarayıcıyı başlat
driver = webdriver.Chrome(options=options)

try:
    # Sayfaya git
    driver.get("https://quotes.toscrape.com/js/")
    
    # Sayfa başlığı
    print(f"Başlık: {driver.title}")
    
    # Elementleri bul
    quotes = driver.find_elements(By.CSS_SELECTOR, ".quote")
    
    for quote in quotes[:5]:
        text = quote.find_element(By.CSS_SELECTOR, ".text").text
        author = quote.find_element(By.CSS_SELECTOR, ".author").text
        print(f"  {author}: {text[:60]}...")

finally:
    # Tarayıcıyı kapat — mutlaka yap!
    driver.quit()

/js/ endpoint'ine dikkat — bu sayfa JavaScript ile render ediliyor. requests ile çeksen alıntılar gelmez, Selenium ile çeksen gelir.

Element Bulma Yöntemleri

Selenium'da element bulmak için By sınıfı kullanılır:

from selenium.webdriver.common.by import By

# ID ile
element = driver.find_element(By.ID, "search-box")

# Class ile
elements = driver.find_elements(By.CLASS_NAME, "product")

# CSS selector ile
element = driver.find_element(By.CSS_SELECTOR, "div.container > h1")

# XPath ile
element = driver.find_element(By.XPATH, "//div[@class='price']")

# Tag adıyla
links = driver.find_elements(By.TAG_NAME, "a")

# Link metniyle
link = driver.find_element(By.LINK_TEXT, "Next →")

find_element() tekil sonuç döndürür (bulamazsa hata fırlatır), find_elements() liste döndürür (bulamazsa boş liste).

Wait Stratejileri

Dinamik sayfalarda en büyük sorun zamanlama'dır. Sayfa yükleniyor ama JavaScript henüz çalışmadı — element arıyorsun, yok. Selenium bunu çözmek için iki wait mekanizması sunar:

Implicit Wait: Tüm element aramalarına genel bir bekleme süresi ekler:

driver.implicitly_wait(10)  # Her aramada en fazla 10 saniye bekle

Bu yöntem basit ama kaba bir araç — her arama için geçerli olduğu için gereksiz yavaşlamaya neden olabilir.

Explicit Wait (Önerilen): Belirli bir koşulun gerçekleşmesini bekler:

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

# Element görünür olana kadar en fazla 10 saniye bekle
wait = WebDriverWait(driver, 10)

element = wait.until(
    EC.presence_of_element_located((By.CSS_SELECTOR, ".results"))
)

# Element tıklanabilir olana kadar bekle
button = wait.until(
    EC.element_to_be_clickable((By.ID, "load-more"))
)
button.click()

# Belirli bir metin görünene kadar bekle
wait.until(
    EC.text_to_be_present_in_element(
        (By.CSS_SELECTOR, ".status"), "Yüklendi"
    )
)

Explicit wait'ler çok daha hassas kontrol sağlar. "Bu element DOM'da olana kadar bekle", "bu buton tıklanabilir olana kadar bekle" gibi spesifik koşullar tanımlarsın.

⚠️ Dikkat: time.sleep() ile bekleme yapmaktan kaçın. Sabit süre beklemek hem yavaş hem güvenilmezdir — sayfa 0.5 saniyede yüklenebilir ama sen 5 saniye bekliyorsun, ya da 5 saniye yetmeyebilir. WebDriverWait koşul sağlanır sağlanmaz devam eder.


7. Headless Browser Modu

Headless mod, tarayıcıyı görünür bir pencere olmadan çalıştırır. Sunucuda (GUI olmayan ortam) scraping yaparken veya daha hızlı çalışmak istediğinde kullanırsın.

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument("--headless=new")       # Yeni headless modu
options.add_argument("--no-sandbox")         # Docker/CI ortamı için
options.add_argument("--disable-dev-shm-usage")  # Bellek optimizasyonu
options.add_argument("--window-size=1920,1080")  # Viewport boyutu

# User-Agent ayarla (bazı siteler headless tarayıcıları engeller)
options.add_argument(
    "user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
    "AppleWebKit/537.36 (KHTML, like Gecko) "
    "Chrome/120.0.0.0 Safari/537.36"
)

driver = webdriver.Chrome(options=options)

--headless=new Chrome 112+ ile gelen yeni headless modu. Eski --headless parametresine göre gerçek Chrome'a çok daha yakın davranır — bazı anti-bot sistemleri eski headless modu tespit edebiliyordu.


8. Anti-Scraping Önlemleri ve Etik Yaklaşım

Web siteleri scraping'e karşı çeşitli savunma mekanizmaları kullanır. Bunları bilmek hem kendin scraping yaparken hem de kendi siteni korurken işe yarar.

Yaygın Anti-Scraping Teknikleri

IP tabanlı rate limiting: Aynı IP'den çok sayıda istek gelirse engelleyin.

import time
import requests

def polite_scrape(urls, delay=2):
    """Nazik scraper — istekler arasında bekler."""
    results = []
    for url in urls:
        response = requests.get(url, headers={
            "User-Agent": "MyResearchBot/1.0 (contact@example.com)"
        })
        results.append(response)
        time.sleep(delay)  # Sunucuya nefes aldır
    return results

User-Agent kontrolü: Bazı siteler bilinen bot User-Agent'larını engeller. Gerçekçi bir User-Agent kullanmak temel bir önlemdir — ama bunu kötü niyetli olarak değil, sitenin normal kullanıcılara sunduğu deneyimi yaşamak için yaparsın.

CAPTCHA: İnsan doğrulama sistemi. CAPTCHA ile karşılaşırsan, bu site senden scraping yapmamanı istiyor demektir. CAPTCHA'yı otomatik çözmeye çalışmak etik açıdan sorunludur.

Honeypot tuzakları: Normal kullanıcının göremeyeceği (CSS ile gizlenmiş) linkler. Bir bot bu linkleri takip ederse, IP'si tespit edilip engellenir.

Etik Scraping Rehberi

import requests
from urllib.robotparser import RobotFileParser

def check_robots_txt(base_url, path):
    """robots.txt kurallarını kontrol et."""
    rp = RobotFileParser()
    rp.set_url(f"{base_url}/robots.txt")
    rp.read()
    
    full_url = f"{base_url}{path}"
    can_fetch = rp.can_fetch("*", full_url)
    crawl_delay = rp.crawl_delay("*")
    
    print(f"URL: {full_url}")
    print(f"İzin: {'✅ Evet' if can_fetch else '❌ Hayır'}")
    if crawl_delay:
        print(f"Bekleme süresi: {crawl_delay} saniye")
    
    return can_fetch

# Kullanım
allowed = check_robots_txt("https://example.com", "/products")
if allowed:
    print("Scraping yapılabilir")
else:
    print("Bu sayfa scraping'e kapalı")

Python'un standart kütüphanesindeki RobotFileParser, robots.txt dosyasını parse edip belirli bir URL'nin taranıp taranamayacağını söyler. Profesyonel bir scraper her zaman önce bunu kontrol eder.

Session nesnesi (requests.Session()) cookie'leri, header'ları ve TCP bağlantılarını istekler arasında paylaşır — hem performansı artırır hem de sitenin seni normal bir kullanıcı olarak görmesini sağlar. Login gerektiren sitelerde session vazgeçilmezdir.


9. Gerçek Dünya Örneği: Haber Başlıkları Çekme

Tüm öğrendiklerimizi birleştirelim. Aşağıdaki örnek, bir haber sitesinden başlıkları çeker, yapılandırır ve kaydeder. Hem requests + BeautifulSoup hem Selenium versiyonunu görelim.

Versiyon 1: BeautifulSoup ile Statik Sayfa

"""
Haber başlıkları scraper — BeautifulSoup versiyonu.
Quotes to Scrape sitesini haber sitesi yerine kullanıyoruz (güvenli ve legal).
"""

import requests
from bs4 import BeautifulSoup
import csv
import time
from urllib.robotparser import RobotFileParser
from datetime import datetime


def check_permission(base_url):
    """robots.txt kontrolü."""
    rp = RobotFileParser()
    rp.set_url(f"{base_url}/robots.txt")
    try:
        rp.read()
        return rp.can_fetch("*", base_url)
    except Exception:
        print("robots.txt okunamadı — dikkatli devam ediyoruz")
        return True


def scrape_quotes(base_url, max_pages=3, delay=1.5):
    """Birden fazla sayfadan alıntı çeker."""
    
    if not check_permission(base_url):
        print("robots.txt izin vermiyor, çıkılıyor.")
        return []
    
    session = requests.Session()
    session.headers.update({
        "User-Agent": "EducationalBot/1.0 (learning-project)"
    })
    
    all_quotes = []
    
    for page_num in range(1, max_pages + 1):
        url = f"{base_url}/page/{page_num}/"
        print(f"Sayfa {page_num} çekiliyor: {url}")
        
        try:
            response = session.get(url, timeout=10)
            response.raise_for_status()
        except requests.RequestException as e:
            print(f"  Hata: {e}")
            break
        
        soup = BeautifulSoup(response.text, "html.parser")
        quotes = soup.find_all("div", class_="quote")
        
        if not quotes:
            print("  Alıntı bulunamadı — son sayfa.")
            break
        
        for quote in quotes:
            text = quote.find("span", class_="text").get_text()
            author = quote.find("small", class_="author").get_text()
            tags = [tag.get_text() for tag in quote.find_all("a", class_="tag")]
            
            all_quotes.append({
                "text": text,
                "author": author,
                "tags": ", ".join(tags),
                "page": page_num,
                "scraped_at": datetime.now().isoformat()
            })
        
        print(f"  {len(quotes)} alıntı bulundu")
        
        # Sonraki sayfa var mı?
        next_btn = soup.find("li", class_="next")
        if not next_btn:
            print("  Son sayfa — başka sayfa yok.")
            break
        
        time.sleep(delay)  # Rate limiting — nazik ol
    
    return all_quotes


def save_to_csv(quotes, filename="quotes.csv"):
    """Alıntıları CSV dosyasına kaydeder."""
    if not quotes:
        print("Kaydedilecek veri yok.")
        return
    
    fieldnames = ["text", "author", "tags", "page", "scraped_at"]
    
    with open(filename, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(quotes)
    
    print(f"\n{len(quotes)} alıntı '{filename}' dosyasına kaydedildi.")


# Çalıştır
quotes = scrape_quotes("https://quotes.toscrape.com", max_pages=3)
save_to_csv(quotes)

# Özet
if quotes:
    authors = set(q["author"] for q in quotes)
    print(f"\nÖzet: {len(quotes)} alıntı, {len(authors)} farklı yazar")

Bu örnek üretim kalitesinde bir scraper'ın temel bileşenlerini gösteriyor: robots.txt kontrolü, session yönetimi, hata yakalama, rate limiting, sayfalama (pagination) ve veri kaydetme.

Versiyon 2: Selenium ile Dinamik Sayfa

"""
Haber başlıkları scraper — Selenium versiyonu.
JavaScript ile render edilen sayfalar için.
"""

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import json
from datetime import datetime


def create_driver():
    """Headless Chrome driver oluşturur."""
    options = Options()
    options.add_argument("--headless=new")
    options.add_argument("--no-sandbox")
    options.add_argument("--disable-dev-shm-usage")
    options.add_argument("--window-size=1920,1080")
    return webdriver.Chrome(options=options)


def scrape_js_quotes(max_pages=3):
    """JavaScript ile render edilen sayfadan alıntı çeker."""
    
    driver = create_driver()
    wait = WebDriverWait(driver, 10)
    all_quotes = []
    
    try:
        for page_num in range(1, max_pages + 1):
            url = f"https://quotes.toscrape.com/js/page/{page_num}/"
            print(f"Sayfa {page_num} çekiliyor: {url}")
            
            driver.get(url)
            
            # JavaScript render'ı tamamlanana kadar bekle
            wait.until(
                EC.presence_of_all_elements_located(
                    (By.CSS_SELECTOR, ".quote")
                )
            )
            
            quotes = driver.find_elements(By.CSS_SELECTOR, ".quote")
            
            for quote in quotes:
                text = quote.find_element(By.CSS_SELECTOR, ".text").text
                author = quote.find_element(By.CSS_SELECTOR, ".author").text
                tag_elements = quote.find_elements(By.CSS_SELECTOR, ".tag")
                tags = [tag.text for tag in tag_elements]
                
                all_quotes.append({
                    "text": text,
                    "author": author,
                    "tags": tags,
                    "page": page_num,
                    "scraped_at": datetime.now().isoformat()
                })
            
            print(f"  {len(quotes)} alıntı bulundu")
            
            # Sonraki sayfa kontrolü
            next_buttons = driver.find_elements(
                By.CSS_SELECTOR, "li.next > a"
            )
            if not next_buttons:
                print("  Son sayfa.")
                break
    
    finally:
        driver.quit()  # Tarayıcıyı her zaman kapat
    
    return all_quotes


def save_to_json(quotes, filename="quotes_js.json"):
    """Alıntıları JSON dosyasına kaydeder."""
    with open(filename, "w", encoding="utf-8") as f:
        json.dump(quotes, f, ensure_ascii=False, indent=2)
    print(f"\n{len(quotes)} alıntı '{filename}' dosyasına kaydedildi.")


# Çalıştır
quotes = scrape_js_quotes(max_pages=3)
save_to_json(quotes)

İki versiyon arasındaki temel fark şu: BeautifulSoup versiyonu sadece HTTP isteği yapıyor (hızlı, hafif), Selenium versiyonu ise gerçek bir tarayıcı çalıştırıyor (yavaş, ağır ama JavaScript'i çalıştırabilir).

Hangisini Ne Zaman Kullanmalı?

DurumAraçNeden
Statik HTMLrequests + BS4Hızlı, az kaynak
JS-rendered sayfaSeleniumJavaScript çalıştırması gerekli
Login gerekli (basit)requests + SessionCookie yönetimi yeterli
Login + JSSeleniumForm doldurma + JS
Büyük ölçek (binlerce sayfa)ScrapyAsenkron, pipeline desteği

💡 İpucu: Her zaman en hafif aracı seçmeye çalış. Sayfa statik mi, dinamik mi bilmiyorsan önce requests ile dene — içeriği gelen HTML'de göremiyorsan Selenium'a geç.


10. İleri Teknikler

Sayfalama (Pagination) ile Tüm Veriyi Çekme

Çoğu site veriyi sayfalara böler. Tüm sayfaları gezmek için sayfalama mantığı kurman gerekir:

import requests
from bs4 import BeautifulSoup
import time

def scrape_all_pages(base_url):
    """Tüm sayfaları otomatik gezen scraper."""
    all_data = []
    page = 1
    
    while True:
        url = f"{base_url}/page/{page}/"
        response = requests.get(url)
        
        if response.status_code == 404:
            break
        
        soup = BeautifulSoup(response.text, "html.parser")
        items = soup.select(".quote")
        
        if not items:
            break
        
        for item in items:
            all_data.append({
                "text": item.select_one(".text").get_text(),
                "author": item.select_one(".author").get_text(),
            })
        
        print(f"Sayfa {page}: {len(items)} öğe")
        
        # Sonraki sayfa var mı?
        if not soup.select_one("li.next"):
            break
        
        page += 1
        time.sleep(1)
    
    return all_data

data = scrape_all_pages("https://quotes.toscrape.com")
print(f"Toplam: {len(data)} alıntı")

Hata Yönetimi ve Retry

Gerçek dünyada ağ hataları, zaman aşımları ve geçici sunucu sorunları yaşanır. Sağlam bir scraper bunlara hazırlıklı olmalı:

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

def create_resilient_session():
    """Otomatik retry mekanizmalı session oluşturur."""
    session = requests.Session()
    
    retry_strategy = Retry(
        total=3,                  # Toplam deneme sayısı
        backoff_factor=1,         # Denemeler arası bekleme: 1s, 2s, 4s
        status_forcelist=[429, 500, 502, 503, 504],  # Hangi status'larda tekrarla
    )
    
    adapter = HTTPAdapter(max_retries=retry_strategy)
    session.mount("http://", adapter)
    session.mount("https://", adapter)
    
    session.headers.update({
        "User-Agent": "EducationalBot/1.0"
    })
    
    return session

# Kullanım
session = create_resilient_session()
response = session.get("https://quotes.toscrape.com/", timeout=10)
print(f"Status: {response.status_code}")

Retry sınıfı, belirli HTTP durum kodlarında isteği otomatik tekrarlar. backoff_factor=1 ile denemeler arasında artan bekleme süreleri uygulanır (1s, 2s, 4s) — bu hem sunucuyu rahatlatır hem de geçici sorunların çözülmesine zaman tanır.



Özet

  • Web scraping, web sayfalarından programatik olarak veri çekme işlemidir — API olmadığında başvurulur

  • requests + BeautifulSoup statik sayfalar için hafif ve hızlı çözüm; find(), find_all(), select() ile HTML ağacında arama yaparsın

  • Selenium JavaScript ile render edilen dinamik sayfalar için gerçek tarayıcı otomasyonu sağlar; WebDriverWait ile akıllı bekleme yaparsın

  • pandas.read_html() web sayfalarındaki tabloları tek satırda DataFrame'e çevirir

  • Etik scraping robots.txt'e uymak, rate limiting uygulamak ve sitenin kullanım şartlarına saygı göstermek demektir

  • Her zaman en hafif aracı seç — önce API, sonra requests + BS4, en son Selenium veya Scrapy