Veri Ayrıştırma (Parsing) Nedir? Parser Türleri ve Hataları

Yayın tarihi:

12 dk okuma

Acar Diveroli
Yazar: Acar Diveroli
Kod parçalı karışık beyaz iplikler mavi bir çekirdekte birleşiyor; düz raylar dört kayda ve bir reddedilmiş satıra uzanıyor

Scraper'ınız bir ürün sayfasını indirip diske kaydediyor. Dosya 180 KB'lık bir HTML ve içinde bir yerlerde gerçekten istediğiniz dört bilgi duruyor: ürün adı, fiyat, stok durumu ve SKU. Bir program bu değerleri bulup denetleyene ve adlandırılmış alanlara yazana kadar indirdiğiniz şey yalnızca metindir. Ham metinden alanlara geçilen bu adımın adı veri ayrıştırmadır (data parsing).

Bu yazıda ayrıştırmanın ne anlama geldiğini, bir ayrıştırıcının (parser) iki temel aşama ve bunların çevresindeki adımlarla nasıl çalıştığını ve HTML, JSON, CSV, log ve özel biçimler için hangi tür parser'ın uygun olduğunu anlatıyoruz. Parsing'i scraping ve veri çıkarmadan (extraction) ayırıyor, BeautifulSoup ve json modülüyle denenmiş kısa bir Python örneği gösteriyor, gerçek projelerde parser'ları bozan hataları (bozuk HTML, geçersiz JSON, şema kayması) ele alıyor ve "kendimiz mi yazalım, hazır araç mı kullanalım" sorusuna bir rehberle bitiriyoruz.

Veri ayrıştırma nedir?

Ayrıştırma, bir girdiyi belirli kurallara göre çözümlemek ve girdinin söylediğini temsil eden bir yapı üretmektir. Girdi çoğunlukla bir metin dizesi ya da bayt akışıdır. Çıktı ise adları ve türleri olan bir şeydir: HTML öğelerinden oluşan bir ağaç, bir Python sözlüğü, bir satır listesi ya da "$1,249.00" metni yerine sayı tutan price alanlı bir kayıt.

Tarayıcınız HTML'i ayrıştırıp sayfaya, API istemciniz JSON'u nesnelere, elektronik tablo programınız CSV'yi hücrelere dönüştürür. Ayrıştırmayı ancak girdi, parser'ın beklediğiyle uyuşmadığında fark edersiniz: Program ya hata verip durur ya da, daha kötüsü, yanlış değerlerle çalışmaya devam eder.

Veri işlerinde bu kelime iki katmanda kullanılır:

  • Biçim ayrıştırma. Baytları biçimin kendi yapısına çevirmek: HTML'i DOM ağacına, JSON'u nesnelere, CSV'yi satırlara. Bunu kütüphaneler yapar.
  • Dar anlamda veri ayrıştırma. Bu yapıdan ihtiyacınız olan değerleri doğru türleriyle almak: fiyatı ondalık sayı, tarihi tarih, stok durumunu doğru ya da yanlış olarak. Bu kısmı genellikle kendiniz yazarsınız.

Parser nasıl çalışır?

Bir JSON kütüphanesinden tarayıcının HTML motoruna kadar neredeyse her parser aynı iki temel aşamayı izler: sözcüksel çözümleme (lexing) ve sözdizimsel çözümleme (parsing). Bu iki aşamadan önce girdiyi okuma adımı, sonra da değerleri seçme ve doğrulama adımları gelir.

  1. Girdiyi metin olarak okuma. Baytlar bir karakter kodlamasıyla, normalde UTF-8 ile karakterlere çevrilir. Burada yapılan yanlış bir tahmin, ayrıştırma başlamadan bozuk karakterler üretir; bu hatayı Python Türkçe Karakter Sorunu yazısında ele aldık.
  2. Belirteçlere ayırma (sözcüksel çözümleme). Parser karakter akışını belirteçlere, yani anlam taşıyan en küçük parçalara böler: bir açılış etiketi, bir öznitelik, bir metin dizesi, bir sayı, bir virgül, bir kapanış süslü parantezi.
  3. Yapıyı kurma (sözdizimsel çözümleme). Belirteçler biçimin dilbilgisine göre düzenlenir. HTML'de sonuç iç içe öğelerden oluşan bir ağaçtır; JSON'da iç içe nesneler ve diziler, CSV'de ise alanlardan oluşan satırlar.
  4. Değerleri seçme. Kodunuz bu yapıyı dolaşır ve ihtiyaç duyduğu alanları seçer; örneğin HTML ağacında bir CSS seçicisi ya da XPath ile. Bir öğeyi göstermenin bu iki yolunu CSS Selector ve XPath yazısında karşılaştırdık.
  5. Dönüştürme ve doğrulama. Metin türü belli veriye dönüşür: "$34.50" değeri 34.50 olur, "In stock" değeri true olur. Zorunlu bir alanı eksik olan kayıtlar sessizce kaydedilmez, işaretlenir.

