- XML'i Python'da ağaç olarak okumak
- Dosyayı ayrıştırmak ve kök elemana ulaşmak
- Eleman bulmak: find, findall, iter ve yol ifadeleri
- Ad alanlı XML ile çalışmak
- Değiştirmek ve XML olarak yazmak
- Büyük dosyalar, güvenilmeyen kaynaklar ve lxml
- Sık yapılan hatalar
- Bu beceriyi Mindset programlarında nerede çalışırsınız
- Sık sorulan sorular
Python'da XML Dosyası Ayrıştırma: Okuma, Değiştirme ve Yazma
XML dosyasını Python'un standart kütüphanesiyle ağaç olarak okur, elemanlara ve özniteliklere ulaşır, değiştirip dosyaya geri yazarsınız.
Bir bakışta
- ElementTree standart kütüphanededir. Ek kurulum gerekmez; xml.etree.ElementTree modülü Python ile birlikte gelir.
- XML bir ağaçtır, satır listesi değil. Kök elemandan başlar, alt elemanlara find ve findall ile inersiniz.
- text, attrib ve tail ayrı alanlardır. Eleman metni, öznitelikler ve eleman sonrası metin birbirine karıştırılmamalıdır.
- Ad alanlı XML'de eşleme sözlüğü gerekir. Önekli elemanları aramak için namespaces sözlüğünü find ve findall'a verin.
- Büyük dosyada iterparse kullanın. Ağacın tamamını belleğe almadan elemanları geldikçe işler.
XML'i Python'da ağaç olarak okumak
XML, etiketlerin iç içe geçtiği hiyerarşik bir metin biçimidir. Sipariş sistemleri, haber beslemeleri, yapılandırma dosyaları ve kurumlar arası veri aktarımları bu biçimi hâlâ kullanır. Satırı elle bölmek burada işe yaramaz: bir elemanın değeri birden çok satıra yayılabilir, etiketler iç içe girer. Bu yüzden XML bir metin olarak değil, bir ağaç olarak okunur.
Python'un standart kütüphanesindeki xml.etree.ElementTree modülü bu ağacı kurar. Her düğüm bir Element nesnesidir ve dört şeyi taşır: etiket adı (tag), öznitelikler sözlüğü (attrib), etiketin içindeki metin (text) ve elemanın kapanış etiketinden sonra gelen metin (tail). Aşağıdaki küçük belge, bölümün geri kalanındaki örneklerin temelidir:
<siparisler> <siparis no="A-ilk" durum="hazir"> <musteri>Ayşe Yılmaz</musteri> <urun>Klavye</urun> <renk>siyah</renk> </siparis> <siparis no="A-son" durum="bekliyor"> <musteri>Mehmet Kaya</musteri> <urun>Fare</urun> </siparis> </siparisler>
Burada siparisler kök elemandır, her siparis bir alt eleman, no ve durum ise öznitelik. İkinci siparişte renk elemanının olmadığına dikkat edin; gerçek veride eksik eleman kuraldır, istisna değil.
XML işi hep aynı basamaklardan geçer
Her basamak bir öncekinin çıktısını kullanır
- Ayrıştırparse ile dosyayı ağaca çevir
- Kökü algetroot ile en dıştaki elemana ulaş
- Bulfind, findall, iter ile elemanları seç
- Oku ya da değiştirtext ve attrib ile değeri al, set ile güncelle
- Yazwrite ile kodlamayı belirterek kaydet
Dosyayı ayrıştırmak ve kök elemana ulaşmak
Dosyadan okumak için parse işlevi kullanılır; dönen nesne bir ağaçtır (ElementTree), kök elemanı getroot ile alırsınız:
import xml.etree.ElementTree as ET
agac = ET.parse('siparisler.xml') kok = agac.getroot() print(kok.tag)
XML metni zaten bir değişkendeyse, örneğin bir web servisinden gelmişse fromstring işlevi doğrudan kök elemanı verir; bu durumda ayrıca getroot çağırmazsınız.
Kök eleman bir sıra gibi davranır: üzerinde for döngüsüyle doğrudan çocuklarını gezebilirsiniz. Her çocuğun etiketi tag, öznitelikleri attrib sözlüğüdür:
for siparis in kok: print(siparis.tag, siparis.attrib)
Öznitelik okumanın güvenli yolu get yöntemidir. siparis.get('durum') öznitelik yoksa hata vermez, boş değer (None) döndürür; ikinci argüman varsayılan değeri belirler. Eleman metni ise her zaman metin (str) olarak gelir. Sayısal bir değer okuyorsanız dönüştürmek sizin göreviniz olur ve eksik elemanı da hesaba katmanız gerekir.
Eleman bulmak: find, findall, iter ve yol ifadeleri
Bir elemanın doğrudan çocuklarını bulmak için find ve findall kullanılır. find ilk eşleşmeyi, eşleşme yoksa None döndürür; findall eşleşenlerin tamamını liste olarak verir. iter ise elemanın kendisi ve tüm torunları üzerinde belge sırasıyla gezer; derinlikte bir etiketi ararken işe yarar.
for siparis in kok.findall('siparis'): musteri = siparis.find('musteri').text urun = siparis.findtext('urun') print(musteri, urun)
for eleman in kok.iter('urun'): print(eleman.text)
findall ve find, tam bir XPath motoru değil, XPath'in bir alt kümesini destekler. Python belgelerine göre bu alt küme şunları içerir: etiket adı, her şeyi eşleyen yıldız, geçerli eleman için nokta, her derinlikte arama için çift eğik çizgi, üst elemana çıkış için iki nokta ve köşeli parantezli koşullar. Örneğin kok.findall(".//siparis[@durum='hazir']") yalnız durumu hazir olan siparişleri verir; kok.findall('siparis[urun]') ise urun alt elemanı bulunan siparişleri seçer.
Buradaki en sık tuzak, find sonucuna doğrudan text eklemektir. Eleman yoksa find None döndürür ve text çağrısı hata verir. findtext bu durumu yumuşatır: eleman yoksa varsayılan değeri döndürür. Ayrıca belgelere göre çocuğu olmayan bir eleman mantıksal bağlamda yanlış (falsy) sayılır; bu yüzden elemanın varlığını if eleman: ile değil, if eleman is not None: ile sınayın.
Ad alanlı XML ile çalışmak
Kurumlar arası XML dosyalarının çoğu ad alanı kullanır. Bu durumda etiketler arka planda süslü parantez içinde bir adres taşır ve düz etiket adıyla aranınca hiçbir şey bulunmaz. Çözüm, öneki adrese eşleyen bir sözlüğü find ve findall'a vermektir:
ad_alanlari = {'sip': 'http://ornek.example/siparis'} for siparis in kok.findall('sip:siparis', ad_alanlari): print(siparis.find('sip:musteri', ad_alanlari).text)
Önek, belgedeki önekle aynı olmak zorunda değildir; önemli olan adresin aynı olmasıdır. Dosyada hiç önek görmüyor ama arama sonuç vermiyorsa kök etiketin varsayılan bir ad alanı taşıyıp taşımadığına bakın: print(kok.tag) çıktısı süslü parantezle başlıyorsa elemanlar bir ad alanındadır.
Herhangi bir ad alanından bağımsız arama gerekiyorsa iter ile tüm elemanları gezip etiketin sonunu karşılaştırmak mümkündür, ancak bu yöntem aynı adlı farklı elemanları ayırt edemez. Yapıyı biliyorsanız eşleme sözlüğü daha güvenlidir.
Değiştirmek ve XML olarak yazmak
Ağaç bellekte değiştirilebilir. Bir özniteliği set ile günceller, metni text'e atayarak değiştirir, yeni alt eleman eklemek için SubElement kullanırsınız:
for siparis in kok.findall('siparis'): if siparis.get('durum') == 'bekliyor': siparis.set('durum', 'islemde') not_eleman = ET.SubElement(siparis, 'not') not_eleman.text = 'Stok kontrol edildi'
ET.indent(agac) agac.write('guncel.xml', encoding='utf-8', xml_declaration=True)
Yazarken iki ayrıntı önemlidir. Birincisi kodlama: write işlevinin varsayılan kodlaması US-ASCII'dir ve çıktı bayt olarak yazılır. Türkçe karakterlerin bozulmaması için encoding='utf-8' verin. İkincisi XML bildirimi: xml_declaration=True her zaman bildirimi yazar, None ise yalnız kodlama ASCII, UTF-8 ya da Unicode dışındaysa yazar. Bir dosyaya kaydedeceğiniz belgede bildirimi açıkça istemek tutarlılık sağlar.
ET.indent okunaklı girinti ekler; ağaç üzerinde yerinde çalışır ve write'tan önce çağrılmalıdır. Dosyaya değil de metne ihtiyacınız varsa tostring işlevi aynı seçenekleri alır; encoding='unicode' verirseniz bayt yerine str döner.
Sıfırdan belge üretmek de aynı araçlarla olur: ET.Element ile kökü oluşturur, SubElement ile dallandırır, ElementTree(kok) ile ağaca sararak yazarsınız.
Büyük dosyalar, güvenilmeyen kaynaklar ve lxml
parse ve fromstring belgenin tamamını belleğe yükler. Çok büyük bir dosyada iterparse daha uygun olur: dosyayı okurken eleman bittikçe size verir. Python belgeleri belleğin ayrıştırma sırasında kendiliğinden boşalmadığını belirtir; işlediğiniz elemanı clear ile temizlemelisiniz:
for olay, eleman in ET.iterparse('buyuk.xml'): if eleman.tag == 'siparis': print(eleman.get('no')) eleman.clear()
İkinci konu güvenliktir. Kendi ürettiğiniz dosyayı okurken sorun yoktur, ancak dışarıdan gelen XML'i ayrıştırırken Python belgelerindeki XML güvenliği bölümünü okumalısınız. Belgeler, iç içe varlık tanımlarıyla az sayıda baytı gigabaytlarca metne genişleten saldırıları (billion laughs ve benzerleri) tanımlar. Hizmet reddi riski taşıyan bu yöntemler yüzünden güvenilmeyen XML'i doğrudan ayrıştırmayın.
lxml ise üçüncü taraf bir kütüphanedir. Eğitiminde, ElementTree arayüzünü kullandığını ve ek olarak yalnız kendisinde bulunan özellikler sunduğunu belirtir; bunlardan biri tam XPath ifadeleri çalıştıran xpath yöntemidir. Basit okuma ve yazma için standart kütüphane yeterlidir; karmaşık sorgular gerekiyorsa lxml'e geçmek mantıklı olur, kodunuzun büyük bölümü zaten aynı kalır.
Belgenin boyutu ve sorgu karmaşıklığı aracı belirler
- Dosya belleğe sığar, sorgular basitparse ve findallStandart kütüphane yeterlidir.
- Dosya çok büyükiterparse ve clearElemanları geldikçe işleyip temizleyin.
- Karmaşık XPath gerekiyorlxmlElementTree'ye benzer arayüz, ek olarak xpath yöntemi.
Sık yapılan hatalar
XML işlerinde hatalar neredeyse hep aynı yerden gelir. Bunları baştan bilmek hata ayıklama süresini kısaltır:
- find sonucuna doğrudan text eklemek; eleman yoksa program çöker
- Eleman metnini sayı sanmak; text her zaman str döner
- Elemanın varlığını if eleman: ile sınamak; çocuğu olmayan eleman yanlış sayılır
- Ad alanlı belgede önek vermeden aramak ve boş sonuç alıp dosyayı bozuk sanmak
- Yazarken kodlama belirtmemek ve Türkçe karakterleri kaybetmek
- Büyük dosyayı parse ile tek seferde yüklemek
Doğru alışkanlık her aramadan sonra eksik veriyi düşünmektir: eleman yoksa ne olacak, metin boşsa ne olacak, öznitelik hiç yazılmamışsa ne olacak?
Üç küçük alışkanlık çoğu XML hatasını önler
Bu beceriyi Mindset programlarında nerede çalışırsınız
Farklı biçimlerde gelen veriyi analize hazırlamak, bir veri analistinin günlük işidir. Data Science Bootcamp'ın Dosyalar ile Çalışmak ve Veri Edinimi ve Çıkartma başlıkları, dosyalardan ve kaynaklardan veri alma ihtiyacını Python ile çözmeye odaklanır.
Python ile Programlama Eğitimi ise Hatalar ve Exceptions ile Modüller ve Paketler konularını içerir; XML ayrıştırırken karşılaşılan eksik veri ve beklenmedik yapı sorunlarını bu temelle yönetirsiniz.
İlgili Mindset programı
Data Science Bootcamp
Sık sorulan sorular
parse ile fromstring arasındaki fark nedir?
parse bir dosya adı ya da dosya nesnesi alır ve ağaç (ElementTree) döndürür; kök elemana getroot ile ulaşırsınız. fromstring ise bellekteki XML metnini alır ve doğrudan kök elemanı döndürür. XML bir web yanıtından ya da veritabanı alanından geliyorsa fromstring, diskteki dosyadan geliyorsa parse kullanın.
Yazdığım XML dosyasında Türkçe karakterler neden bozuk?
write işlevinin varsayılan kodlaması US-ASCII'dir; Türkçe harfler bu kodlamada gösterilemez. Dosyayı kaydederken encoding='utf-8' verin ve xml_declaration=True ile bildirimi yazdırın. Okurken de dosya zaten UTF-8 ise parse kodlamayı bildirimden kendisi çözer.
find ile elemanı bulduktan sonra neden AttributeError alıyorum?
Aranan eleman bulunamadığında find None döndürür ve None üzerinde text çağrısı hata verir. Ya findtext kullanın ya da sonucu önce is not None ile sınayın. Eleman ad alanlı bir belgedeyse önek ve eşleme sözlüğü vermediğiniz için de bulunamamış olabilir.
XML ayrıştırmak için lxml kurmam gerekir mi?
Çoğu iş için gerekmez; standart kütüphanedeki ElementTree okuma, arama, değiştirme ve yazmayı karşılar. lxml, ElementTree'ye benzer bir arayüz sunar ve ek olarak tam XPath çalıştıran xpath yöntemini getirir. Karmaşık sorgularınız varsa ya da çok büyük belgelerle çalışıyorsanız değerlendirin.
Birkaç gigabaytlık XML dosyasını nasıl okurum?
parse tüm belgeyi belleğe alır; bu boyutta iterparse kullanın. Elemanlar bittikçe size verilir. İşlediğiniz elemanı clear ile boşaltmazsanız bellek yine de büyür, çünkü belgelere göre ayrıştırma sırasında bellek kendiliğinden serbest bırakılmaz.