Analysieren von JSON-Dateien mit Python
Die meisten freien Softwareprojekte haben heute ihre Heimat auf den Servern von GitHub. Dessen Betreiber veröffentlicht die Aktivitätsprotokolle der Public-Domain-Projekte auf seiner Webseite. Diese Dateien im JSON-Format lassen sich natürlich mit Python herunterladen und analysieren.
![Analysieren von JSON-Dateien mit Python](https://heise.cloudimg.io/width/610/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/1/8/3/4/1/7/2/python-2afa87149aa7bdc2.jpeg)
- Gerhard Völkl
Ihr Inhalt soll eine Antwort auf die Frage liefern, wie oft Python in GitHub-Projekten zum Einsatz kommt und wie häufig andere Programmiersprachen. Die Dateien mit den Aktivitätsinformationen auf GitHub sind stundenweise unterteilt. Beispielsweise enthält die Datei http://data.githubarchive.org/2016-01-01-15.json.gz die Aktivitäten vom 01.01.2016, die zwischen 15:00 und 15:59 Uhr stattgefunden haben.
Eine komplette Datei mit Python herunterzuladen geht am einfachsten mit der Funktion urlib.request.urlretrieve:
import urllib.request
urllib.request.urlretrieve ("http://data.githubarchive.org
/2015-01-01-16.json.gz", #Wo laden?
"bzero.json.gz") #Wohin speichern?
Die Datei ist gepackt und ist erst noch mit dem Modul gzip in Klartext umzusetzen:
import gzip
import shutil
with gzip.open('bzero.json.gz', 'rb') as input:
with open('bzero.json', 'wb') as output:
shutil.copyfileobj(input, output)
Das JSON-Format (JavaScript Object Notation) wurde in den letzten Jahren im Netz zunehmend beliebter, da es Informationen in strukturierter Form aufnehmen kann und einfacher zu lesen ist als XML oder ähnliche Formate. Ursprünglich war JSON ein Bestandteil der Programmiersprache JavaScript, es hat sich aber emanzipiert und wird von vielen Entwicklern verwendet. Hier ein Ausschnitt der heruntergeladenen Datei bzero.json:
{"id":"1234",
"type":"PullRequestEvent",
"actor":{"id":1234,
"url":"https://..."
"repo":{"id":1234,
"name":"tester/tester",
"url":"https://..."},
"payload":{"action":"opened","number":1,
"pull_request":{"url":"https://...",
"id":1234,"html_url":"https://...",
"diff_url":"https://...",
"patch_url":"https://...",
"issue_url":"https://...",
"number":1234,"state":"open",
"locked":false,"title":"QA fixes",
"head":{"label":"jtester:master",
"ref":"master",
"repo":
{"id":1234,"name":"tester",
"full_name":"tester/tester",
"private":false,
"description":"everyone's favorite Li",
"watchers_count":0,
"language":"Python",
"has_issues":false,"has_downloads":true,
"has_wiki":true,"has_pages":false,
"forks_count":0,"mirror_url":null}},
...
"public":true,"created_at":"2015-01-01T16:02:50Z",
"org"
{"id":1234,"login":"tester","gravatar_id":"",
"url":"https://...",
"avatar_url":"https://..."}}
Einlesen von JSON-Dateien
In Python gehört das Modul json zur Verarbeitung dieses Formats zum Standard. Es enthält Funktionen, die den Text in der JSON-Struktur in Python-Dictionarys beziehungsweise Listen umwandeln (json.load) oder auch umgekehrt (json.dump). Dabei gelten folgende Regeln:
- { "element":1, "element2":"Inhhalt"} wird zu einer Dictionary in Python und ["element","element2"] zu einer Liste.
- Verschachtelte Elemente in JSON sind in Python wiederum in Dictionarys verschachtelt.
Eine komplette Datei einzulesen, geht mit wenigen Zeilen:
import json
with open('bzero.json') as file:
json.load(file)
Wer dies nachvollzieht, bekommt allerdings die Fehlermeldung:
UnicodeDecodeError: 'charmap' codec can't decode byte 0x81 in position
32871: character maps to <undefined>
Sie ist nicht unbedingt selbsterklärend. Nach etwas Nachforschung im Internet kommt man auf ihre Bedeutung: Sie besagt, dass der Zeichensatz ein anderer ist, als von json.load erwartet. Gut, dass
open in Python 3 einen Parameter für den Zeichensatz (encoding) hat. In diesem Fall ist es UTF-8.
import json
with open('bzero.json', encoding='utf-8') as file:
json.load(file)
Das führt allerdings zur nächsten nicht sehr aussagekräftigen Fehlermeldung:
JSONDecodeError: Extra data: line 2 column 1 (char 397)
Grund dafür ist, dass die Datei zwar aus einzelnen Einträgen im JSON-Format besteht, aber die Datei als Ganzes nicht dem Format entspricht.
{Datensatz 1}
{Datensatz 2}
{Datensatz 3}
Es fehlen []-Klammern um die gesamte Datei, damit es eine Liste im JSON-Format ergibt.
[{Datensatz 1}
{Datensatz 2}
{Datensatz 3}]
Eine gute Lösung des Problems findet sich in Jake VanderPlas' "Python Data Science Handbook". Er
verwendet dafür Python-Generator-Objekte, wie im Folgenden veranschaulicht:
with open('bzero.json',encoding='utf-8') as f:
data = (line.strip() for line in f) #Erzeugt Generator Objekt
data_json = "[{0}]".format(','.join(data))
data = json.loads(data_json)
Mit json.loads wandelt man einen String im JSON-Format, hier data_json, in Python-Elemente um, in diesem Fall in eine Liste, die Dictionarys enthält:
In [5]:
data[0]
Out[5]:
{'actor': {'avatar_url': 'https://avatars.githubusercontent.com/u/626420?',
'gravatar_id': '',
'id': 626420,
'login': 'mukimov',
'url': 'https://api.github.com/users/mukimov'},
'created_at': '2015-01-01T16:00:00Z',
'id': '2489678845',
'payload': {'action': 'started'},
'public': True,
'repo': {'id': 996780,
'name': 'vim-scripts/ctags.vim',
'url': 'https://api.github.com/repos/vim-scripts/ctags.vim'},
'type': 'WatchEvent'}