HTML standardı, tarayıcının parser'ını tam da bu aşamalarla tarif eder. WHATWG'nin parsing bölümü süreci belirteçlere ayırma ve ağaç kurma olarak ikiye ayırır; ayrıca parser'ın hatalardan nasıl toparlanması gerektiğini de tanımlar. Tarayıcıların bozuk sayfaları reddetmek yerine göstermesinin nedeni budur.

Parsing, scraping ve veri çıkarma farkı

Bu üç kelime çoğu zaman eş anlamlı gibi kullanılır, ama aynı veri hattının farklı adımlarını adlandırır.

TerimNe yaparGirdiÇıktı
Crawling (tarama)Bağlantıları izleyerek sayfaları bulurBir başlangıç URL'siURL listesi
Scraping (kazıma)Bu sayfaların içeriğini indirirURL'lerHam HTML, JSON veya dosyalar
Parsing (ayrıştırma)Ham içeriği yapıya dönüştürürHam metinAğaçlar, nesneler, satırlar
Extraction (veri çıkarma)Yapının içinden gereken değerleri seçerBir ağaç veya nesneAdlandırılmış alanlar
Doğrulama ve temizlemeTürleri denetler, kopyaları siler, biçimleri düzeltirAlanlarGüvenilir kayıtlar
SaklamaKayıtları bir yere yazarKayıtlarCSV, JSON Lines, veritabanı

Günlük kullanımda "parsing" kelimesi ayrıştırmayı, veri çıkarmayı ve doğrulamanın bir kısmını birlikte kapsar; bu yazının geri kalanında da bu anlamda kullanıyoruz. Crawling ve scraping işin veri getirme tarafıdır; bu ayrımı Web Scraping ve Web Crawling Farkı yazısında anlattık. Kayıtların nereye yazılacağını Kazınan Veri CSV, JSON ve SQLite'a Nasıl Kaydedilir? yazısında, tüm zinciri tekrarlanabilir tek bir iş olarak ise ETL Nedir? yazısında ele alıyoruz.

Parser türleri

Farklı girdiler farklı parser'lar ister. Yanlış türü kullanmak, örneğin iç içe HTML üzerinde düzenli ifade (regex) çalıştırmak, kırılgan scraper'ların başlıca nedenlerinden biridir.

Parser türüEn uygun olduğu yerÖrneklerZayıf noktası
HTML / DOM parserBozuk işaretleme dahil web sayfalarıhtml.parser, lxml veya html5lib ile BeautifulSoup; Node.js'te CheerioSayfa düzeni değişince seçicilerin de değişmesi gerekir
JSON parserAPI yanıtları, sayfaya gömülü verilerPython json, JavaScript'te JSON.parseKatıdır: tek bir fazla virgül tüm belgeyi geçersiz kılar
CSV / ayraçlı dosya parser'ıDışa aktarımlar, elektronik tablolar, raporlarPython csv, pandas read_csvTırnaklama, ayraç ve kodlama, dosyayı üreten sisteme göre değişir
XML parserBeslemeler, site haritaları, eski kurumsal API'lerlxml, xml.etree.ElementTreeAd alanları (namespace) seçicileri uzatır
Düzenli ifadeler (regex)Bilinen bir alanın içindeki küçük, düz kalıplar: fiyat, tarih, kimlikPython reİç içe yapıyı izleyemez; düzen değişince bozulur
Dilbilgisi tabanlı parserÖzel biçimler, sorgu dilleri, yapılandırma dosyalarıANTLR, Lark gibi parser üreteçleriDilbilgisini yazmak zaman alır
Model tabanlı parserSabit düzeni olmayan dağınık metinJSON döndüren dil modelleriÇıktı mutlaka doğrulanmalı; sonuçlar çalıştırmadan çalıştırmaya değişebilir

