Schnittstellen / API#

Mit der Pageviews API von Wikipedia können verschiedene Abrufstatistiken abgerufen werden. Mach dich zuerst mit der Pageviews-Dokumentation vertraut. Bearbeite anschließend die Aufgabe zum Wikipedia Jahresrückblick.

Welche Wikipedia-Seiten wurden an deinem Geburtstag am häufigsten in Deutschland besucht? Speichere die Top 10 in einer Liste.

# Code
Hide code cell source
"""
Mit dem requests-Paket können REST-Abfragen an alle Webseiten gestellt werden. Wie in der Aufgabe "REST-Abfragen auf normalen Webseiten" deutlich wird, sind solche Abfragen aber nur auf extra dafür eingerichteten APIs sinnvoll.
Die einfachsten REST-Abfragen beinhalten nur die URL, die abgefragt werden soll.
Meistens wird aber auch eine Authentifizierung benötigt, die im sogenannten Header festgelegt sein muss.
Was genau benötigt wird und wie die URL aussieht, um einer bestimmten API eine Abfrage zu stellen, sollte vor der Verwendung in der Dokumentation der jeweiligen API recherchiert werden.
"""
import requests
url = 'https://wikimedia.org/api/rest_v1/metrics/pageviews/top-per-country/DE/all-access/2022/10/07'
headers = {'user-agent': 'CoolBot/0.0 (https://example.org/coolbot/; coolbot@example.org)'}

response = requests.get(url, headers=headers)
# Hier speichern wir aus der erhaltenen Antwort die ersten 10 Ergebnisse.
# Beachte, dass die Struktur der Antwort je nach API sehr unterschiedlich ist und damit auch der Zugriff auf die gewünschten Daten ganz anders aussehen kann.
top10 = response.json()['items'][0]['articles'][:10]
top10
[{'article': 'Wikipedia:Hauptseite',
  'project': 'de.wikipedia',
  'views_ceil': 705800,
  'rank': 1},
 {'article': 'Spezial:Suche',
  'project': 'de.wikipedia',
  'views_ceil': 136000,
  'rank': 2},
 {'article': 'Jeffrey_Dahmer',
  'project': 'de.wikipedia',
  'views_ceil': 114100,
  'rank': 3},
 {'article': 'Main_Page',
  'project': 'en.wikipedia',
  'views_ceil': 103100,
  'rank': 4},
 {'article': 'Günter_Lamprecht',
  'project': 'de.wikipedia',
  'views_ceil': 88600,
  'rank': 5},
 {'article': 'Special:MyPage/toolserverhelferleinconfig.js',
  'project': 'de.wikipedia',
  'views_ceil': 42900,
  'rank': 6},
 {'article': 'Armageddon',
  'project': 'de.wikipedia',
  'views_ceil': 28700,
  'rank': 7},
 {'article': 'Special:Search',
  'project': 'en.wikipedia',
  'views_ceil': 26300,
  'rank': 8},
 {'article': 'Elisabeth_von_Österreich-Ungarn',
  'project': 'de.wikipedia',
  'views_ceil': 24400,
  'rank': 9},
 {'article': 'Wladimir_Wladimirowitsch_Putin',
  'project': 'de.wikipedia',
  'views_ceil': 24200,
  'rank': 10}]

Ermittle nun für jede Seite der Top 10 wie häufig diese zwischen dem 1. Januar und dem 30. November 2022 aufgerufen wurde.

# Code
Hide code cell source
def format_data(input:list) -> list:
    """
    Die Hilfsfunktion format_data nimmt sich aus der Eingabeliste alle Viewzahlen
    und speichert diese in einer neuen Liste, das ist Teil des Data Cleanings und hilft uns später die gewünschten Daten einfach in einen Pandas Dataframe zu speichern.
    :param input: Eine Liste von Dictionaries, in denen jeweils die Viewzahlen zu finden sind.
    :return: Eine Liste die alle Viewzahlen enthält.
    """
    hilfsliste = []
    for entry in input:
        hilfsliste.append(entry['views'])

    return hilfsliste

