---
title: 'Yapısal Ayrıştırma'
source: 'https://academia.sh/tr/kurslar/veri-hazirlama/yapisal-ayristirma'
course: 'Veri Toplama ve Hazırlama'
language: tr
updated: '2026-08-17T18:08:45+00:00'
license: 'CC BY-SA 4.0'
---

# Yapısal Ayrıştırma

Aynı 1.877 satırı yayımlayan bir sayfa dört yapı sürümünde iki seçici setiyle okunuyor ve kırılan seçici sayısının hasarı ölçmediği çıkıyor: araya tek bir sarmalayıcı katman girince konumsal setin yol seçicisi kırılır ve 1.877 satırın tamamı düşer, ama kırılma gürültülüdür. Araya bir sütun eklendiğinde hiçbir seçici kırılmaz, 1.877 kayıt çıkar, hiçbiri düşmez ve hepsi yanlış alandan okunmuştur; toplam yalnızca 60,1 m³ sapar, yüzde 0,16, ve 1.877 kaydın bölge alanı artık bir bölge adı bile değildir. Sunum katmanı yenilendiğinde bu kez anlamsal setin iki seçicisi kırılır ve çıkan kayıt sıfıra iner. Sayfanın kendi bildirdiği toplamla hesaplanan toplamı karşılaştırmak, sessiz kaymayı yakalayan tek denetimdir.

Bir önceki dersin kaynağı verisini alan için tasarlanmış bir arayüzden verdi: alan adları
yanıtın içindeydi, sayfa sınırı ilan edilmişti. Verinin bir kısmı ise böyle bir arayüzün arkasında
değil, insan gözü için düzenlenmiş bir sayfanın yapısının içinde durur. Orada alan adı yoktur;
kaydın hangi parçasının nerede olduğunu yalnızca konum ve görünüm işaretleri söyler.

Bu dersin ölçüsü şudur: sayfanın yapısı değiştiğinde kaç seçici kırılır, kaç satır düşer ve kaç
satır yanlış alandan okunmuş olarak hayatta kalır. Ölçünün asıl sorusu sayının kendisi değil,
kırılmanın **duyulup duyulmadığıdır**. Teknik soru burada da tek başına yeterli değildir: bir
sayfadan kazıma yoluyla veri toplamak, kaynağın kullanım koşulları ve açık bir erişim izni
olmadan teknik olarak mümkün olsa bile hukuki ve mesleki bir sorundur; burada izin verilmiş bir
toplama varsayılır ve kişisel veri taşıyan alanlar sayfaya hiç konmamıştır.

- **VT15.** Sayfa kurgudur ve `node` ile modellenir: belge ağacı bellekte kurulur, ağ çağrısı
  yapılmaz. Koşan kod bir toplama aracı değildir; yalnızca ayrıştırır, doğrular ve sayar.
  **Modeldir.**
- **VT16.** Sayfa önceki iki dersin kümesini yayımlar — 1.877 okuma satırı, 38.690,7 m³.
- **VT17.** Dört yapı sürümü aynı veriyi taşır, düzeni değişir: `Y0` taban, `Y1` tabloyu bir
  sarmalayıcı katmana alır, `Y2` araya bir sütun ekler, `Y3` sunum katmanını yeniler.
- **VT18.** Bir seçici iki durumda **kırık** sayılır: hiçbir düğümle eşleşmiyorsa ya da üstbilgi
  metninden çözülmesi gereken sütun konumu bulunamıyorsa.
- **VT19.** İki okumanın aynı sayılması için eşik 0,05 m³'tür, çünkü sayfa değerleri bir
  ondalıkla yayımlıyor ve altındaki bir fark yalnızca yuvarlamadan gelebilir.

## Sayfanın Yapısı