İki satır ek açıklama istiyor. HTML parser'ları birbirinden farklıdır: BeautifulSoup, sizin seçtiğiniz bir parser'ın üzerinde çalışır ve belgelerinde bozuk <a></p> parçasının html.parser, lxml ve html5lib ile üç farklı ağaç ürettiği gösterilir. Parser'ı açıkça belirtin; aksi hâlde başka bir parser'ın kurulu olduğu makinede çıktınız değişebilir. Model tabanlı ayrıştırma, yapay zekâ destekli scraper'ların düzeni sürekli değişen sayfaları okuma yoludur. Esneklik kazanmak için kesin kurallardan vazgeçer; bu yüzden doğrulama daha da önem kazanır. Ayrıntıları AI Web Scraper Nedir ve Nasıl Çalışır? yazısında bulabilirsiniz.

Python ile kısa bir ayrıştırma örneği

Örnek, küçük bir ürün sayfasını bir HTML parser'ı, CSS seçicileri, fiyat alanı için tek bir düzenli ifade, zorunlu alan denetimi ve sayfaya gömülü veri için ayrı bir JSON ayrıştırmasıyla işliyor. Aşağıdakini page.html adıyla kaydedin; ikinci kartta kapatılmamış bir <span> var, üçüncüsünde ise fiyat yok:

html
<!doctype html>
<html lang="en">
<head>
  <title>Desk lamps</title>
  <script type="application/ld+json">
  {"@context": "https://schema.org", "@type": "ItemList",
   "itemListElement": [
     {"@type": "ListItem", "position": 1, "url": "/p/arc-lamp"},
     {"@type": "ListItem", "position": 2, "url": "/p/clip-lamp"}
   ]}
  </script>
</head>
<body>
  <div class="product" data-sku="L-100">
    <h2 class="name">Arc Desk Lamp</h2>
    <span class="price">$1,249.00</span>
    <span class="stock">In stock</span>
  </div>
  <div class="product" data-sku="L-200">
    <h2 class="name">  Clip Lamp </h2>
    <span class="price">$34.50</span>
    <span class="stock">Out of stock
  </div>
  <div class="product" data-sku="L-300">
    <h2 class="name">Floor Lamp</h2>
    <span class="stock">In stock</span>
  </div>
</body>
</html>

İki kütüphaneyi pip install beautifulsoup4 lxml ile kurun, ardından aşağıdaki kodu sayfanın yanına parse_products.py adıyla kaydedin:

python
import json
import re
from decimal import Decimal
from pathlib import Path

from bs4 import BeautifulSoup

PRICE_RE = re.compile(r"[\d.,]+")


def parse_price(text):
    """'$1,249.00' -> Decimal('1249.00'); sayı yoksa None."""
    match = PRICE_RE.search(text or "")
    if not match:
        return None
    return Decimal(match.group().replace(",", ""))


def parse_products(html):
    soup = BeautifulSoup(html, "lxml")
    rows, problems = [], []

    for card in soup.select("div.product"):
        name = card.select_one(".name")
        price = card.select_one(".price")
        stock = card.select_one(".stock")
        row = {
            "sku": card.get("data-sku"),
            "name": name.get_text(strip=True) if name else None,
            "price": parse_price(price.get_text()) if price else None,
            "in_stock": stock is not None and stock.get_text(strip=True) == "In stock",
        }
        missing = [key for key in ("sku", "name", "price") if row[key] is None]
        if missing:
            problems.append({"sku": row["sku"], "missing": missing})
            continue
        rows.append(row)

    # Sayfaya gömülü yapılandırılmış veri: HTML olarak değil, JSON olarak ayrıştırın.
    urls = []
    for tag in soup.select('script[type="application/ld+json"]'):
        try:
            data = json.loads(tag.string or "")
        except json.JSONDecodeError as err:
            problems.append({"json_ld": f"line {err.lineno}, col {err.colno}: {err.msg}"})
            continue
        urls += [item.get("url") for item in data.get("itemListElement", [])]

    return rows, urls, problems


