Shanraq.org Shanraq.org
XML талдау: жолдың орнына ағаш, тегтің орнына `None` және аттың алдындағы мекенжай
IT

Python: деректен өз есебіңізге дейін 19-сабақ (барлығы 56)

XML талдау: жолдың орнына ағаш, тегтің орнына `None` және аттың алдындағы мекенжай

Python курсының он тоғызыншы сабағы. Ұлттық банктің сол жауабы, бірақ оны талдағыш оқиды: 39 валюта, бағам жолдың кесегі емес, тег аты бойынша. Плюс екі тұзақ: жоқ тег үшін `find` `None` қайтарады, ал аттар кеңістігі бар тег қысқа атымен мүлде табылмайды.

Не үшін керек

Өткен сабақта доллар бағамын былай алдық: <title>USD</title> дегенді тауып, бір кесекті, содан кейін тағы бір кесекті кестік. Бұл банк жағында бірінші өзгеріс болғанға дейін ғана жұмыс істейді.

Белгілеу — жол емес. Онда құрылым бар: тегтер бірінің ішінде бірі, әрқайсысының аты, кейбірінің атрибуттары бар. Талдағыш дәл сол құрылымды оқиды, сонда «доллар бағамы» таңбаның нөмірі емес, ағаштағы мекенжай болады.

Стандартты кітапхана мұны өзі істей алады: xml.etree.ElementTree модулі, ештеңе орнатудың қажеті жоқ.

Бірден тұтас

talda.py файлы. Іске қосу: ортадан python talda.py.

Міндеттісі — алғашқы екі блок: ағаш және керек валюталарды тег аты бойынша таңдау. Үшінші мен төртіншісі талдауды бәрінде кезекпен бұзатын екі тұзақты көрсетеді.

"""19-сабақ: XML талдау — бөтен белгілеуден алынған сандар.

Өткен сабақта доллар бағамын жол әдістерімен алдық: тегті тауып, кесектерге
бөлдік. Олай істеуге болмайды: бір бос орын өзгерсе, талдау бұзылады. Бүгін сол
жауапты тег дегеннің не екенін білетін талдағыш оқиды.
"""

import urllib.request
import xml.etree.ElementTree as ET

URL = "https://nationalbank.kz/rss/get_rates.cfm?fdate=15.01.2026"
WANTED = ("USD", "EUR", "RUB", "CNY")

request = urllib.request.Request(URL, headers={"User-Agent": "shanraq-course/1.0"})
with urllib.request.urlopen(request, timeout=30) as answer:
    charset = answer.headers.get_content_charset() or "utf-8"
    text = answer.read().decode(charset)

print("== жолдың орнына ағаш")
root = ET.fromstring(text)
print("түбір:", root.tag, "| баласы:", len(root))
print("күні:", root.findtext("date"))
print("валюта саны:", len(root.findall("item")))

print()
print("== тег аты бойынша төрт валюта")
for item in root.findall("item"):
    code = item.findtext("title")
    if code not in WANTED:
        continue
    rate = float(item.findtext("description"))
    quant = int(item.findtext("quant"))
    print(f"{code}: {rate:8.2f}{quant} бірлікке | {item.findtext('fullname')}")

print()
print("== белгілеуде жоқ нәрсе")
first = root.find("item")
print("бөтен тег бойынша find:", first.find("net-takogo-tega"))
print("әдепкісі бар findtext:", first.findtext("net-takogo-tega", "мұндай тег жоқ"))
try:
    print(first.find("net-takogo-tega").text)
except AttributeError as error:
    print("ал былай құлайды:", error)

print()
print("== аттар кеңістігі: алдында мекенжайы бар тег")
atom = ET.fromstring('<feed xmlns="http://www.w3.org/2005/Atom">'
                     '<entry><title>Бағам</title></entry></feed>')
