forked from CorrelAid/lernplattform
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path05_1_datenimport-exkurs-api.Rmd
More file actions
209 lines (162 loc) · 13.3 KB
/
05_1_datenimport-exkurs-api.Rmd
File metadata and controls
209 lines (162 loc) · 13.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
[](https://creativecommons.org/licenses/by/4.0/deed.de)
*"Eigene API-Anfragen (Exkurs)" von Frie Preu in "R Lernen - der Datenkurs von und für die Zivilgesellschaft" von CorrelAid e.V. Lizensiert unter [Creative Commons Attribution 4.0 International](https://creativecommons.org/licenses/by/4.0/deed.de).*

### **Interaktive Übung**
Eine API (_Application Programming Interface_, de: Schnittstelle zur Programmierung von Anwendungen) ist eine Schnittstelle, die ein System bereitstellt, um anderen Programmen die Interaktion zu ermöglichen.
Eine Interaktion sieht so aus:
1. Der **Client** macht eine Anfrage (engl. Request) an die API
2. Die API verarbeitet die Anfrage und gibt eine Antwort (engl.: **Response**) zurück.
3. Der Client verarbeitet die Antwort.
APIs verfügen zumeist über eine **Dokumentation**, die enthält, welche Funktionalitäten verfügbar sind und wie Anfragen gestellt werden müssen.
**Analogie**: Wenn Ihr (als _Client_) im Restaurant seid, stellt Euch das Restaurant eine:n Kellner:in (Eure _API_) und eine Speisekarte (Eure _API-Dokumentation_) bereit. Der:die Kellner:in nimmt Eure Bestellungen (_Anfragen_) entgegen, die Küche verarbeitet diese und der:die Kellner:in bringt Eure Bestellung (_Antwort_).
Die allermeisten APIs heutzutage verwenden das HTTP-Protokoll, welches fünf sogenannte _Methoden_ umfasst: GET, POST, PUT, PATCH und DELETE. Da wir in unserem Fall auf Interaktionen schauen, welche sich auf den Datenaustausch fokussieren, ergeben sich folgende Entsprechungen:
- `GET` --> Daten lesen
- `POST` --> Neue Daten erstellen
- `PUT` --> Daten ersetzen
- `PATCH` --> Daten aktualisieren
- `DELETE` --> Daten löschen
(siehe Folie 10 von ["Datenzugriff im World Wide Web"](https://projektzyklus.correlaid.org/07_datenmanagement-webdaten/2021-05-09_Datenzugriff_im_WWW.pdf), Jan Dix, lizensiert unter [Creative Commons Attribution 4.0 International](https://creativecommons.org/licenses/by/4.0/legalcode.de).)
[Hier (en)](https://github.com/public-apis/public-apis) findet Ihr eine Liste von öffentlichen APIs, die Ihr kostenfrei nutzen könnt.
### **GET-Anfragen**
Wenn Ihr nur Daten laden möchtet, reicht `GET` meistens aus. Je nach API können allerdings auch `POST` Anfragen notwendig sein. `GET`-Anfragen können als normale URL (das was Ihr in euren Browser eingebt) abgebildet werden. Diese URLs setzen sich aus drei Teilen zusammen:
`{BASIS_URL}/{ROUTE}?{QUERY_PARAMETER}`.
Das kennt Ihr zum Beispiel von einer Google-Suche: `https://www.google.com/search?q=CorrelAid`.
- `BASIS_URL`: `https://www.google.com/`
- `ROUTE`: `search`
- `QUERY_PARAMETER`:
- `q`: `CorrelAid`
**Analogie**: Im Restaurant bestellt (--> `GET`) Ihr bei Elmo (Eure API mit der Basis-URL `https://elmo.correlandfriends.de/`) auf der Route "Essen" (`essen`) das Gericht Risotto (Query-Parameter `gericht=risotto`). Die komplette Anfrage-URL wäre also: `https://elmo.correlandfriends.de/essen?gericht=risotto`. Das Fragezeichen signalisiert das Ende der Route und den Anfang der Query-Parameter.
### **Statuscodes**
Fast alle APIs geben in Ihrer Antwort einen Code zurück, anhand dem man schnell sehen kann, ob die Anfrage erfolgreich war oder nicht. Dieser sogenannte _Statuscode_ ist sehr hilfreich, da er Aufschluss gibt, was schief gegangen sein könnte.
Wenn die Anfrage erfolgreich war, gibt die API einen `200` Statuscode zurück. Darüber hinaus gibt es viele Statuscodes, die einen Fehler anzeigen. Häufige Fälle sind:
- `404`: Nicht gefunden ("Not found"). Z.B. existiert der Endpunkt / die Route gar nicht in der API
- `401`: Nicht autorisiert ("Not authorised"): Ihr seid nicht autorisiert auf die API zuzugreifen, z.B. weil Ihr keinen *Token* übergeben habt.
- `403`: Nicht erlaubt ("Forbidden"): Ihr seid zwar im Prinzip für die API autorisiert, aber nicht für die Route, auf die ihr zugreifen wollt (z.B. sensitive Daten oder Administration).
- `422`: Nicht verarbeitbare Anfrage ("Unprocessable Entity"): Eure Anfrage wurde nicht richtig gestellt
- `500`: Interner Server-Fehler ("Internal Server Error"): Eure Anfrage ist zwar richtig gestellt worden, innerhalb der API kam es aber zu einem Fehler
**Analogie**: Nach Eurem Besuch im Restaurant "Correl and Friends" geht Ihr noch in die Bar "AidBar". Doch irgendwie seid Ihr nicht so richtig satt geworden vorhin und bestellt, ohne die Karte (_API -Dokumentation_) zu konsultieren, eine Pommes (`/essen?gericht=Pommes`). Leider muss Euch die Kellnerin enttäuschen, "Aid's Tavern" verkauft nur Getränke: 404!
Auf Folien 13-16 [dieses Foliensatzes](https://projektzyklus.correlaid.org/07_datenmanagement-webdaten/2021-05-09_Datenzugriff_im_WWW.pdf) findet Ihr noch mehr Erklärungen zu wichtigen Statuscodes.
### **Beispiel**
Wir verwenden die [Sustainable Development Goals (SDG) API](https://unstats-undesa.opendata.arcgis.com/#api) der Vereinten Nationen (en: _United Nations_), welche Daten über den Fortschritt der [Sustainable Development Goals](https://sdgs.un.org/) bereitstellt. Die Dokumentation der API findet Ihr [hier](https://unstats.un.org/SDGAPI/swagger/). Der Indikator "[Municipal Solid Waste collection coverage by cities (percent)](https://www.sdg.org/datasets/undesa::indicator-11-6-1-municipal-solid-waste-collection-coverage-by-cities-percent/about)" (Seriencode: `EN_REF_WASCOL`) passt gut zu unseren bisherigen Analysen zum Thema Plastikverschmutzung. Um die Daten zu diesem Indikator zu laden, verwendet Ihr den GET-Endpunkt [`/v1/sdg/Series/Data`](https://unstats.un.org/SDGAPI/swagger/#!/Series/V1SdgSeriesDataGet). Zudem habt Ihr nun mehrere Möglichkeiten, Query-Parameter anzugeben, unter anderem den Code des Indikators (`seriesCode`) und den Zeitrahmen, für den Ihr Daten benötigt.
Um Anfragen an APIs zu machen, nutzen wir das [`httr`](https://httr.r-lib.org/)-Package (Doku).
```{r series-req, exercise=TRUE}
# library(httr)
basis_url <- "https://unstats.un.org/" # Haupt-URL
initiale_anfrage <- httr::GET( # Initialisierung
basis_url, # URL verlinken
path = "/SDGAPI/v1/sdg/Series/Data", # Route definieren
query = list(
seriescode = "EN_REF_WASCOL"
)
)
```
Wenn wir uns die Anfrage-URL anschauen, erkennen wir wieder das Schema `{BASIS_URL}/{ROUTE}?{QUERY_PARAMETER}`.
```{r waste-url, exercise=TRUE}
initiale_anfrage$url
```
```{r quiz-route, exercise = FALSE, echo=FALSE}
quiz(caption = NULL,
question("Was ist die Basis-URL der Anfrage?",
answer("https://unstats.un.org/SDGAPI/v1/sdg/Series/Data"),
answer("https://unstats.un.org/SDGAPI/v1/sdg/Series/"),
answer("https://unstats.un.org/", correct = TRUE),
correct = "Richtig!",
incorrect = "Leider falsch: Schaue dir den Code nochmal an!",
allow_retry = TRUE,
try_again_button = "Nochmal versuchen"),
question("Was ist die Route der Anfrage?",
answer("v1/sdg/Series/Data"),
answer("SDGAPI/v1/sdg/Series/Data/", correct = TRUE),
answer("sdg/Series/"),
correct = "Richtig!",
incorrect = "Leider falsch: Schaue dir den Code nochmal an!",
allow_retry = TRUE,
try_again_button = "Nochmal versuchen"
),
question("Wie viele Parameter hat unsere Anfrage?",
answer("0"),
answer("1", correct = TRUE),
answer("2"),
answer("3"),
answer("4"),
correct = "Richtig!",
incorrect = "Leider falsch: Schaue dir den Code nochmal an!",
allow_retry = TRUE,
try_again_button = "Nochmal versuchen"
)
)
```
Zuerst checken wir nun den **Statuscode**, um sicher zu gehen, dass unsere Anfrage erfolgreich war. In `httr` gibt es hierzu auch die `stop_for_status` Funktion, die einen Fehler schmeißt, wenn die Anfrage nicht erfolgreich war, sonst aber nichts tut.
```{r waste-status, exercise=TRUE}
initiale_anfrage$status_code # 200
httr::stop_for_status(initiale_anfrage) # nichts passiert, alles gut!
```
Nun fragen wir uns: **Wie viele Seiten Dokument** müssen überhaupt geladen werden? APIs geben Euch nämlich meistens nicht von Haus aus alle Resultate zurück.
```{r apicontent, exercise = TRUE}
# Vorläufig Inhalt der Inititalabfrage zur Prüfung speichern
content <- httr::content(initiale_anfrage)
# Einlesen der Seitenanzahl durch das Attribut "totalPages", auf das wir mithilfe von "$" zugreifen
total_pages <- content$totalPages
print(total_pages)
pages <- c(1:total_pages)
```
Diese Information merken wir uns und schauen uns nun an, was genau die Antwort unserer Anfrage beinhaltet. Diese erhaltet Ihr, indem Ihr die Funktion `httr::content()` (de: Inhalt) verwendet. Output dieser Funktion ist eine **vielschichtige (eng. nested) Liste**.
```{r content, exercise=TRUE}
# Inhalt der Response herausziehen
httr::content(initiale_anfrage)
```
Wir erhalten von der Anfrage also eine Liste mit mehreren Elementen zurück, die die gesamte _Antwort_ darstellt. Als Anwender:innen seid Ihr hauptsächlich an den **übermittelten Daten** interessiert, die wir nun ziehen ("$data") . Mit `purrr::map_df` können wir nun die Datenliste (ebenfalls vielschichtig) entpacken.
```{r goals-extract, exercise=TRUE}
# Für verschiedene Städte in verschiedenen Ländern erhalten wir so zu verschiedenen Jahren Ihre Müllsammlungsquoten
waste_list <- waste_data$data
# Mit dem purrr-Package ziehen wir nun die Daten in einen Dataframe
library(purrr)
waste_geo <- waste_list %>%
purrr::map_df(`[`, c("geoAreaCode", "geoAreaName", "dimensions", "value")) %>%
filter(dimensions != "G") # Duplikate entfernen, die aus der Datenstruktur resultieren
# Datensatz betrachten
dplyr::glimpse(waste_geo)
```
Abschließend schauen wir uns nochmal genauer eines der Datenelemente an:
```{r inspect-data, exercise=TRUE}
# Wir schauen uns eines der Daten Elemente an. Die doppelten Klammern extrahieren das x-te Element aus der Liste, hier das zehnte.
str(waste_data$data[[10]])
```
Das Daten-Listenelement enthält ziemlich viele Informationen. Für Euch relevant ist der Wert (`value`) sowie die Informationen über das "Wo": `geoAreaCode` und `geoAreaName`.
Diese Informationen nutzen wir nun - gemeinsam mit der Seitenanzahl - um für jede Seite die benötigten Informationen zu ziehen und diese in einem Dataframe zusammenzuführen. Wie genau Ihr mit solchen komplexeren Datenstrukturen umgehen könnt und diese Daten aus _allen_ Elementen extrahieren könnt, lernt Ihr zu einem späteren Zeitpunkt in Eurer Karriere als Datenwissenschaftler:innen der Zivilgesellschaft - hier schon einmal die Preview:
```{r listofdataframes, exercise=TRUE}
# Funktion, mit der alle Seiten geladen werden, indem die Seitenanzahl "Page" durch eine Nummer zwischen 1 und 11 (= Total Pages) ersetzt wir
list_of_dataframes <- purrr::map(pages, function(page) {
antwort <- httr::GET(
basis_url,
path = "/SDGAPI/v1/sdg/Series/Data",
query = list(
seriescode = "EN_REF_WASCOL",
page = page # Hier wird die Seitenzahl eingesetzt
)
)
# Von jeder Seite die Daten laden
waste_list <- httr::content(antwort)$data
# Generierung eines Dataframes mit den ausgewählten Variablen
waste_geo <- waste_list %>%
purrr::map_df(`[`, c("geoAreaCode", "geoAreaName", "dimensions", "value"))
return(waste_geo)
})
# Erstellung eines großen Dataframes mit allen Daten
waste_geo_df <- dplyr::bind_rows(list_of_dataframes)
```
### **Authentifizierung**
In unserem Beispiel konnten wir ohne Authentifizierung die UN-SDG API verwenden. Die meisten APIs erfordern allerdings eine Authentifizierung, d.h. Ihr müsst beweisen, dass Ihr berechtigt (*autorisiert*) seid, auf die API und ihre Daten zuzugreifen.
Hierzu gibt es viele verschiedene Modelle. Im einfachsten Fall erstellt ihr euch einen sogenannten Token in der entsprechenden Website in den Benutzereinstellungen. Ein **Token** ist ein zufällig erstellter String, quasi euer API-"Passwort". Tokens sehen meistens so aus `eyJpc3MiOiJodHRwczovL2V4YW1...`.
```{r authorization-ex, exercise=FALSE, eval=FALSE, echo=TRUE}
# Liest die Umgebungsvariable API_TOKEN und speichert sie im Objekt Token
basis_url <- "https://beispielapi.org/"
token <- Sys.getenv("API_TOKEN")
# Füge unserer Anfrage einen Autorisierungs-Header hinzu, sodass die API weiß, dass wir autorisiert sind
antwort <- httr::GET(basis_url,
httr::add_headers(Authorization = paste("Authorization", token)))
```
Die Details zur Authentifizierung sind von API zu API unterschiedlich. Die genauen Instruktionen solltet ihr in der jeweiligen API-Dokumentation finden.
Es gibt spezifische Statuscodes, die in Verbindung mit Authentifizierung besonders häufig auftauchen: 401 und 403. Diese Fehlercodes tauchen häufig auf, wenn man eigentlich den richtigen Token hat, aber ihn in der Anfrage nicht richtig übergeben hat. Lest deshalb immer genau in der Dokumentation nach, wie die Authentifizierung der API im Detail funktioniert.
Aus Sicherheitsgründen ist es zudem sehr wichtig, Euren Token (oder sonstige **sicherheitsrelevante Informationen**) **nicht direkt im Code zu speichern**. Anstatt dessen nutzt ihr hierzu am besten **Umgebungsvariablen** (engl. environment variables). Wie man Umgebungsvariablen zu R hinzufügt, ist in [diesem Blogpost](https://www.roelpeters.be/what-is-the-renviron-file/) gut beschrieben. Auch wir üben das zu einem späteren Zeitpunkt nochmal.
Ihr wollt mehr darüber lernen, wie man API-Anfragen selbst schreibt? Dataquest bietet den Kurs ["APIs in R"](https://app.dataquest.io/course/apis-in-r){target="_blank"} an.