if __name__ == "__main__":
    html = Path("page.html").read_text(encoding="utf-8")
    rows, urls, problems = parse_products(html)
    print(json.dumps(rows, indent=2, default=str))
    print("urls:", urls)
    print("problems:", problems)

python parse_products.py komutu BeautifulSoup 4.15.0, lxml 6.1.3 ve Python 3.13 ile şu çıktıyı verir:

text
[
  {
    "sku": "L-100",
    "name": "Arc Desk Lamp",
    "price": "1249.00",
    "in_stock": true
  },
  {
    "sku": "L-200",
    "name": "Clip Lamp",
    "price": "34.50",
    "in_stock": false
  }
]
urls: ['/p/arc-lamp', '/p/clip-lamp']
problems: [{'sku': 'L-300', 'missing': ['price']}]

Kapatılmamış <span> hiçbir şeyi bozmadı, çünkü lxml ağacı onardı; boşluklu ürün adı temiz çıktı ve fiyatı olmayan lamba boş hâliyle kaydedilmek yerine problems listesine düştü. Denemek için JSON-LD bloğunun sonuna fazladan bir virgül eklediğimizde betik çalışmaya devam etti ve hatanın yerini doğrudan gösteren line 5, col 64: Illegal trailing comma before end of object iletisini verdi. Bu parser'ın verdiği diğer hata iletilerini ve her birinin nedenini JSONDecodeError: Expecting Value Hatası Nasıl Çözülür? yazısında sıraladık.

Aynı parser'ın Node.js sürümü BeautifulSoup yerine Cheerio kullanır; bkz. Cheerio ile Web Scraping. İstek gönderme, sayfalama ve kaydetme adımlarını da içeren eksiksiz bir scraper için BeautifulSoup rehberiyle başlayın.

Sık görülen ayrıştırma hataları ve nedenleri

  • Bozuk HTML. Kapatılmamış etiketler, fazladan kapanış etiketleri ve yanlış yere konmuş etiketler web'de olağandır. Tarayıcılar ve HTML parser'ları bunları onarır, ama her parser kendi yöntemiyle onarır; bu yüzden html5lib ile çalışan bir seçici html.parser ile boş dönebilir. Parser seçimini sabitleyin ve kaydedilmiş sayfalar üzerinde test edin.
  • Geçersiz JSON. JSON katı bir biçimdir. Dilbilgisini RFC 8259 tanımlar; sondaki fazla virgüller, tek tırnaklar ve yorumlar bu dilbilgisinde yer almaz. Sık rastlanan bir neden ise aslında JSON'la ilgili değildir: Sunucu bir HTML hata sayfası ya da boş bir gövde döndürmüştür ve JSON parser'ı daha ilk karakterde hata verir.
  • Yinelenen anahtarlar. RFC 8259'a göre bir nesnede adlar yinelendiğinde alıcı yazılımın nasıl davranacağı öngörülemez. Python'un json modülü hata vermeden son değeri tutar; yani {"price": 10, "price": 12} sessizce 12 olur.
  • CSV'de tırnaklama ve ayraçlar. Ürün adının içindeki bir virgül, alan tırnak içine alınmadıysa tek alanı ikiye böler. RFC 4180 yaygın tırnaklama kuralını anlatır, ancak yalnızca bilgilendirme amaçlı bir belgedir ve pek çok dışa aktarım noktalı virgül ya da sekme kullanır. line.split(",") yerine her zaman bir CSV kütüphanesi kullanın.
  • Yerel ayara bağlı sayılar ve tarihler. 1.249,00 Almanya'da ve Türkiye'de binin üzerinde bir tutardır, basit bir parser ise onu 1 civarında bir sayıya çevirir; 03/04/2026 ülkeye göre mart ya da nisan anlamına gelir. Her kaynak için biçimi açıkça belirterek ayrıştırın.
  • Şema kayması. Site bir sınıfın adını değiştirir ya da fiyatı yeni bir öğeye taşır; parser çalışmaya devam eder ama None veya yanlış değer döndürür. Hiçbir şey çökmediği için en pahalıya patlayan hata budur. Yukarıdaki problems listesi gibi zorunlu alan denetimleri, sessiz kaymayı görünür bir sayıya dönüştürür.
  • HTML'de bulunmayan içerik. Bazı sayfalar içeriklerini yüklendikten sonra JavaScript ile oluşturur; bu durumda veri indirilen dosyada hiç yer almaz. Bunu nasıl anlayacağınızı Statik ve Dinamik Sayfalar yazısında gösterdik.