print("түбір тегі:", atom.tag)
print("қысқа ат бойынша іздеу:", atom.find("entry"))
NS = {"a": "http://www.w3.org/2005/Atom"}
print("кеңістігімен іздеу:", atom.find("a:entry/a:title", NS).text)

Шығатыны:

== жолдың орнына ағаш
түбір: rates | баласы: 45
күні: 15.01.2026
валюта саны: 39

== тег аты бойынша төрт валюта
USD:   510.43 — 1 бірлікке | ДОЛЛАР США
EUR:   594.86 — 1 бірлікке | ЕВРО
CNY:    73.21 — 1 бірлікке | КИТАЙСКИЙ ЮАНЬ
RUB:     6.49 — 1 бірлікке | РОССИЙСКИЙ РУБЛЬ

== белгілеуде жоқ нәрсе
бөтен тег бойынша find: None
әдепкісі бар findtext: мұндай тег жоқ
ал былай құлайды: 'NoneType' object has no attribute 'text'

== аттар кеңістігі: алдында мекенжайы бар тег
түбір тегі: {http://www.w3.org/2005/Atom}feed
қысқа ат бойынша іздеу: None
кеңістігімен іздеу: Бағам

Талдау

Жол емес, ағаш

root = ET.fromstring(text)

fromstring белгілеу мәтінін алады да, түбірлік элементті қайтарады. Әрі қарай мәтінмен емес, ағашпен жұмыс істейді:

  • root.tag — тегтің аты;
  • len(root) — тікелей баласы нешеу;
  • root.find("item") — осы аты бар бірінші бала;
  • root.findall("item") — осындай балалардың бәрі;
  • root.findtext("date") — осындай бірінші баланың ішіндегі мәтін.

Шығудағы айырмаға назар аударыңыз: түбірдің баласы 45, ал item — 39. Қалған алтауы — generator, title, link және басқа бас бөлігі. findall тек сұралғанды алады; len бәрін санайды.

Елестетіп көріңіз. Кітаптың мазмұны. Үшінші тарауды табу үшін басынан бастап әріп санамайсыз — мазмұнға қарап, бетті ашасыз.

Мәндер әрқашан жол

rate = float(item.findtext("description"))
quant = int(item.findtext("quant"))

XML-де сан жоқ — онда тек мәтін бар. findtext "510.43" қайтарады, әрі ол жол болып тұрғанда оны қосуға да, шамасы бойынша салыстыруға да келмейді.

Бұл — JSON мен күндер туралы болған сол әңгіме: санға талдау кезінде айналдырады. Әрі сол жерде, талдау кезінде, n/a-мен, бос жолмен және нүктенің орнындағы үтірмен не істейтіні көрінеді.

Мұндағы quant әшекей емес: банк бағамды әрдайым бір бірлікке бере бермейді. Өзбек сомы — жүзге, иран риалы — мыңға. quant-қа бөлмей салыстыру жүз есе қателеседі, әрі үнсіз қателеседі.

find қате емес, None қайтарады

бөтен тег бойынша find: None
әдепкісі бар findtext: мұндай тег жоқ
ал былай құлайды: 'NoneType' object has no attribute 'text'

Тег болмаса, find None береді — әрі бұл жетінші сабақтағы сол жетіспейтін мән. .text деп сұрағанша ештеңе болмайды; сұрадыңыз — бағдарлама зиянсыз көрінетін жерде құлайды.

Осыдан ереже шығады: find(...).text емес, әдепкісі бар findtext алады. Екі әрекеттің орнына бір функция, әрі жетіспейтін мән туралы шешім құлағанға дейін емес, бірден қабылданады.

Аттар кеңістігі: алдында мекенжайы бар тег

түбір тегі: {http://www.w3.org/2005/Atom}feed
қысқа ат бойынша іздеу: None
кеңістігімен іздеу: Бағам

Міне, RSS, Atom немесе банк алмасуын алғаш талдағандардың бәрі сүрінетін тұзақ. Белгілеуде xmlns="..." болса, әр тегтің аты көрінгеннен ұзын: оның алдында жүйелі жақшада аттар кеңістігінің мекенжайы тұрады.

find("entry") аты entry болатын тегті іздейді — әрі таппайды, өйткені тегтің аты {http://www.w3.org/2005/Atom}entry. Бұл кезде қате де жоқ: тағы да None, тағы да үнсіздік.

Префикстер сөздігімен емделеді:

NS = {"a": "http://www.w3.org/2005/Atom"}
atom.find("a:entry/a:title", NS)

Ұлттық банктің жауабында аттар кеңістігі жоқ — сондықтан ол сабақта қысқа талданады. Бірақ бірінші бөтен фидтен-ақ мұны кездестіресіз.

Бөтен XML және қауіпсіздік

ElementTree не берілсе, соны талдайды, әрі әдейі құрастырылған файлмен оны көп жад жегізуге болады. Бақылаудағы дерек үшін — банк, статистика — бұл мәселе емес. Пайдаланушылар жіберетін файлдар үшін defusedxml алады: сол интерфейс, өшірілген қауіпті мүмкіндіктер.

Бұл туралы айту үндемегеннен адалырақ: бөтен белгілеуді талдау — бөтен кірістірумен жұмыс.

Сабақ картасы

Сабақ картасы: ағаш, findtext және аттар кеңістігі

Өз сөзіңізбен айтыңыз

Қарамай, дауыстап немесе қағазға жауап беріңіз. Жауаптары — сабақтың соңында.

  1. Нәтиже бірдей болса, ағашты талдау жол бойынша іздеуден немен жақсы?
  2. Жоқ тег үшін find не қайтарады, әрі мұның қаупі неде?
  3. xmlns бар белгілеуде find("entry") тегті неге таппайды?

Жаттығу

Тапсырмаға дейінгі үш қысқа қадам: болжау, толықтыру, жөндеу. Жауаптары — сабақтың соңында, бірақ алдымен өзіңіз жауап беріңіз.

1. Болжаңыз. Бұл бағдарлама не басып шығарады?

import xml.etree.ElementTree as ET

root = ET.fromstring("<rates><item><title>USD</title></item><item><title>EUR</title></item></rates>")
print(len(root.findall("item")), root.find("item").findtext("title"))

2. Бос орынды толтырыңыз. ... орнына мәнді санға айналдырыңыз.

import xml.etree.ElementTree as ET

root = ET.fromstring("<item><description>510.43</description></item>")
value = root.findtext("description")
print(type(value).__name__, ... * 2)

3. Жөндеңіз. Бұл жауапта change тегі жоқ, әрі бағдарлама құлайды. Ол бұл туралы сөзбен айтатындай етіңіз.

import xml.etree.ElementTree as ET

root = ET.fromstring("<item><title>USD</title></item>")
print(root.find("change").text)

Тапсырма

Міндетті. Ұлттық банктің сол бекітілген күнгі жауабын алып, оны талдағышпен талдаңыз:

URL = "https://nationalbank.kz/rss/get_rates.cfm?fdate=15.01.2026"
WANTED = ("USD", "RUB", "UZS")

Жауаптағы күнді және ондағы валюта санын басып шығарыңыз. Содан кейін үш валютаның әрқайсысы бойынша — бағамды берілген күйінде, ол неше бірлікке берілгенін және бір бірлігі қанша тұратынын (нүктеден кейін төрт таңба). Соңғы жолмен жауапта жоқ тегті сұраңыз — құлаудың орнына әдепкімен.

Күтілетін шығу:

күні: 15.01.2026 | жауаптағы валюта: 39
USD: 510.43 — 1 бірлікке → бір бірлігі 510.4300 теңге
RUB: 6.49 — 1 бірлікке → бір бірлігі 6.4900 теңге
UZS: 4.24 — 100 бірлікке → бір бірлігі 0.0424 теңге
жауапта автор жоқ: author тегі жоқ

Дайын болғаны: шығу жол-жолмен сәйкес келеді; белгілеу бойынша бірде-бір split жоқ — тек find, findall, findtext; бір бірліктің бағамы quant-қа бөлумен саналған; жоқ тег find-тан кейінгі None тексеруімен емес, әдепкімен өңделген.

Өз деректеріңізде. Өзіңіз оқитын кез келген RSS-фидті алыңыз — жаңалық, блог, подкаст. Оны сол тәсілмен талдап, алғашқы бес жазбаның тақырыбын басып шығарыңыз. Фидте xmlns болса, мұны бос нәтижеден білесіз — енді не істеу керегін де білесіз.

Қалауыңызша.

  • Кез келген элемент үшін item.attrib басып шығарып, ондағы нәрсеге қараңыз.
  • Жауаптан quant = 1000 болатын валютаны тауып, оның бір бірлікке бағамын санаңыз.
  • Сол жауапты өткен сабақтағы жол әдістерімен талдап, кодтың ұзындығын салыстырыңыз.

Жобада бұл қайда тұрады

Шолу екінші нағыз дереккөзді алады — валюта бағамын, — әрі оны сенімді алады: талдау енді банктің бос орынды жылжытуына тәуелді емес. Әрі қарай екі дереккөздің сандарын бір кестеге жинауға болады: жылдық инфляция және күндік бағам.

Қарыздар. Біз жауапты жадқа тұтас оқимыз — бір күн үшін бұл дұрыс, он жылдық тарих үшін енді дұрыс емес. Әрі quant-ты ескердік, ал бағам өзгермейтін демалыс пен мереке күндерін шолу әзірге ажыратпайды.

Жауаптар

Жауаптарды көрсету

Сұрақтарға

  1. Өйткені нәтиже тек бүгін бірдей. Жол бойынша талдау таңбалардың ретіне сүйенеді: бос орын өзгерсе, тегтердің орны ауысса, өріс қосылса — ол үнсіз басқа нәрсені ала бастайды. Талдағыш құрылымға сүйенеді, ал белгілеу үшін құрылым — уәде.
  2. None қайтарады. Қаупі — қате тег жоқ жерде емес, None-нан .text сұралған жерде шығады. Сондықтан әдепкісі бар findtext алады.
  3. Өйткені xmlns болғанда тегтің толық аты кеңістіктің мекенжайын қамтиды: {http://www.w3.org/2005/Atom}entry. Қысқа ат оған тең емес, әрі find адал түрде None дейді.

Жаттығуға

  1. 2 USD. findall екі элементті де тапты, find — біріншісін, ал findtext оның title-ының ішіндегі мәтінді алды.
2 USD
  1. float(value). findtext әрқашан жол береді, ал жолды екіге қалағандай көбейтуге болмайды: "510.43" * 2 екі еселенген санды емес, екі еселенген жолды берер еді.
import xml.etree.ElementTree as ET

root = ET.fromstring("<item><description>510.43</description></item>")
value = root.findtext("description")
print(type(value).__name__, float(value) * 2)
str 1020.86
  1. find None қайтарды, ал None-да .text жоқ. Әдепкі сұраққа бірден жауап береді:
import xml.etree.ElementTree as ET

root = ET.fromstring("<item><title>USD</title></item>")
print(root.findtext("change", "өзгеріс жоқ"))
өзгеріс жоқ

Дереккөздер

Мәтінде қате не теру қатесі кездессе, бізге айтыңыз

Тапсырманы тексеру

Алдымен VS Code-та шешіп, іске қосыңыз — редактор қатені сол жерде көрсетеді. Дайын шешімді осында қойыңыз. Тексеретін — модель: ол қатені атап көрсетеді, бірақ дайын жауапты бермейді.

Тексеру үшін кіру керек. Кіру

Пікірлер (0)

Әзірге пікір жоқ. Бірінші болыңыз.