"""
Um jetzt für alle Seiten der Top 10 die monatlichen Aufrufzahlen in einem bestimmten Zeitraum zu finden müssen wir eine neue GET-Abfrage stellen.
Um die URL nicht für jede Seite einzeln zu definieren, arbeiten wir mit einem f-String. So können wir die Variablen project und seite einfach an der entsprechenden Stelle einsetzen.
Wichtig ist hierbei, dass die Variablen zuvor festgelegt werden (innerhalb der for-Schleife).
"""
yearly_views = {}
for entry in top10:
    seite = entry['article']
    project = entry['project']

    url2= f'https://wikimedia.org/api/rest_v1/metrics/pageviews/per-article/{project}/all-access/all-agents/{seite}/monthly/2022010100/2022113000'

    response2 = requests.get(url2, headers=headers)
    #print(response2.json())
    """Es kann vorkommen, dass in der Top 10 Seiten auftauchen, die für diese Anfrage eine fehlerhafte Antwort liefern, deshalb überprüfen wir vor dem speichern in unser Dictionary, ob die Antwort den Status Code 200 hat, also OK ist."""
    if response2.status_code == 200:
        yearly_views[seite] = format_data(response2.json()['items'])

yearly_views
{'Wikipedia:Hauptseite': [44262455,
  36924036,
  43388543,
  34314471,
  36495483,
  35085703,
  36638610,
  43347061,
  45682846,
  40416186,
  41663638],
 'Spezial:Suche': [8903866,
  7663019,
  8253779,
  8019271,
  7448381,
  7140473,
  7042975,
  7102671,
  7107440,
  7598127,
  7267013],
 'Jeffrey_Dahmer': [25149,
  23631,
  19592,
  28361,
  25032,
  25538,
  20772,
  33370,
  1525563,
  3360663,
  498380],
 'Main_Page': [370821303,
  355461972,
  363534154,
  345340304,
  373874468,
  426538027,
  395058931,
  410623033,
  345382769,
  409487242,
  396711178],
 'Günter_Lamprecht': [6942,
  6365,
  8405,
  5003,
  6508,
  4905,
  6926,
  12272,
  4900,
  146141,
  6390],
 'Armageddon': [4250,
  3111,
  3937,
  2799,
  2787,
  3506,
  2389,
  2809,
  3697,
  112474,
  4651],
 'Special:Search': [51386256,
  47538973,
  61230888,
  57099022,
  52423122,
  48578060,
  50003781,
  48936945,
  51458391,
  60878555,
  49106176],
 'Elisabeth_von_Österreich-Ungarn': [310795,
  113627,
  53185,
  139013,
  74521,
  58298,
  78653,
  72093,
  172067,
  817391,
  171248],
 'Wladimir_Wladimirowitsch_Putin': [159914,
  2105921,
  1576213,
  313807,
  208446,
  118831,
  113471,
  116797,
  245639,
  253360,
  129941]}

Speichere die Ergebnisse in einem pandas DataFrame und stelle die Aufrufzahlen der Top 10 in dem Zeitraum grafisch dar.