Parser'ı kendiniz mi yazmalısınız, hazır araç mı kullanmalısınız?

Standart biçimler için alt düzey parser'ı hiçbir zaman kendiniz yazmazsınız; kütüphaneler olgunlaşmıştır. Asıl karar, bunun üzerine kurulan veri çıkarma katmanıyla, yani site başına seçiciler, dönüştürmeler ve denetimlerle ilgilidir.

Kaynaklarınızın sayısı sınırlıysa ve nadiren değişiyorsa kendiniz yazın. Kendi kodunuz şeffaftır, sayfa başına maliyeti yoktur ve kaydedilmiş örnekler üzerinde kolayca test edilir.

Düzeni sık değişen yüzlerce siteyi izliyorsanız ya da girdinin e-postalar ve PDF'ler gibi sabit bir düzeni hiç yoksa hazır bir araç veya hizmet kullanın. Hacim ya da kullanıcı başına ödeme yapar, çıktı üzerinde daha az denetime razı olursunuz.

Karma bir yapı yaygındır: Varsa resmî API'ler ya da gömülü JSON, önemli kaynaklar için elle yazılmış parser'lar, geri kalan uzun kuyruk için de model tabanlı bir yedek kullanılır. Üçünden gelen kayıtlar, saklanmadan önce aynı doğrulama adımından geçer.

Ayrıştırmanın kullanıldığı alanlar

  • Büyük ölçekli veri toplama. Her scraping hattı indirme ile veritabanı arasında ayrıştırma yapar; bkz. veri kazıma.
  • Crawler'lar ve site denetimleri. Bir crawler her sayfayı bağlantılar ve üst veriler için ayrıştırır; bkz. web crawler.
  • Fiyat takibi. Sayıya çevrilmiş fiyatlar zaman içinde karşılaştırılabilir; örnek için rakip fiyat takibi yazısına bakın.
  • Değişiklik takibi. Ham HTML yerine ayrıştırılmış alanları karşılaştırmak, reklamlar ve zaman damgaları yüzünden çıkan yanlış alarmları önler; bkz. Web Sitesi Değişiklik Takibi Nasıl Yapılır?
  • Analiz. Türü belli kayıtlar, örüntü aramanın başladığı yerdir; bkz. Veri Madenciliği Nedir?
  • Loglar ve güvenlik. Hataları saymadan önce log satırları zaman damgası, IP adresi, durum kodu ve yol alanlarına ayrıştırılır.

Sık yapılan hatalar

  • Geliştirirken canlı siteyi ayrıştırmak. Birkaç gerçek sayfayı diske kaydedin ve parser'ı onlar üzerinde yazın: Siteye daha az istek gider, testler tekrarlanabilir olur.
  • Tüm HTML belgesinde regex kullanmak. Önce ağacı ayrıştırın, regex'i yalnızca tek bir alanın metni içinde kullanın.
  • Fiyatları metin ya da kayan noktalı sayı olarak saklamak. Erkenden Decimal türüne ya da kuruş cinsinden tamsayıya çevirin, hata ayıklama için ham metni de saklayın.
  • Her istisnayı yutmak. Çıplak bir try/except: pass şema kaymasını gizler. Hataları kayıt kimliğiyle birlikte toplayın ve her çalıştırmadan sonra sayısına bakın.
  • Gömülü JSON'u görmezden gelmek. Pek çok ürün sayfası, görünen HTML'deki değerlerin daha temiz bir hâlini içeren JSON-LD taşır.
  • Engellenen istekler için parser'ı suçlamak. Elinizdeki "HTML" bir engelleme sayfası ya da 429 hatasıysa hiçbir seçici eşleşmez. Ayrıştırmadan önce durum kodunu kontrol edin, site yavaşlamanızı istiyorsa hızınızı düşürün.

Karar rehberi