```js
// sayfa.mjs — kurgu bir kamu yayin sayfasinin belge agaci ve kucuk bir secici
// motoru. MODELDIR: ag cagrisi yoktur, sayfa bellekte kurulur ve ayni 1877
// okuma satirini yayimlar. Dort yapi surumu ayni veriyi tasir, duzeni degisir.
export const TOHUM = 4711;
const uretec = (t) => { let s = t >>> 0;
  return () => { s = (Math.imul(s, 1103515245) + 12345) >>> 0; return s / 4294967296; }; };
const r = uretec(TOHUM);
export const BOLGE = ["kuzey", "doğu", "merkez", "güney", "batı"];
export const KAYIT = [];
for (let i = 0; i < 1877; i++) {           // once 1400 abonenin ilk donemi, sonra 477 ikinci
  const n = i < 1400 ? i : i - 1400, desi = 50 + Math.floor(r() * 321);
  KAYIT.push({ abone_no: 10000 + n, donem: i < 1400 ? "2024-01" : "2024-02", desi,
    onceki: 50 + ((desi * 37 + 11) % 321), bolge: BOLGE[n % 5] });
}
export const kimlik = (k) => `${k.abone_no}-${k.donem}`;
export const m3 = (d) => d / 10;

// Dugum: { etiket, oz, cocuk }; cocuk ya dugum dizisi ya da metindir.
const d = (etiket, oz, cocuk) => ({ etiket, oz, cocuk });
export const metin = (n) => (typeof n.cocuk === "string" ? n.cocuk
  : n.cocuk.map(metin).join(""));
const cocuklar = (n) => (Array.isArray(n.cocuk) ? n.cocuk : []);
const soy = (n) => cocuklar(n).flatMap((c) => [c, ...soy(c)]);

// Secici dili: bosluk = herhangi bir alt duzey, ">" = dogrudan cocuk.
// Adim bicimi: etiket, .sinif, [ad=deger] ya da bunlarin birlesimi.
const parca = (p) => {
  const m = /^([\w-]*)(?:\.([\w-]+))?(?:\[([\w-]+)=([\w-]+)\])?$/.exec(p);
  return { etiket: m[1], sinif: m[2], ad: m[3], deger: m[4] };
};
const uyar = (n, t) => (!t.etiket || n.etiket === t.etiket)
  && (!t.sinif || (n.oz.sinif ?? "") === t.sinif)
  && (!t.ad || n.oz[t.ad] === t.deger);
export function bul(kok, secici) {
  let dugum = [kok], dogrudan = false;
  for (const p of secici.trim().split(/\s+/)) {
    if (p === ">") { dogrudan = true; continue; }
    const t = parca(p);
    dugum = dugum.flatMap((n) => (dogrudan ? cocuklar(n) : soy(n)).filter((x) => uyar(x, t)));
    dogrudan = false;
  }
  return dugum;
}

// Y0 taban; Y1 tabloyu bir sarmalayici katmana alir; Y2 araya "onceki_m3"
// sutunu ekler; Y3 sunumu yeniler: sinif adlari uretilmis adlarla degisir ve
// okuma sutununun basligi yeniden adlandirilir.
export function sayfa(surum) {
  const ek = surum === "Y2", yeni = surum === "Y3";
  const sn = (a) => (yeni ? { ozet: "b-7f2", toplam: "b-91c", satir: "b-4ad" }[a] : a);
  const baslik = ek ? ["abone_no", "donem", "onceki_m3", "okuma_m3", "bolge"]
    : ["abone_no", "donem", yeni ? "tuketim_m3" : "okuma_m3", "bolge"];
  const hucre = (k) => (ek ? [k.abone_no, k.donem, m3(k.onceki), m3(k.desi), k.bolge]
    : [k.abone_no, k.donem, m3(k.desi), k.bolge])
    .map((v) => d("hucre", {}, String(v)));
  const tablo = d("tablo", {}, [
    d("ustbilgi", {}, [d("satir", {}, baslik.map((b) => d("hucre", {}, b)))]),
    d("govde", {}, KAYIT.map((k) => d("satir",
      { sinif: sn("satir"), "veri-tur": "okuma" }, hucre(k)))),
  ]);
  const toplam = KAYIT.reduce((t, k) => t + m3(k.desi), 0).toFixed(1);
  return d("sayfa", {}, [
    d("ozet", { sinif: sn("ozet") }, [d("p", { sinif: sn("toplam") },
      `Toplam: ${toplam} m3`)]),
    d("bolum", {}, [surum === "Y1" ? d("sarmalayici", { sinif: "kutu" }, [tablo]) : tablo]),
  ]);
}

if (import.meta.url === `file://${process.argv[1]}`) {
  console.log(`tohum ${TOHUM}; sayfa ${KAYIT.length} okuma satiri yayimliyor, toplam ` +
    `${KAYIT.reduce((t, k) => t + m3(k.desi), 0).toFixed(1)} m3`);
  const y = (h) => h.map((v, j) => (j ? String(v).padStart([0, 8, 8, 44, 32][j])
    : String(v).padEnd(6))).join("");
  console.log(y(["surum", "dugum", "satir", "sutun basliklari", "ilk veri satiri"]));
  for (const s of ["Y0", "Y1", "Y2", "Y3"]) {
    const k = sayfa(s), satir = bul(k, "govde > satir");
    console.log(y([s, soy(k).length, satir.length,
      bul(k, "ustbilgi satir hucre").map(metin).join(","),
      bul(satir[0], "hucre").map(metin).join(",")]));
  }
}
```

```
tohum 4711; sayfa 1877 okuma satiri yayimliyor, toplam 38690.7 m3
surum    dugum   satir                            sutun basliklari                 ilk veri satiri
Y0        9396    1877               abone_no,donem,okuma_m3,bolge          10000,2024-01,18,kuzey
Y1        9397    1877               abone_no,donem,okuma_m3,bolge          10000,2024-01,18,kuzey
Y2       11274    1877     abone_no,donem,onceki_m3,okuma_m3,bolge     10000,2024-01,30.1,18,kuzey
Y3        9396    1877             abone_no,donem,tuketim_m3,bolge          10000,2024-01,18,kuzey
```

Dört sürümün ortak yanı sağdan ikinci sütunda görünmüyor: **veri satırı sayısı dördünde de
1.877**. Yayımlanan kayıt kümesi hiç değişmedi. Değişen şey düğüm sayısı ve sütun düzenidir. Y1
sayfaya tek bir düğüm ekler (9.396'dan 9.397'ye), Y2 her satıra bir hücre ekleyerek düğüm sayısını
11.274'e çıkarır, Y3 hiçbir düğüm eklemez ve yalnızca metinleri değiştirir.

Bu üç değişikliğin hiçbiri veriyi bozmaz. Y1 görünümü kutu içine almak, Y2 bir sütun daha
yayımlamak, Y3 sunumu yenilemek için yapılmış olabilir; üçü de sayfayı yayımlayan taraf açısından
sıradan bakım işleridir. Sayfanın yapısı bir sözleşme değildir ve yayımlayan tarafın kimseye haber
verme yükümlülüğü yoktur. Ayrıştırıcının bağlandığı şey işte bu sözleşmesiz yüzeydir.

Son sütun Y2'nin neden özel olduğunu gösteriyor. İlk satırın hücreleri artık
`10000,2024-01,30.1,18,kuzey`: üçüncü hücrede 18 değil 30,1 duruyor ve 30,1 de bir okuma değeri
gibi görünüyor. Yeni sütun **sayısal ve aynı aralıkta** olduğu için, üçüncü hücreyi okuma sanan
bir ayrıştırıcı geçerli bir sayı bulur.

## Yapı Değişince Ne Kırılır

```js
// ayristir.mjs — ayni sayfayi iki secici seti okur. A1 konuma dayanir: yol ve
// sabit sutun sirasi. A2 anlama dayanir: satiri veri isareti, sutunu ustbilgi
// metni bulur. Olculen sey yapi degisince kac secici kirildigi ve kac satirin
// dustugu ya da yanlis alandan okunmus olarak hayatta kaldigidir.
import { sayfa, bul, metin, KAYIT, kimlik, m3, BOLGE } from "./sayfa.mjs";

