Zajem podatkov#
Zdaj, ko znamo analizirati eno stran filmov, bi radi to ponovili za več strani. Pri tem seveda teh strani ne bomo zajemale ročno, ampak bomo napisali program, ki bo to storil namesto nas.
Knjižnica requests
#
Kljub temu, da Pythonova standardna knjižnica že vsebuje modul urllib
za delo s spletnimi stranmi, bomo uporabili knjižnico requests
, ki je bolj enostavna za uporabo, hkrati pa podpira stvari, kot so certifikati, preusmeritve in podobno. Ko si knjižnico enkrat namestimo, lahko strani preprosto preberemo z ukazom
import requests
r = requests.get('http://www.fmf.uni-lj.si/')
statusna_koda = r.status_code
vsebina = r.text
Podatke moramo zajeti s strani oblike https://www.imdb.com/search/title/?title_type=feature&sort=num_votes,desc&count=250&start=<START>
, kjer je <START>
enak 1, 251, 501 in tako naprej do 9751. Da nam strani ne bo treba zajemati večkrat (ter s tem izgubljati časa in tvegati, da bo strežnik začel zavračati naše zahteve), bomo vsako izmed njih shranili v datoteko:
st_strani = 40
na_stran = 250
for stran in range(st_strani):
url = f"https://www.imdb.com/search/title/?title_type=feature&sort=num_votes,desc&count={na_stran}&start={1 + stran * na_stran}"
odziv = requests.get(url)
if odziv.status_code == 200:
print(url)
with open(f"stran-{stran}.html", "w") as f:
f.write(odziv.text)
else:
print("Prišlo je do napake")
Če to sestavimo skupaj s programom za analizo strani iz prejšnjega poglavja dobimo seznam 10000 slovarjev, na primer:
filmi = [
{
"sifra": 76759,
"naslov": "Vojna zvezd",
"leto": 1977,
"ocena": 8.6,
"reziser": "George Lucas",
"igralci": [...],
},
{
"sifra": 80684,
"naslov": "Imperij vrača udarec",
"leto": 1980,
"ocena": 8.7,
"reziser": "Irvin Kershner",
"igralci": [...],
},
{
"sifra": 82971,
"naslov": "Lov za izgubljenim zakladom",
"leto": 1981,
"ocena": 8.4,
"reziser": "Steven Spielberg",
"igralci": [...],
},
{
"sifra": 86190,
"naslov": "Vrnitev jedija",
"leto": 1983,
"ocena": 8.3,
"reziser": "Richard Marquand",
"igralci": [...],
},
{
"sifra": 87469,
"naslov": "Indiana Jones in tempelj smrti",
"leto": 1984,
"ocena": 7.6,
"reziser": "Steven Spielberg",
"igralci": [...],
},
{
"sifra": 97576,
"naslov": "Indiana Jones in Zadnji križarski pohod",
"leto": 1989,
"ocena": 8.2,
"reziser": "Steven Spielberg",
"igralci": [...],
},
]
Zapis CSV#
Prečiščene podatke bi lahko shranili v zapis JSON, vendar jih bomo raje shranili v tabelarično obliko, ki je za analizo primernejša od hierarhične. Za to uporabimo zapis CSV (Comma-Separated Values), ki je univerzalni zapis podatkov v besedilni datoteki. Datoteke CSV lahko odpremo v praktično vsakem programu za delo s podatki. V zapisu CSV vsaka vrstica predstavlja vnos, v katerem so polja ločena z vejico. Prvo vrstico običajno uporabimo za imena polj. Na primer, tabelo:
id |
naslov |
leto |
ocena |
---|---|---|---|
76759 |
Vojna zvezd |
1977 |
8.6 |
80684 |
Imperij vrača udarec |
1980 |
8.7 |
82971 |
Lov za izgubljenim zakladom |
1981 |
8.4 |
86190 |
Vrnitev jedija |
1983 |
8.3 |
87469 |
Indiana Jones in tempelj smrti |
1984 |
7.6 |
97576 |
Indiana Jones in Zadnji križarski pohod |
1989 |
8.2 |
bi v zapisu CSV pisali kot:
id,naslov,leto,ocena
76759,Vojna zvezd,1977,8.6
80684,Imperij vrača udarec,1980,8.7
82971,Lov za izgubljenim zakladom,1981,8.4
86190,Vrnitev jedija,1983,8.3
87469,Indiana Jones in tempelj smrti,1984,7.6
97576,Indiana Jones in Zadnji križarski pohod,1989,8.2
Da se izognemo robnim primerom kot vejice v naslovih, CSV datotek ne bomo pisali na roke temveč prek knjižnice csv
. Iz zgornjega seznama slovarjev lahko CSV datoteko naredimo z:
import csv
with open("filmi.csv", "w") as f:
writer = csv.writer(f)
writer.writerow(["id", "naslov", "leto", "ocena"])
for film in podatki:
writer.writerow([film["sifra"], film["naslov"], film["leto"], film["ocena"]])
Če želimo, lahko vsako vrstico podamo kar neposredno s slovarjem:
with open("filmi.csv", "w") as f:
writer = csv.DictWriter(f, fieldnames=["id", "naslov", "leto", "ocena"])
writer.writeheader()
for film in podatki:
writer.writerow(film)
Podobno lahko s knjižnico CSV tudi beremo datoteke.
Normalizacija podatkov#
Kako bi v tabeli shranili podatke o režiserju? Očitna rešitev je, da v tabelo dodamo še stolpec z režiserjem, na primer:
id |
naslov |
leto |
ocena |
reziser |
---|---|---|---|---|
76759 |
Vojna zvezd |
1977 |
8.6 |
George Lucas |
80684 |
Imperij vrača udarec |
1980 |
8.7 |
Irvin Kershner |
82971 |
Lov za izgubljenim zakladom |
1981 |
8.4 |
Steven Spilberg |
86190 |
Vrnitev jedija |
1983 |
8.3 |
Richard Marquand |
87469 |
Indiana Jones in tempelj smrti |
1984 |
7.6 |
Steven Spielberg |
97576 |
Indiana Jones in Zadnji križarski pohod |
1989 |
8.2 |
Steven Spielberg |
… |
… |
… |
… |
… |
Vendar ta rešitev ni najboljša. Prva težava je, da je podvajanje podatkov potratno (kaj šele, da bi si ob vsakem režiserju želeli shraniti še življenjepis). Še večja težava pa je, da pri podvajanju podatki niso nujno konsistentni. Na primer, pri tretjem filmu je ime režiserja napisano napačno.
Namesto tega bomo podatke o režiserjih shranili v ločeno tabelo:
id |
ime |
datum_rojstva |
zivljenjepis |
---|---|---|---|
184 |
George Lucas |
1944-05-14 |
… |
229 |
Steven Spielberg |
1946-12-18 |
… |
449984 |
Irvin Kershner |
1923-04-29 |
… |
549658 |
Richard Marquand |
1937-09-22 |
… |
… |
… |
… |
… |
V tabeli filmov bomo shranili samo številko režiserja:
id |
naslov |
leto |
ocena |
reziser |
---|---|---|---|---|
76759 |
Vojna zvezd |
1977 |
8.6 |
184 |
80684 |
Imperij vrača udarec |
1980 |
8.7 |
449984 |
82971 |
Lov za izgubljenim zakladom |
1981 |
8.4 |
229 |
86190 |
Vrnitev jedija |
1983 |
8.3 |
549658 |
87469 |
Indiana Jones in tempelj smrti |
1984 |
7.6 |
229 |
97576 |
Indiana Jones in Zadnji križarski pohod |
1989 |
8.2 |
229 |
… |
… |
… |
… |
… |
Tako vsako podatek zapišemo samo enkrat, zato ni več težav ne s prostorom, ne s konsistentnostjo podatkov. Pravimo, da so podatki normalizirani.
Kako pa bi shranili podatke o igralcih, ki jih je lahko več? (V resnici je tudi režiserjev lahko več, vendar to zaenkrat zanemarimo.) Vemo že, da bomo igralce dodali v tabelo oseb:
id |
ime |
datum_rojstva |
zivljenjepis |
---|---|---|---|
125 |
Sean Connery |
1930-08-25 |
… |
148 |
Harrison Ford |
1942-07-13 |
… |
184 |
George Lucas |
1944-05-14 |
… |
229 |
Steven Spielberg |
1946-12-18 |
… |
… |
… |
… |
… |
V tabeli filmov bi lahko shranili seznam številk igralcev:
id |
naslov |
leto |
ocena |
reziser |
igralci |
---|---|---|---|---|---|
76759 |
Vojna zvezd |
1977 |
8.6 |
184 |
434,148,402 |
80684 |
Imperij vrača udarec |
1980 |
8.7 |
449984 |
434,148,402 |
82971 |
Lov za izgubljenim zakladom |
1981 |
8.4 |
229 |
148,261 |
86190 |
Vrnitev jedija |
1983 |
8.3 |
549658 |
434,148,402 |
87469 |
Indiana Jones in tempelj smrti |
1984 |
7.6 |
229 |
148 |
97576 |
Indiana Jones in Zadnji križarski pohod |
1989 |
8.2 |
229 |
148,125 |
… |
… |
… |
… |
… |
Vendar bi bilo po takih podatkih težko iskati. Namesto tega bomo uvedli dodatno povezovalno tabelo vlog, v kateri vsak vnos sporoča, kateri igralec je igral v katerem filmu:
film |
oseba |
---|---|
76759 |
434 |
76759 |
148 |
76759 |
402 |
80684 |
434 |
80684 |
148 |
… |
… |
Če tabelo opremimo še s tipom vloge (I - igralec, R - režiser), lahko vanjo shranimo tako podatke o igralcih kot o režiserjih:
film |
oseba |
vloga |
---|---|---|
76759 |
148 |
I |
76759 |
184 |
R |
76759 |
402 |
I |
76759 |
434 |
I |
80684 |
148 |
I |
80684 |
434 |
I |
80684 |
449984 |
R |
82971 |
229 |
R |
86190 |
549658 |
R |
87469 |
229 |
R |
97576 |
229 |
R |
… |
… |
… |
Če bi kakšna oseba tako režirala nek film kot igrala v njem, bi v tabeli imeli dva vnosa.