# Code
Hide code cell source
"""
Jetzt können wir unsere Ergebnisse in einen Pandas Dataframe übertragen und anschließend darstellen.
Ich habe mich hier für eine logarithmische Darstellung der Y-Achse entschieden, da sonst die sehr häufig aber relativ konstanten besuchten Haupt- und Suchseiten die seltener aber wesentlich trendabhängiger besuchten Sonderseiten überschatten würden.
"""
import pandas as pd
import matplotlib
df = pd.DataFrame(yearly_views, index=['jan', 'feb', 'mar', 'apr', 'mai', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov'])
df.plot(kind='line', logy = True)
df
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [4], line 7
      5 import pandas as pd
      6 import matplotlib
----> 7 df = pd.DataFrame(yearly_views, index=['jan', 'feb', 'mar', 'apr', 'mai', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov'])
      8 df.plot(kind='line', logy = True)
      9 df

NameError: name 'yearly_views' is not defined

REST-Abfragen auf normalen Webseiten#

Stelle eine get-request an deine Liebelingswebseite und gib den Status Code der Antwort aus.

# Code
Hide code cell source
"""
Wie oben bereits erwähnt, lassen sich REST-Abfragen an jede gültige Webseite stellen. Wie zu sehen ist, ist auch der Status Code in Ordnung.
Im Grunde funktionieren auch Webbrowser so, dass sie GET-Anfragen an die jeweiligen Seiten stellen und als Antwort einen HTML-Text erhalten, der dann dargestellt wird.
"""
import requests
url = "https://www.portalnovosti.com/ko-kain-klan"

response = requests.get(url)

response.status_code
200

Gib nun den Inhalt der Antwort aus.

# Code
Hide code cell source
"""
Wie sich hier aber zeigt, sind HTML-Responses zur Datenakquirierung wenig geeignet, da sich Daten nur sehr aufwändig aus HTML holen lassen.
"""
response.content
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [6], line 4
      1 """
      2 Wie sich hier aber zeigt, sind HTML-Responses zur Datenakquirierung wenig geeignet, da sich Daten nur sehr aufwändig aus HTML holen lassen.
      3 """
----> 4 response.content

NameError: name 'response' is not defined

Versuche den eben ausgeben Inhalt im JSON-Format auszugeben.

# Code
Hide code cell source
"""
Eine Response die HTML beinhaltet, kann logischerweise nicht als JSON dargestellt werden.
"""
response.json()

"""
Um zu überprüfen, um welches Format es sich in der Response handelt, lohnt ein Blick in den Header. Hier muss nach dem Eintrag 'Content-Type' gesucht werden.
"""
response.headers
response.headers['Content-Type']
---------------------------------------------------------------------------
JSONDecodeError                           Traceback (most recent call last)
File ~/anaconda3/envs/rise-environment/lib/python3.10/site-packages/requests/models.py:971, in Response.json(self, **kwargs)
    970 try:
--> 971     return complexjson.loads(self.text, **kwargs)
    972 except JSONDecodeError as e:
    973     # Catch JSON-related errors and raise as requests.JSONDecodeError
    974     # This aliases json.JSONDecodeError and simplejson.JSONDecodeError

File ~/anaconda3/envs/rise-environment/lib/python3.10/json/__init__.py:346, in loads(s, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)
    343 if (cls is None and object_hook is None and
    344         parse_int is None and parse_float is None and
    345         parse_constant is None and object_pairs_hook is None and not kw):
--> 346     return _default_decoder.decode(s)
    347 if cls is None:

File ~/anaconda3/envs/rise-environment/lib/python3.10/json/decoder.py:337, in JSONDecoder.decode(self, s, _w)
    333 """Return the Python representation of ``s`` (a ``str`` instance
    334 containing a JSON document).
    335 
    336 """
--> 337 obj, end = self.raw_decode(s, idx=_w(s, 0).end())
    338 end = _w(s, end).end()

File ~/anaconda3/envs/rise-environment/lib/python3.10/json/decoder.py:355, in JSONDecoder.raw_decode(self, s, idx)
    354 except StopIteration as err:
--> 355     raise JSONDecodeError("Expecting value", s, err.value) from None
    356 return obj, end

JSONDecodeError: Expecting value: line 1 column 1 (char 0)

During handling of the above exception, another exception occurred:

JSONDecodeError                           Traceback (most recent call last)
Cell In [2], line 2
      1 # Dein Code
----> 2 response.json()

File ~/anaconda3/envs/rise-environment/lib/python3.10/site-packages/requests/models.py:975, in Response.json(self, **kwargs)
    971     return complexjson.loads(self.text, **kwargs)
    972 except JSONDecodeError as e:
    973     # Catch JSON-related errors and raise as requests.JSONDecodeError
    974     # This aliases json.JSONDecodeError and simplejson.JSONDecodeError
--> 975     raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)

JSONDecodeError: Expecting value: line 1 column 1 (char 0)