const A1 = { ad: "A1 konumsal", satir: "bolum > tablo > govde > satir", toplam: "ozet > p",
  konum: () => ({ abone: 0, donem: 1, okuma: 2, bolge: 3 }) };
const A2 = { ad: "A2 anlamsal", satir: "satir[veri-tur=okuma]", toplam: ".ozet .toplam",
  konum: (kok) => {
    const b = bul(kok, "ustbilgi satir hucre").map(metin);
    return { abone: b.indexOf("abone_no"), donem: b.indexOf("donem"),
      okuma: b.indexOf("okuma_m3"), bolge: b.indexOf("bolge") };
  } };

// Esik 0.05 m3: okumalar bir ondalikla yayimlaniyor, altindaki bir fark
// yalnizca yuvarlamadan gelebilir.
const ESIK = 0.05;
const DOGRU = new Map(KAYIT.map((k) => [kimlik(k), m3(k.desi)]));
const HEDEF = KAYIT.reduce((t, k) => t + m3(k.desi), 0);

function coz(kok, a) {
  const kirik = [], satirlar = bul(kok, a.satir), basliklar = bul(kok, a.toplam);
  if (satirlar.length === 0) kirik.push(a.satir);
  if (basliklar.length === 0) kirik.push(a.toplam);
  const k = a.konum(kok);
  for (const [ad, i] of Object.entries(k)) if (i < 0) kirik.push(`sutun:${ad}`);
  const cikan = [];
  for (const s of satirlar) {
    const h = bul(s, "hucre").map(metin), okuma = Number(h[k.okuma]);
    if (!Number.isFinite(okuma)) continue;         // sessizce dusen satir
    cikan.push({ ad: `${h[k.abone]}-${h[k.donem]}`, okuma, bolge: h[k.bolge] });
  }
  const bildirilen = basliklar.length ? Number(/[\d.]+/.exec(metin(basliklar[0]))[0]) : null;
  return { kirik, cikan, bildirilen };
}

