Nedir bu web scraping?

Kısaca internet sayfalarından ham HTML çekip, içinden işe yarar veriyi ayıklama sanatıdır. Bir çeşit dijital madencilik düşün: HTTP isteğiyle cevher (HTML) alırsın, BeautifulSoup’la öğütürsün, geriye temiz veri kalır.

Bu yazıda tam da bu süreci uçtan uca deneyimleyeceğiz:

Amaç, web scraping’in temel akışını gerçek bir projede görmek. “Hello World” değil, kullanılabilir bir uçtan uca örnek.

Kurulum ve ortam

Önce sanal ortamı kuralım ve bağımlılıkları yükleyelim:

python -m venv .venv
source .venv/bin/activate    # Windows: .venv\Scripts\activate
pip install requests beautifulsoup4 lxml PyQt6

lxml’i BeautifulSoup için parser olarak kullanacağız çünkü Python’un standart parser’ından belirgin biçimde hızlı. Büyük tablolar için fark hissedilir.

HTTP isteği gönderme

requests ile minimum gürültülü bir istek atmak şöyle görünür. User-Agent header’ı eklemek, bazı sitelerin varsayılan istekleri reddetmesini önler:

import requests
from bs4 import BeautifulSoup

URL = "https://www.tff.org/Default.aspx?pageID=198"
HEADERS = {
    "User-Agent": (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "AppleWebKit/537.36 (KHTML, like Gecko) "
        "Chrome/124.0 Safari/537.36"
    )
}

response = requests.get(URL, headers=HEADERS, timeout=10)
response.raise_for_status()
soup = BeautifulSoup(response.text, "lxml")

HTML’i ayrıştırmak

BeautifulSoup’un en sevdiğim tarafı, CSS selektörlerini doğrudan kullanabilmesi. soup.select() bir liste döner; ne bulamadığında patlar ne de sessizce None döner:

rows = soup.select("table.s-table tbody tr")

teams = []
for row in rows:
    cells = [c.get_text(strip=True) for c in row.select("td")]
    if len(cells) < 9:
        continue
    teams.append({
        "rank":     int(cells[0]),
        "name":     cells[1],
        "played":   int(cells[2]),
        "wins":     int(cells[3]),
        "draws":    int(cells[4]),
        "losses":   int(cells[5]),
        "gf":       int(cells[6]),
        "ga":       int(cells[7]),
        "points":   int(cells[8]),
    })

Veriyi temizleme

Gerçek dünyada veri hiçbir zaman beklediğin gibi gelmez. Takım adlarına sponsor eki yapışmış olabilir, sayı sütunlarına bir tire karakteri kaçmış olabilir. Tek bir küçük normalize fonksiyonu hayat kurtarır:

SPONSOR_SUFFIXES = (" A.Ş.", " FK", " AŞ")

def clean_name(raw: str) -> str:
    for suffix in SPONSOR_SUFFIXES:
        if raw.endswith(suffix):
            return raw[: -len(suffix)].strip()
    return raw.strip()

PyQt6 arayüzü

Veriyi terminale yazdırmak yerine küçük bir tablo penceresinde gösterelim. PyQt6’nın QTableWidget’ı bu iş için fazlasıyla yeterli:

from PyQt6.QtWidgets import (
    QApplication, QTableWidget, QTableWidgetItem
)

app = QApplication([])
table = QTableWidget(len(teams), 9)
table.setHorizontalHeaderLabels(
    ["#", "Takım", "O", "G", "B", "M", "AG", "YG", "P"]
)
for r, t in enumerate(teams):
    for c, key in enumerate(["rank", "name", "played", "wins",
                              "draws", "losses", "gf", "ga", "points"]):
        table.setItem(r, c, QTableWidgetItem(str(t[key])))
table.show()
app.exec()

Kapanış notları

Web scraping’in tek satırlık örneklerden ibaret olmadığını görmek için kasıtlı olarak küçük ama uçtan uca bir akış seçtim. Gerçek projelerde karşına çıkacak şeyler aşağı yukarı bu akışın detaylandırılmış halinden ibaret.

Bir sonraki yazıda aynı pipeline’ı asenkron httpx ile dönüştürüp, paralel istekle 10 sayfayı saniyeler içinde nasıl tarayacağımızı göstereceğim.