GirdinizÖnerilen yaklaşım
JSON döndüren bir APIAPI'yi kullanın; standart JSON kütüphanesiyle ayrıştırıp zorunlu alanları doğrulayın
Birkaç siteden statik HTML sayfalarılxml ile BeautifulSoup ya da Node.js'te Cheerio, artı CSS seçicileri
İçeriği JavaScript ile oluşturan sayfalarÖnce gömülü JSON'a veya sitenin API'sine bakın; headless tarayıcıyı yalnızca gerekirse kullanın
CSV veya elektronik tablo dışa aktarımlarıAyraç ve kodlaması açıkça belirtilmiş bir CSV kütüphanesi ya da pandas
Sabit düzenli sunucu loglarıSatır başına regex veya bir log parser'ı, ardından alanların türe çevrilmesi
Denetiminizdeki özel bir metin biçimiDilbilgisi tabanlı bir parser (ANTLR, Lark)
E-postalar, PDF'ler, düzensiz serbest metinÇıktısı sıkı biçimde doğrulanan model tabanlı bir çıkarıcı
Düzeni değişen yüzlerce siteTicari bir ayrıştırma hizmeti ya da karma bir yapı

Sıkça sorulan sorular

Veri ayrıştırma en basit hâliyle nedir?

Ham metni okuyup programın kullanabileceği etiketli parçalara dönüştürmektir. İçeri bir web sayfası girer, dışarı ad, fiyat ve stok durumu olan bir kayıt çıkar.

Parsing ile scraping arasındaki fark nedir?

Scraping, bir web sitesinden içerik indirir; parsing ise bu içeriği yapılandırılmış veriye dönüştürür. Parser'ı olmayan bir scraper size yalnızca ham HTML dosyaları bırakır.

HTML ayrıştırmak için hangi Python kütüphanesi kullanılır?

En yaygın olanı BeautifulSoup'tur ve html.parser, lxml veya html5lib üzerinde çalışır. lxml doğrudan da kullanılabilir ve XPath'i destekler. JSON için Python'un yerleşik json modülü yeterlidir.

HTML düzenli ifadelerle ayrıştırılabilir mi?

Yalnızca çok küçük, düz metin parçaları için. HTML iç içedir ve çoğu zaman bozuktur; regex iç içe yapıyı izleyemez. Sayfayı bir HTML parser'ıyla ayrıştırın, gerekirse regex'i tek bir alanın içinde kullanın.

Parser'ım neden birden boş değer döndürmeye başladı?

Çoğunlukla site işaretlemesini değiştirmiştir ve seçicileriniz artık eşleşmiyordur. Diğer olası nedenler: içerik yerine bir hata sayfasının gelmesi ya da içeriğin sonradan JavaScript ile yüklenmesi.

Veri ayrıştırmak için proxy gerekir mi?

Ayrıştırmanın kendisi sizin makinenizde çalışır ve proxy gerektirmez. Proxy'ler veri getirme tarafında önem kazanır: Bir scraper herkese açık sayfaları yüksek hacimde topladığında ya da bir sayfayı başka bir ülkedeki ziyaretçilerin gördüğü gibi görmesi gerektiğinde. Residential Proxy adresleriyle ülke ve şehir seçebilir, Rotating Proxy ile istekleri farklı adreslere dağıtabilirsiniz; her iki durumda da sitelerin kullanım koşullarına ve hız sınırlarına uyun.

Özet

Veri ayrıştırma, ham girdiyi türü belli, yapılandırılmış kayıtlara dönüştürür: Bir belirteç ayırıcı (tokenizer) metni parçalara böler, parser bu parçaları biçimin kurallarına göre düzenler, kodunuz da ihtiyaç duyduğu değerleri seçer, dönüştürür ve denetler. Parser'ı girdiye göre seçin, regex'i tek tek alanların içinde tutun. Gerçek bir projede emeğin büyük kısmı hatalara gider; özellikle de hiçbir hata iletisinin haber vermediği şema kaymasına. Bu yüzden her kaydı doğrulayın ve her çalıştırmadan sonra hataları sayın. Veri getirme tarafında belirli ülkelerden adreslere ihtiyaç duyduğunuzda proxy hizmetlerimize göz atın.

ChatGPT'ye sorClaude'a sor