const y = (h) => h.map((v, j) => (j > 1 ? String(v).padStart([0, 0, 9, 7, 7, 7, 8, 11, 12][j])
  : String(v).padEnd(j ? 14 : 6))).join("");
console.log(y(["yapi", "ayristirici", "kirilan", "giren", "cikan", "dusen", "yanlis",
  "toplam m3", "bildirilen"]));
const kayit = {};
for (const surum of ["Y0", "Y1", "Y2", "Y3"]) {
  const kok = sayfa(surum);
  for (const a of [A1, A2]) {
    const r = kayit[surum + a.ad] = coz(kok, a);
    const t = r.cikan.reduce((s, x) => s + x.okuma, 0);
    const yanlis = r.cikan.filter((x) => !(Math.abs((DOGRU.get(x.ad) ?? -1) - x.okuma) < ESIK));
    console.log(y([surum, a.ad, r.kirik.length, KAYIT.length, r.cikan.length,
      KAYIT.length - r.cikan.length, yanlis.length, t.toFixed(1),
      r.bildirilen === null ? "-" : r.bildirilen.toFixed(1)]));
  }
}
const y1a = kayit["Y1A1 konumsal"], y2a = kayit["Y2A1 konumsal"], y3b = kayit["Y3A2 anlamsal"];
console.log(`\nY1'de tek bir katman eklendi ve A1'in yol secicisi ${y1a.kirik[0]} hicbir satir ` +
  `bulamadi: ${KAYIT.length} satirin tamami dustu, kirilma gurultuludur`);
const t2 = y2a.cikan.reduce((s, x) => s + x.okuma, 0);
console.log(`Y2'de A1'in kirilan secicisi yok, ${y2a.cikan.length} kayit cikti ve hicbiri ` +
  `dusmedi; ama sutunlar bir kaydigi icin toplam ${(t2 - HEDEF).toFixed(1)} m3 sapti ve ` +
  `${y2a.cikan.filter((x) => !BOLGE.includes(x.bolge)).length} kaydin bolge alani bir bolge ` +
  `adi degil`);
