Doslej smo podatke vedno brali iz datotek in te so bile skoraj vedno tabelarične - podatki so bili po vrsticah in vrednosti v stolpcih, ločenih z vejicami ali -- izjemoma in običajno samo za vajo -- s čem drugim.
Ni vedno tako. Podatki so lahko zapisani tudi v drugačnih oblikah. Včasih brez posebnega razloga, pogosto pa zato, ker bodo podatki strukturirani: v csv lahko zapišemo le tabele -- vrstice in stolpce -- ne pa kakih bolj zapletenih, recimo hierarhičnih podatkov. Seznam kontaktov je seznam oseb, vsaka oseba lahko vsebuje več naslovov, telefonskih številk in elektronskih naslovov, vsi so dodatno okarakterizirani s tem ali so službeni, domači ali kaki drugi, dodane so lahko različne opombe in priponke... Take podatke bi bilo zoprno zapisovati v tabelarično obliko. Zato uporabljamo druge oblike. Na srečo jih je le nekaj in za branje vseh je Python odlično opremljen.
Ker bomo podatke pogosto dobili na spletu, ne v že pripravljenih datotekah, se za začetek naučimo prebrati podatke s spletne strani. Točneje, s spletnega vira, ki v resnici ni spletna stran namenjena temu, da bi jo brali ljudje, temveč temu, da bi z nje računalniki pobirali podatke. Za začetek bomo prebrali tečajnico z NLB, ki jo dobimo na naslovu https://www.nlb.si/services/tecajnica/?type=companies&format=csv.
Skopirajmo URL v brskalnik, poglejmo.
Num Date____ Bank Type_______ NCu CCu Buy___________ Sell__________
001 20231227 NLB_ companies__ 840 USD 0001,111300000 0001,095000000
001 20231227 NLB_ companies__ 826 GBP 0000,873800000 0000,860800000
001 20231227 NLB_ companies__ 756 CHF 0000,951700000 0000,933700000
001 20231227 NLB_ companies__ 348 HUF 0383,700000000 0379,700000000
001 20231227 NLB_ companies__ 941 RSD 0119,470000000 0114,870000000
001 20231227 NLB_ companies__ 977 BAM 0001,968000000 0001,947000000
001 20231227 NLB_ companies__ 807 MKD 0062,300000000 0060,500000000
001 20231227 NLB_ companies__ 203 CZK 0024,760000000 0024,420000000
001 20231227 NLB_ companies__ 985 PLN 0004,354000000 0004,314000000
001 20231227 NLB_ companies__ 975 BGN 0001,967000000 0001,937000000
001 20231227 NLB_ companies__ 578 NOK 0011,292000000 0011,152000000
001 20231227 NLB_ companies__ 752 SEK 0011,112000000 0010,972000000
001 20231227 NLB_ companies__ 208 DKK 0007,504900000 0007,404900000
001 20231227 NLB_ companies__ 036 AUD 0001,624200000 0001,610200000
001 20231227 NLB_ companies__ 124 CAD 0001,463400000 0001,449400000
001 20231227 NLB_ companies__ 392 JPY 0158,140000000 0156,740000000
001 20231227 NLB_ companies__ 946 RON 0005,009200000 0004,929200000
001 20231227 NLB_ companies__ 710 ZAR 0020,701400000 0020,301400000
001 20231227 NLB_ companies__ 344 HKD 0008,724100000 0008,524100000
001 20231227 NLB_ companies__ 949 TRY 0032,628500000 0032,208500000
001 20231227 NLB_ companies__ 484 MXN 0019,047700000 0018,447700000
001 20231227 NLB_ companies__ 554 NZD 0001,760100000 0001,728100000
001 20231227 NLB_ companies__ 376 ILS 0004,041500000 0003,951500000
001 20231227 NLB_ companies__ 784 AED 0004,076000000 0004,036000000
To stran sem izbral prav zato, ker nam da tečajnico v obliki, ki jo že obvladamo. "Uradnejša" tečajnica Banke Slovenije je v XML, ki se ga moramo še naučiti brati.
(Mimogrede lahko opazite, da se URL konča z format=csv
.
Očitno bi tam lahko pisalo tudi kaj drugega, recimo
format=xml
. Več o tem, kako lahko spreminjamo URL, da
dobite različne podatke, izvemo na https://www.nlb.si/avtomatiziran-prevzem-tecajnic. NLB
to dokumentira; obliko URL bomo morali sicer pogosto ugibati sami ali pa
jo odkriti s pomočjo primernih orodij.)
Zdaj pa je naša naloga, da teh podatkov ne dobimo v brskalnik temveč
jih preberemo s programom v Pythonu. Uporabiti nam je modul
urllib.request
; ta vsebuje funkcijo urlopen
,
ki se vede nekoliko podobno kot open
, s katerim odpiramo
datoteke: tako kot open
vrne objekt, ki predstavlja
datoteko, pripravljeno za branje, tako urlopen
vrne nekaj,
kar je pripravljeno za branje.
from urllib.request import urlopen
= urlopen("https://www.nlb.si/services/tecajnica/?type=companies&format=csv") f
Zdaj pa kakšen bralnik iz csv
?
import csv
for vrstica in csv.DictReader(f):
print(vrstica)
---------------------------------------------------------------------------
Error Traceback (most recent call last)
Cell In[2], line 3
1 import csv
----> 3 for vrstica in csv.DictReader(f):
4 print(vrstica)
File ~/miniforge3/envs/prog/lib/python3.12/csv.py:115, in DictReader.__next__(self)
112 def __next__(self):
113 if self.line_num == 0:
114 # Used only for its side effect.
--> 115 self.fieldnames
116 row = next(self.reader)
117 self.line_num = self.reader.line_num
File ~/miniforge3/envs/prog/lib/python3.12/csv.py:102, in DictReader.fieldnames(self)
100 if self._fieldnames is None:
101 try:
--> 102 self._fieldnames = next(self.reader)
103 except StopIteration:
104 pass
Error: iterator should return strings, not bytes (the file should be opened in text mode)
Žal ne. Če z open
odpremo datoteko (in ne zahtevamo, da
jo odpre kot ne-besedilno, binarno datoteko), bomo ob branju vrstic (ali
celotne datoteke) dobili nize. Funkcija urlopen
vrne
objekt, ki ob branju ne vrača nizov temveč bajte.
f.readline()
b'001 20250108 NLB_ companies__ 840 USD 0001,042500000 0001,026200000 \r\n'
Vidimo b
na začetku, pred narekovaji? Ta pove, da to,
kar je med narekovaji ni zaporedje znakov (niz) temveč zaporedje števil.
Števila, ki jih je mogoče pokazati kot znake ASCII, pokaže kot znake,
ostala pa zamenja z nečim čudnim (česar tu hvalabogu ne vidimo).
Razliko med nizi in bajti vidimo, če poskušamo dostopati do posamičnih elementov.
= f.readline() s
s
b'001 20250108 NLB_ companies__ 826 GBP 0000,835700000 0000,822700000 \r\n'
14] s[
76
Če bi bil s
niz, bi dobili štirinajsti znak "N". Tako pa
dobimo štirinajsto številko, 76 (ki v ASCII slučajno pomeni veliki N).
Razliko vidimo tudi pri pretvarjanju v seznam.
print(list(s))
[48, 48, 49, 32, 50, 48, 50, 53, 48, 49, 48, 56, 32, 78, 76, 66, 95, 32, 99, 111, 109, 112, 97, 110, 105, 101, 115, 95, 95, 32, 56, 50, 54, 32, 71, 66, 80, 32, 48, 48, 48, 48, 44, 56, 51, 53, 55, 48, 48, 48, 48, 48, 32, 48, 48, 48, 48, 44, 56, 50, 50, 55, 48, 48, 48, 48, 48, 32, 13, 10]
Če bi bil s
niz, bi dobili seznam črk, tako pa seznam
števil.
Zakaj? Tako datoteke kot podatki s spleta so zgolj številke. Pri
branju datotek Python predpostavi, da številke predstavljajo znake,
zapisane v privzetem kodiranju za operacijski sistem (UTF-8 v spodobnih
sistemih (Linux, macOS), razna lokalna kodiranja v nespodobnih) oziroma
kodiranju, ki ga funkciji open
podamo kot dodatni argument
encoding
. Vse prebrane številke zato dekodira v
nize.
Pri branju s spleta Python (oziroma objekt, ki ga vrne
urlopen
) ne dekodira ničesar. Vrne nam kar številke in
dekodirati jih moramo tako, da sami pokličemo metodo
decode
.
"ascii") s.decode(
'001 20250108 NLB_ companies__ 826 GBP 0000,835700000 0000,822700000 \r\n'
Tule smo dekodirali kar v ASCII, saj v imenih valut (vsaj v tem seznamu) ni šumnikov. Če bi brali kakšne druge podatke, bi morda pisali
"utf-8") s.decode(
'001 20250108 NLB_ companies__ 826 GBP 0000,835700000 0000,822700000 \r\n'
ali celo
"utf-16") s.decode(
'〰‱〲㔲\u3130㠰丠䉌\u205f潣灭湡敩彳\u205f㈸‶䉇⁐〰〰㠬㔳〷〰〰〠〰ⰰ㈸㜲〰〰‰\u0a0d'
Karkoli že.
Tečajnico bomo prebrali kar brez csv
, saj ne bo tako
zapletena.
= {}
tecajnica
= urlopen("https://www.nlb.si/services/tecajnica/?type=companies&format=csv")
t # preskočimo prvo vrstico, ki vsebuje le imena stolpcev
t.readline()
for vrstica in t:
= vrstica.decode("ascii").split() # Pred klicem split spremenimo bajte v niz
vrstica = vrstica[5] # stolpec z imenom valute
valuta = vrstica[6] # stolpec z vrednostjo
cena = float(cena.replace(",", "."))
tecajnica[valuta]
tecajnica
{'USD': 1.0425,
'GBP': 0.8357,
'CHF': 0.9503,
'HUF': 417.392,
'RSD': 119.369,
'BAM': 1.968,
'MKD': 62.3,
'CZK': 25.2982,
'PLN': 4.2837,
'BGN': 1.967,
'NOK': 11.8014,
'SEK': 11.5705,
'DKK': 7.5108,
'AUD': 1.6692,
'CAD': 1.4927,
'JPY': 164.44,
'RON': 5.0115,
'ZAR': 19.5802,
'HKD': 8.1512,
'TRY': 36.8289,
'MXN': 21.363,
'NZD': 1.8529,
'ILS': 3.826,
'AED': 3.8233}
Ker NLB objavlja strojno berljive podatke tako, da uporablja
decimalno vejico namesto pike, moramo pred klicem float
še
z replace(",", ".")
spraviti podatek v strojno berljivo
obliko.
Eden pogostih formatov, v katerih dobimo podatke s spleta, je JSON. Razlog za njegovo pogostost razkriva njegovo ime: JSON je JavaScript Object Notation. Ker so spletne strani navadno sprogramirane v JavaScriptu, si podatke najraje pošiljajo v Javascriptu lastnem formatu.
Za branje in pisanje v obliki JSON ima Python modul
json
:
json.dump(obj, f)
v datoteko f
shrani
vrednost obj
v formatu json,json.load(f)
prebere (naslednji) objekt iz
f
,json.dumps(obj)
vrne niz z zapisom objekta
obj
v formatu json in,json.loads(s)
iz niza s razbere objekt.Poglejmo, kako bi bila v njem videti naša tečajnica.
import json
json.dumps(tecajnica)
'{"USD": 1.0425, "GBP": 0.8357, "CHF": 0.9503, "HUF": 417.392, "RSD": 119.369, "BAM": 1.968, "MKD": 62.3, "CZK": 25.2982, "PLN": 4.2837, "BGN": 1.967, "NOK": 11.8014, "SEK": 11.5705, "DKK": 7.5108, "AUD": 1.6692, "CAD": 1.4927, "JPY": 164.44, "RON": 5.0115, "ZAR": 19.5802, "HKD": 8.1512, "TRY": 36.8289, "MXN": 21.363, "NZD": 1.8529, "ILS": 3.826, "AED": 3.8233}'
Da. Če zapišemo Pythonov slovar v obliki json, je videti natančno kot Pythonov slovar. :)
Pa seznam?
"Ana", "Berta", "Cilka"]) json.dumps([
'["Ana", "Berta", "Cilka"]'
Skratka. Če shranjujemo Pythonove objekte v formatu json, jih zapisuje natančno tako, kot jih zapisujemo v Pythonu. Skoraj.
"Ana", True, False, None]) json.dumps([
'["Ana", true, false, null]'
Zapisuje jih po Javascriptovsko: True
in
False
sta napisana z malo in namesto None
piše
null
.
Terke postanejo seznami.
1, 2, 3)) json.dumps((
'[1, 2, 3]'
Za množice ne ve.
"Ana", "Berta"}) json.dumps({
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[16], line 1
----> 1 json.dumps({"Ana", "Berta"})
File ~/miniforge3/envs/prog/lib/python3.12/json/__init__.py:231, in dumps(obj, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separators, default, sort_keys, **kw)
226 # cached encoder
227 if (not skipkeys and ensure_ascii and
228 check_circular and allow_nan and
229 cls is None and indent is None and separators is None and
230 default is None and not sort_keys and not kw):
--> 231 return _default_encoder.encode(obj)
232 if cls is None:
233 cls = JSONEncoder
File ~/miniforge3/envs/prog/lib/python3.12/json/encoder.py:200, in JSONEncoder.encode(self, o)
196 return encode_basestring(o)
197 # This doesn't pass the iterator directly to ''.join() because the
198 # exceptions aren't as detailed. The list call should be roughly
199 # equivalent to the PySequence_Fast that ''.join() would do.
--> 200 chunks = self.iterencode(o, _one_shot=True)
201 if not isinstance(chunks, (list, tuple)):
202 chunks = list(chunks)
File ~/miniforge3/envs/prog/lib/python3.12/json/encoder.py:258, in JSONEncoder.iterencode(self, o, _one_shot)
253 else:
254 _iterencode = _make_iterencode(
255 markers, self.default, _encoder, self.indent, floatstr,
256 self.key_separator, self.item_separator, self.sort_keys,
257 self.skipkeys, _one_shot)
--> 258 return _iterencode(o, 0)
File ~/miniforge3/envs/prog/lib/python3.12/json/encoder.py:180, in JSONEncoder.default(self, o)
161 def default(self, o):
162 """Implement this method in a subclass such that it returns
163 a serializable object for ``o``, or calls the base implementation
164 (to raise a ``TypeError``).
(...)
178
179 """
--> 180 raise TypeError(f'Object of type {o.__class__.__name__} '
181 f'is not JSON serializable')
TypeError: Object of type set is not JSON serializable
Terke in množice nas ne bodo posebej bolele: v Pythonu si navadno ne bomo shranjevali v JSON (razen, kadar bomo to počeli zato, da bomo podatke kam poslali, naslovnik pa bo tipično JavaScript, ki tako ali tako ne bo navdušen nad množicami in terkami v Pythonovem pomenu teh dveh besed). Podatke v JSON bomo brali in tam bo pač, kar bodo tja zapisali. Prebrati bomo znali vse.
Kljub temu: zapišimo tečajnico v JSON. Dodajmo tudi primerno motivacijo: če bi napisali program za pretvarjanje valut, bi ta vsakič, ko ga poženemo, prebral podatke z NLB. Morda bi jih bilo primerneje shraniti v datoteko. Program bi potem deloval tako, da bi podatke s spleta pobral le prvič, potem pa bi jih bral kar iz datoteke.
import os
from urllib.request import urlopen
import json
if os.path.exists("tecajnica.json"):
= json.load(open("tecajnica.json"))
tecajnica else:
= {}
tecajnica = urlopen("https://www.nlb.si/services/tecajnica/?type=companies&format=csv")
t
t.readline()for vrstica in t:
= vrstica.decode("ascii").split()
vrstica = vrstica[5]
valuta = vrstica[6]
cena = float(cena.replace(",", "."))
tecajnica[valuta]
open("tecajnica.json", "w")) json.dump(tecajnica,
Zdaj pomislimo še, da se tečajnica pravzaprav dnevno spreminja. Ko preberemo datoteko, moramo preveriti, ali vsebuje današnje podatke. To najpreprosteje storimo tako, da vanjo zapišemo še datum.
(Da nam pravkar zapisana datoteka ne bi nagajala v naslednjem koraku, jo tule pobrišimo.)
if os.path.exists("tecajnica.json"):
"tecajnica.json") os.remove(
import os
from urllib.request import urlopen
import json
from datetime import datetime
= datetime.today().strftime("%Y-%m-%d")
danes
= {}
tecajnica if os.path.exists("tecajnica.json"):
print("Tečajnico berem iz datoteke.")
= json.load(open("tecajnica.json"))
datum, tecajnica if datum != today:
= {}
tecajnica
if not tecajnica:
print("Tečajnico berem s spleta.")
= urlopen("https://www.nlb.si/services/tecajnica/?type=companies&format=csv")
t
t.readline()for vrstica in t:
= vrstica.decode("ascii").split()
vrstica = vrstica[5]
valuta = vrstica[6]
cena = float(cena.replace(",", "."))
tecajnica[valuta]
open("tecajnica.json", "w")) json.dump([danes, tecajnica],
Tečajnico berem s spleta.
Ko prvič poženemo gornjo celico, izpiše, da bere s spleta. V nadaljnjih poganjanjih pravi, da bere iz datoteke. Če počakamo en dan (ali pa poženemo eno celico višje, tisto, ki briše datoteko), pa bo spet brala s spleta.
Datoteka tecajnica.json pa je videti tako.
Pickle ni standarden format. Podatkov, zapisanih v njem ne boste našli na spletu. Pač pa je uporaben, če želimo na hiter in preprost način shraniti v datoteko poljuben objekt (ali več objektov) v Pythonu.
Uporabljamo ga podobno kot json.
import pickle
open("tecajnica.pickle", "wb")) pickle.dump(tecajnica,
= pickle.load(open("tecajnica.pickle", "rb"))
t
print(t)
{'USD': 1.0425, 'GBP': 0.8357, 'CHF': 0.9503, 'HUF': 417.392, 'RSD': 119.369, 'BAM': 1.968, 'MKD': 62.3, 'CZK': 25.2982, 'PLN': 4.2837, 'BGN': 1.967, 'NOK': 11.8014, 'SEK': 11.5705, 'DKK': 7.5108, 'AUD': 1.6692, 'CAD': 1.4927, 'JPY': 164.44, 'RON': 5.0115, 'ZAR': 19.5802, 'HKD': 8.1512, 'TRY': 36.8289, 'MXN': 21.363, 'NZD': 1.8529, 'ILS': 3.826, 'AED': 3.8233}
Pickle ne zapisuje v besedilno datoteko, zato mu ne ponudimo datoteke
odprte z open(ime)
temveč open(ime, "wb")
oziroma, ko beremo, open(ime, "rb")
. Druga črka, "b", pove,
da gre za binarno datoteko.
Pickle zna zapisati vse. (Recimo. Možno je definirati nov podatkovni tip, ki se ne razume s picklom, vendar se je za to potrebno skoraj potruditi.) Problem pickla je v tem, da to obliko zapisa pozna le Python in, še hujše, se zapis spreminja in novejša verzija Pythona lahko sestavi novo obliko, ki ni berljiva v starejših.
Če si želimo nekaj shraniti le za to, da bomo to kasneje prebrali - sami, zase - je pickle najpreprostejši.
JSON je namenjen prenašanju podatkov. Podatki, ki so javno objavljeni z namenom, da bi jih brali z računalniki, pa bodo najpogosteje v formatu XML.
Tistim, ki so že kdaj videli HTML, bo XML, Extensible markup language, videti znano. Začnimo z ročno sestavljenim XMLjem, ki bi ga lahko uporabljali za opisovanje podatkov o kontaktih. V zapiskih jih najdemo v datoteki clovek.xml.
Kot vidimo, je XML sestavljen iz hierarhije elementov. Datoteka ima
vedno en osnovni, korenski element - tule je to oseba
. Tega
"odpremo" na začetku, z <oseba>
in zapremo na koncu,
z </oseba>
. Vmes so drugi elementi, recimo
ime
(začeto z <ime>
in končano z
</ime>
), pa priimek
,
starost
in tako naprej. Znotraj elementa
kontakti
se, če se dogovorimo tako, nahajajo elementi vrste
telefon
in mail
; vsak od njiju se lahko ponovi
tudi večkrat.
Element telefon
ima lahko atribut tip
,
mail
pa ima lahko atribut uraden
.
Kako je potrebno reč oblikovati, vidimo iz primera. O XML bi se dalo reči še veliko, vendar se tu, pri tem predmetu, omejimo le na osnovno idejo. Detajle si bo ta, ki jih potrebuje, že poiskal drugje.
Knjižnice za branje XML delujejo na dva načina - to ne velja le za Python temveč tudi v splošnem. Prvi tip deluje tako, da prebira datoteko in nas obvešča o elementih, na katere naleti. Tak način branja zasede manj pomnilnika, vendar ga je zahtevneje uporabljati. Tu bomo pogledali preprostejšega, ki deluje tako, da celotno datoteko prebere v drevesno strukturo in nam nato omogoča iskanje po njej.
from xml.dom import minidom
= minidom.parse(open("xml/clovek-stisnjen.xml"))
kontakt
kontakt
<xml.dom.minidom.Document at 0x1068cd130>
Iz razlogov, ki bodo pojasnjeni kasneje, smo namesto datoteke clovek.xml brali clovek-stisnjen.xml, ki je enaka clovek.xml, le da smo vse stlačili v eno vrstico in pobrisali presledke med elementi.
Spremenljivka kontakt
zdaj vsebuje prebrani dokument.
Ima kup lastnosti in metod, nas bo zanimala le ena:
getElementsByTagName
. Takole, recimo, dobimo vse elemente
telefon
:
"telefon") kontakt.getElementsByTagName(
[<DOM Element: telefon at 0x106a32690>, <DOM Element: telefon at 0x106a32720>]
Poigrajmo se s prvim od njih.
= kontakt.getElementsByTagName("telefon")[0] el
Element el
ima brate. Prvi je
nextSibling
.
= el.nextSibling
brat brat
<DOM Element: telefon at 0x106a32720>
Tudi ta ima brata.
brat.nextSibling
<DOM Element: mail at 0x106a327b0>
Brat prvega telefona je drugi telefon. Brat drugega telefona je prvi mail. In tako naprej.
Element ima tudi starša.
= el.parentNode
oce oce
<DOM Element: kontakti at 0x106a32600>
Oče telefona je Edison. Oče elementa telefon
pa je
kontakti
.
Oče ima otroke - to so pač "otroci" elementa kontakti
-
telefoni in maili.
oce.childNodes
[<DOM Element: telefon at 0x106a32690>,
<DOM Element: telefon at 0x106a32720>,
<DOM Element: mail at 0x106a327b0>,
<DOM Element: mail at 0x106a32840>,
<DOM Element: mail at 0x106a328d0>]
Metode getElementsByTagName
nima le dokument temveč vsak
element. Tako lahko dobimo maile znotraj oce
.
"mail") oce.getElementsByTagName(
[<DOM Element: mail at 0x106a327b0>,
<DOM Element: mail at 0x106a32840>,
<DOM Element: mail at 0x106a328d0>]
Tudi element ima otroke. Pravzaprav le enega, zato ne bomo brskali po
childNodes
temveč bomo vzeli kar
firstChild
.
el.firstChild
<DOM Text node "'+386 1 234'...">
Element, ki je otrok telefon
-a je
Text Node
. Ta nima otrok, pač pa ima vrednost,
nodeValue
.
el.firstChild.nodeValue
'+386 1 234 5678'
Aha, še atributi.
"tip") el.getAttribute(
'domaci'
To je to. Povzemimo vse, kar bomo potrebovali.
el.getElementsByTagName(name)
vrne elemente z imenom
name
znotraj el
.el.getAttribute(name)
vrne vrednost atributa
name
.el.childNodes
so otroci elementa.el.firstChild
je prvi otrok.el.nextSibling
je naslednji brat.el.parentNode
je oče elementa.el.nodeValue
je vrednost znotraj elementa.Zdaj znamo prebrati večino XML-jev, na katere bomo naleteli.
Če je datoteka XML oblikovana tako, da jo lahko beremo tudi smrtniki,
ne le računalniki, vsebuje presledke in znake za novo vrstico. Te vidimo
kot dodatne elemente vrste Text Node. Načelno nas ne bi (in ne bodo)
motili, le nextSibling
telefona ali maila bi bil Text Node,
pa tudi v vseh seznamih otrok bi se pojavljali in nam tako kvarili
preprostost primera. Samo to.
Banka Slovenije na strani https://www.bsi.si/_data/tecajnice/dtecbs.xml objavlja zadnje tečaje v obliki XML.
DtecBS xmlns="http://www.bsi.si" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.bsi.si http://www.bsi.si/_data/tecajnice/DTecBS.xsd">
<tecajnica datum="2023-12-22">
<tecaj oznaka="USD" sifra="840">1.1023</tecaj>
<tecaj oznaka="JPY" sifra="392">156.66</tecaj>
<tecaj oznaka="BGN" sifra="975">1.9558</tecaj>
<tecaj oznaka="CZK" sifra="203">24.589</tecaj>
<tecaj oznaka="DKK" sifra="208">7.4560</tecaj>
<tecaj oznaka="GBP" sifra="826">0.86660</tecaj>
<tecaj oznaka="HUF" sifra="348">381.93</tecaj>
<tecaj oznaka="PLN" sifra="985">4.3420</tecaj>
<tecaj oznaka="RON" sifra="946">4.9708</tecaj>
<tecaj oznaka="SEK" sifra="752">11.0556</tecaj>
<tecaj oznaka="ISK" sifra="352">150.50</tecaj>
<tecaj oznaka="CHF" sifra="756">0.9417</tecaj>
<tecaj oznaka="NOK" sifra="578">11.2705</tecaj>
<tecaj oznaka="TRY" sifra="949">32.2044</tecaj>
<tecaj oznaka="AUD" sifra="036">1.6197</tecaj>
<tecaj oznaka="BRL" sifra="986">5.3624</tecaj>
<tecaj oznaka="CAD" sifra="124">1.4639</tecaj>
<tecaj oznaka="CNY" sifra="156">7.8640</tecaj>
<tecaj oznaka="HKD" sifra="344">8.6105</tecaj>
<tecaj oznaka="IDR" sifra="360">17029.65</tecaj>
<tecaj oznaka="ILS" sifra="376">3.9764</tecaj>
<tecaj oznaka="INR" sifra="356">91.6280</tecaj>
<tecaj oznaka="KRW" sifra="410">1430.05</tecaj>
<tecaj oznaka="MXN" sifra="484">18.6955</tecaj>
<tecaj oznaka="MYR" sifra="458">5.1059</tecaj>
<tecaj oznaka="NZD" sifra="554">1.7505</tecaj>
<tecaj oznaka="PHP" sifra="608">61.067</tecaj>
<tecaj oznaka="SGD" sifra="702">1.4593</tecaj>
<tecaj oznaka="THB" sifra="764">38.084</tecaj>
<tecaj oznaka="ZAR" sifra="710">20.3070</tecaj>
<tecajnica>
</DtecBS> </
Struktura je očitna. Preberimo!
Te zapiske sem pripravil v letu 2023/24. Banka
Slovenije je medtem prijazno spremenila delovanje strežnika (ki je bilo
predtem dolga leta enako in nespremenjeno). Po novem
urlopen("http://www.bsi.si/_data/tecajnice/dtecbs.xml")
ne
deluje več - vrne rezultat "403 Forbidden". Pogosto to pomeni, da hočejo
poznati ime "brskalnika". Mirno se lahko zlažemo, da uporabljamo Chrome
na Window 10. Za to je potrebno nastaviti "header" zahtevka, takole:
from urllib.request import Request, urlopen
= "http://www.bsi.si/_data/tecajnice/dtecbs.xml"
url = {
headers "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}
= Request(url, headers=headers)
request = urlopen(request) response
Funkciji urlopen
smo doslej podajali niz z URL-jem.
Poleg tega sprejme tudi obsežnejšo reč Request
. Tega
sestavimo tako, Request
-u podamo URL in informacije, ki jih
želimo poslati skupaj z zahtevkom, na primer ime brskalnika
("User-Agent"
).
Primer s tem postane bolj zapleten: izvirni primer je pač uporabil
urlopen("https:// in tako naprej)
, zdaj pa je potrebno iti
malo globlje v protokol http. Vseeno naj ostane v zapiskih; navsezadnje
je tudi ta komplikacija nekaj, s čemer se srečujemo v resničnem svetu
pobiranja podatkov v spleta.
from xml.dom import minidom
= minidom.parse(response)
tecaji
= {}
tecajnica for tecaj in tecaji.getElementsByTagName("tecaj"):
"oznaka")] = float(tecaj.firstChild.nodeValue) tecajnica[tecaj.getAttribute(
Kar preprosto, ni?
Na naslovu https://www.bsi.si/_data/tecajnice/dtecbs-l.xml najdemo tečaje od 1. januarja 2007. Ker se ta XML kar dolgo prenaša, ga imamo že shranjenega ob zapiskih, tako da ga bomo prebrali kar iz datoteke.
Datoteka je strukturirana tako.
<?xml version="1.0"?><DtecBS xmlns="http://www.bsi.si" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.bsi.si http://www.bsi.si/_data/tecajnice/DTecBS-l.xsd">
tecajnica datum="2007-01-01">
<tecaj oznaka="USD" sifra="840">1.3170</tecaj>
<tecaj oznaka="JPY" sifra="392">156.93</tecaj>
<tecaj oznaka="BGN" sifra="975">1.9558</tecaj>
<
...
Enako torej, kot prejšnja, le da so tečaji za posamezni dan zaprti še
v tecajnica
, ki kot atribut vsebuje datum.
Preberimo, recimo, tečaj dolarja. Brali bomo v dva seznama: en bo
vseboval datume, drugi tečaj. Datumi ne bodo zapisani z dnevi, leti in
tako naprej, temveč s številom dni, ki so minili od 1. januarja 2007. Za
to bomo uporabili datetime
; ta ima metodo
fromisoformat
, ki zna prebrati datum v obliki, kot je
zapisan v tej datoteki. Od datuma bomo odšteli 1. januar 2007; rezultat
bo nek podatkovni tip timedelta
, ki ima atribute
days
. Ta vsebuje razliko v dnevih.
Gremo!
from datetime import datetime
from xml.dom import minidom
= datetime(2007, 1, 1)
zacetek = []
datumi = []
vrednosti
= minidom.parse(open("xml/dtecbs-l.xml"))
dokument for tecajnica in dokument.getElementsByTagName("tecajnica"):
= datetime.fromisoformat(tecajnica.getAttribute("datum"))
datum for tecaj in tecajnica.getElementsByTagName("tecaj"):
if tecaj.getAttribute("oznaka") == "USD":
- zacetek).days)
datumi.append((datum float(tecaj.firstChild.nodeValue))
vrednosti.append(break
In kako mu kaj gre, dolarju? Takole.
import matplotlib.pyplot as plt
plt.plot(datumi, vrednosti)
[<matplotlib.lines.Line2D at 0x12a702e40>]
Če znamo brati XML, znamo prebrati skoraj vse.
Ste že kdaj načrtovali kakšno pot in dobili datoteko .gpx? Ali pa opravili kakšno pot in jo shranili na Stravi ali čem podobnem? Tudi to lahko izvozite v GPX. GPX je XML v določeni standardizirani obliki.
Takole vam Strava shrani vaš tek. Vsebuje podatke o času lokaciji in nadmorski višini, zraven pa tudi vse, kar beleži vaša ura (ali karkoli že, s čimer med tekom in po njem hranite svoj ego).
<?xml version="1.0" encoding="UTF-8"?>
gpx xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd" creator="StravaGPX" version="1.1" xmlns="http://www.topografix.com/GPX/1/1" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3">
<metadata>
<time>2024-12-16T13:32:08Z</time>
<metadata>
</trk>
<name>Bajer</name>
<type>running</type>
<trkseg>
<trkpt lat="46.0505810" lon="14.4691250">
<ele>298.4</ele>
<time>2024-12-16T13:32:08Z</time>
<extensions>
<power>0</power>
<gpxtpx:TrackPointExtension>
<gpxtpx:atemp>28</gpxtpx:atemp>
<gpxtpx:hr>96</gpxtpx:hr>
<gpxtpx:cad>0</gpxtpx:cad>
<gpxtpx:TrackPointExtension>
</extensions>
</trkpt>
</trkpt lat="46.0505810" lon="14.4691250">
<ele>298.2</ele>
<time>2024-12-16T13:32:09Z</time>
<extensions>
<power>0</power>
<gpxtpx:TrackPointExtension>
<gpxtpx:atemp>28</gpxtpx:atemp>
<gpxtpx:hr>95</gpxtpx:hr>
<gpxtpx:cad>0</gpxtpx:cad>
<gpxtpx:TrackPointExtension>
</extensions>
</trkpt> </
... in tako naprej
Datoteke svg, v katere shranjujemo slike v vektorski obliki, so XML.
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
< viewBox="0 0 595.28 841.89" style="enable-background:new 0 0 595.28 841.89;" xml:space="preserve">
style type="text/css">
<
.st0{fill:#FF0001;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
.st1{fill:none;stroke:#018837;stroke-width:10;stroke-linecap:round;stroke-miterlimit:10;}style>
</ellipse class="st0" cx="232.21" cy="251.29" rx="56.79" ry="20.71"/>
<path class="st1" d="M59.71,201.29l70,191.43c117.14,5.71,131.43-111.43,117.14,5.71c-14.29,117.14,110,61.43,109.29,43.57"/>
<svg> </
Kanali RSS pošiljajo podatke v obliki XML. Na https://img.rtvslo.si/feeds/00.xml izvenimo, da so zasnežili Krvavec, da so Rijskmuseumu pripravili razstavo Fransa Halsa in da se je v Murski Soboti nekdo skozi žep ustrelil v nogo.
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet title="XSL_formatting" type="text/xsl" href="https://img.rtvslo.si/_up/export/rss/rss.xsl"?>
rss version="2.0">
<channel>
<title>MMC RTV - Vse novice</title>
<item>
<title>Zagon nove šestsedežnice odprl smučarsko sezono na Krvavcu</title>
<pubDate>Wed, 27 Dec 2023 15:28:48 +0100</pubDate>
<link>https://www.rtvslo.si/zabava-in-slog/ture-avanture/zagon-nove-sestsedeznice-odprl-smucarsko-sezono-na-krvavcu/692950</link>
<enclosure url="https://img.rtvcdn.si/_up/upload/2023/12/27/66145453.jpg" length="5120394" type="image/jpeg"/>
<
description><![CDATA[Na Krvavcu, kjer so umetno zasnežili strmine, se je začela smučarska sezona. Otvoritev je pospremilo tudi odprtje nove, hitrejše in ogrevane šestsedežnice Zvoh, ki je zamenjala 40 let staro dvosedežnico. ]]></description>
<item>
</item>
<title>Frans Hals, mojster nizozemskega meščanskega realizma, se predstavi v Rijksmuseumu</title>
<pubDate>Wed, 27 Dec 2023 14:15:47 +0100</pubDate>
<link>https://www.rtvslo.si/kultura/vizualna-umetnost/frans-hals-mojster-nizozemskega-mescanskega-realizma-se-predstavi-v-rijksmuseumu/692952</link>
<enclosure url="https://img.rtvcdn.si/_up/upload/2023/12/27/66145422.jpg" length="546109" type="image/jpeg"/>
<
description><![CDATA[V amsterdamskem Rijksmuseumu za februar napovedujejo razstavo Frans Hals – Veliko pred svojim časom. Hals, ki je bil izjemno produktiven, inovativen, zabaven in tudi nekoliko grob v svojih delih, je bil eden od slikarjev, ki so zaznamovali 17. stoletje. ]]></description>
<item>
</item>
<title>V hlačni žep trenirke vtaknil pištolo in se ustrelil v nogo</title>
<pubDate>Wed, 27 Dec 2023 13:15:56 +0100</pubDate>
<link>https://www.rtvslo.si/crna-kronika/v-hlacni-zep-trenirke-vtaknil-pistolo-in-se-ustrelil-v-nogo/692945</link>
<enclosure url="https://img.rtvcdn.si/_up/upload/2021/09/27/65927267.jpg" length="732130" type="image/jpeg"/>
<
description><![CDATA[Na območju Murske Sobote je v torek malo po 18. uri 78-letni moški pri pospravljanju orožja pištolo vtaknil v hlačni žep trenirke in se pri tem ustrelil v nogo, so sporočili murskosoboški policisti. ]]></description>
<item> </
ARSO v obliki XML objavlja vremensko napoved. To je najbrž prijazno do avtorjev raznih aplikacij za telefone, ki zajemajo podatke z njihove spletne strani.
Celo če preimenujemo datoteko .xlsx ali .docx v zip in jo odzipamo, v njej najdemo XMLje, ki vsebujejo naše besedilo in njegovo oblikovanje.