console.log(`Y2'de sayfanin kendi bildirdigi ${y2a.bildirilen.toFixed(1)} m3 ile A1'in ` +
  `hesapladigi ${t2.toFixed(1)} m3 arasindaki fark ${Math.abs(t2 - y2a.bildirilen).toFixed(1)} ` +
  `m3, esik ${ESIK} m3'un ${Math.round(Math.abs(t2 - y2a.bildirilen) / ESIK)} kati`);
console.log(`Y3'te A2'nin ${y3b.kirik.length} secicisi kirildi (${y3b.kirik.join(", ")}) ve ` +
  `cikan kayit ${y3b.cikan.length}; ayni surumde A1 hicbir sey kaybetmedi`);
```

```
yapi  ayristirici     kirilan  giren  cikan  dusen  yanlis  toplam m3  bildirilen
Y0    A1 konumsal           0   1877   1877      0       0    38690.7     38690.7
Y0    A2 anlamsal           0   1877   1877      0       0    38690.7     38690.7
Y1    A1 konumsal           1   1877      0   1877       0        0.0     38690.7
Y1    A2 anlamsal           0   1877   1877      0       0    38690.7     38690.7
Y2    A1 konumsal           0   1877   1877      0    1877    38630.6     38690.7
Y2    A2 anlamsal           0   1877   1877      0       0    38690.7     38690.7
Y3    A1 konumsal           0   1877   1877      0       0    38690.7     38690.7
Y3    A2 anlamsal           2   1877      0   1877       0        0.0           -

Y1'de tek bir katman eklendi ve A1'in yol secicisi bolum > tablo > govde > satir hicbir satir bulamadi: 1877 satirin tamami dustu, kirilma gurultuludur
Y2'de A1'in kirilan secicisi yok, 1877 kayit cikti ve hicbiri dusmedi; ama sutunlar bir kaydigi icin toplam -60.1 m3 sapti ve 1877 kaydin bolge alani bir bolge adi degil
Y2'de sayfanin kendi bildirdigi 38690.7 m3 ile A1'in hesapladigi 38630.6 m3 arasindaki fark 60.1 m3, esik 0.05 m3'un 1202 kati
Y3'te A2'nin 2 secicisi kirildi (.ozet .toplam, sutun:okuma) ve cikan kayit 0; ayni surumde A1 hicbir sey kaybetmedi
```

Taban yapıda iki set de aynı kümeyi çıkarıyor: 1.877 kayıt, sıfır düşen, sıfır yanlış, 38.690,7
m³. Aynı sonucu veren iki ayrıştırıcı aynı ayrıştırıcı değildir ve fark yalnızca yapı değişince
ölçülebilir.

Y1 satırında **bir katman**, bir seçici ve 1.877 satır var. Araya giren tek `sarmalayici` düğümü
A1'in dört adımlık yol seçicisini geçersiz kıldı, çünkü `bolum > tablo` doğrudan çocuk ister ve
tablo artık bolumün torunu. Kırılan seçici sayısı 1, düşen satır 1.877. Bu kayıp **gürültülüdür**:
çıkan kayıt sıfırdır ve bunu fark etmemek için hiçbir denetim yapmamak gerekir. A2'nin satır
seçicisi `satir[veri-tur=okuma]` yolu değil işareti aradığı için aynı sayfada 1.877 satır bulur.

Y2 dersin asıl bulgusudur. A1'in **hiçbir seçicisi kırılmadı**: yol yerinde, dört indis de
sınırlar içinde, 1.877 kayıt çıktı ve hiçbiri düşmedi. Ama sütunlar bir kaydığı için okuma alanına
önceki dönem okuması geldi ve **1.877 kaydın tamamı yanlış değer taşıyor**. Toplam 38.630,6 m³,
yani gerçekten yalnızca 60,1 m³, yüzde 0,16 uzakta. Bir eşik, bir büyüklük kontrolü, bir "makul mü"
bakışı bu sayıyı geçirir. Bölge alanı ise artık okuma değerini taşıyor: **1.877 kaydın bölge alanı
bir bölge adı bile değil**. Kırılan seçici sayısı hasarı ölçmez — Y1'de o sayı 1 iken tek bir
yanlış değer üretilmedi, Y2'de 0 iken tek bir doğru değer üretilmedi.

Y3 aynı sorunun yönünü ters çeviriyor. Sunum yenilenince A2'nin sınıfa dayalı toplam seçicisi ve
üstbilgi metnine dayalı okuma sütunu birlikte kırıldı: **2 kırık seçici, sıfır çıkan kayıt**.
Konuma dayanan A1 ise aynı sürümde hiçbir şey kaybetmedi, çünkü sınıf adı da başlık metni de
onun umurunda değil. Anlamsal seçiciler daha dayanıklı değildir; **başka şeylere** bağlıdır ve
hangi setin dayanacağı, yapının hangi yönde değişeceğine bakar. Ölçülmeden bilinemez.

Son satırlar tek gerçek savunmayı gösteriyor. Sayfa kendi toplamını yayımlıyor ve Y2'de bu sayı
38.690,7 m³ iken ayrıştırılan küme 38.630,6 m³ veriyor; fark 60,1 m³, eşiğin 1.202 katı. Hiçbir
seçicinin kırılmadığı, hiçbir satırın düşmediği, toplamın makul göründüğü durumda hatayı yakalayan
tek şey **kaynağın kendi bildirdiği sayıyla karşılaştırma** oldu. Bir ayrıştırıcıyı doğrulamak,
seçicilerin eşleşip eşleşmediğine bakmak değil, çıkan kümeyi kaynağın kendi beyanına karşı
saymaktır.

## Özet

- Sayfanın yapısı bir sözleşme değildir; aynı 1.877 satır dört düzende yayımlanabilir ve düzen
  değişimi yayımlayan taraf için sıradan bir bakım işidir.
- Araya tek bir katman girmesi konuma dayalı yol seçicisini kırar: 1 kırık seçici, 1.877 düşen
  satır, sıfır çıkan kayıt. Bu kırılma gürültülüdür ve fark edilmemesi zordur.
- Araya bir sütun girmesi hiçbir seçiciyi kırmaz: 1.877 kayıt çıkar, sıfır düşer ve 1.877'sinin
  değeri yanlıştır; toplam yalnızca 60,1 m³, yüzde 0,16 sapar ve bölge alanı bölge adı olmaktan
  çıkar.
- Sunum katmanının yenilenmesi bu kez anlamsal seti kırar (2 seçici, sıfır kayıt) ve konumsal set
  aynı sürümden zarar görmez; dayanıklılık setin türünden değil, değişimin yönünden çıkar.
- Kırılan seçici sayısı hasarı ölçmez; ayrıştırılan kümeyi kaynağın kendi bildirdiği toplamla
  karşılaştırmak, sessiz kaymayı yakalayan tek denetimdir.

## Sonraki Adım

Dört derste veri dört yoldan geldi ve her yolda kaç satırın kaybolduğu sayıldı. Bu derslerin ortak
varsayımı, kümenin tamamının toplanabilir olmasıydı: dosya okunabiliyordu, sorgu koşabiliyordu,
arayüz sonuna kadar sayfalanabiliyordu, sayfa baştan sona ayrıştırılabiliyordu. Kütle büyüdüğünde
bu varsayım düşer ve yerine bir karar gelir — bütün kayıtlar yerine bir alt küme alınır. Sonraki
ders dört örnekleme kararının aynı soruya verdiği dört cevabı ölçer ve asıl soruyu sorar: alınan
alt küme kütleyi ne kadar temsil ediyor ve dışarıda kalan kayıtlar kimler.
