Wikidata:Werkzeuge/OpenRefine/Bearbeiten/Anleitungen/Mit APIs arbeiten
In dieser Anleitung werden wir Informationen über eine Web-API extrahieren und nach Wikidata hochladen. Eine API ist eine maschinenlesbare Version einer Webseite, die die automatische Extraktion von Informationen dieser Webseite erlaubt.
Erste Schritte
ORCID ist eine Initiative, um eindeutige Identifikatoren für wissenschaftliche Autoren anzubieten. Jeder kann sein eigenes Profil bei ORCID erstellen und einen eindeutigen Identifikator erhalten, den er beim Verfassen wissenschaftlicher Artikel oder anderer Texte nutzen kann. In Wikidata kann die Eigenschaft ORCID iD (P496) genutzt werden, um auf diese Profile zu verlinken, die häufig Informationen über den Arbeitgeber des Forschers enthalten. Beispielsweise wurden bei der Erstellung des Datenobjektes über Andreas Küttel (Q289574) keine Zugehörigkeiten angegeben, jedoch wurde https://orcid.org/0000-0003-0235-3590 verlinkt, wo es einen Eintrag für eine Anstellung gibt. Unser Ziel ist, diese Information nach Wikidata zu importieren.
Lass uns zunächst eine kleine SPARQL-Abfrage schreiben, um Beispiele für Forscher zu finden, die in ihrem Wikidata-Datenobjekt keine Zugehörigkeit haben, bei denen jedoch eine ORCID iD (P496) angegeben ist.
SELECT ?researcher ?orcid WHERE {
?researcher wdt:P31 wd:Q5;
wdt:P496 ?orcid.
FILTER NOT EXISTS { ?researcher wdt:P108 ?institution }
}
LIMIT 200
Führe diese Abfrage aus und lade die Ergebnisse als Tabelle herunter. Erstelle mit ihr ein OpenRefine-Projekt (die Standardeinstellungen für das Importwerkzeug sollten angemessen sein). Die erste Spalte enthält die URLs der Wikidata-Datenobjekte. Im Moment behandelt OpenRefine sie wie jede andere URL: sie sind technisch gesehen nicht mit Wikidata abgeglichen. Um diesen Zellen den Status als abgeglichen zu verleihen, musst du für diese Spalte einen Abgleich laufen lassen, der wesentlich schneller als üblich erfolgen sollte (da keine Suche erfolgt - die QIDs werden nur bestätigt und ausgegeben).[1]
Abfrage über die API
ORCID bietet eine öffentliche API, die ohne Anmeldung genutzt werden kann, um die öffentlichen Inhalte jedes ORCID-Profils abzurufen. Es gibt dort eine Schnellanleitung, die erklärt, wie die API funktioniert. Du musst ORCIDs Beschreibung der API nicht komplett verstehen - ihre Anleitung ist ziemlich kompliziert, da sie viele Funktionen behandelt, die wir hier nicht brauchen. Wir müssen nur wissen, welche URL wir abrufen müssen, um die Information zur Zugehörigkeit in einem strukturierten Format zu erhalten. Um Inhalte des Abschnitts Anstellung abzurufen, müssen wir die folgende Adresse aufrufen: "https://pub.orcid.org/v2.1/"+value+"/employments"
, wobei +value+
durch den ORCID-Identifikator der Person ersetzt wird. Standardmäßig erfolgt die Ausgabe im XML-Format, wir können jedoch stattdessen JSON erhalten, indem wir den HTTP-Header Accept: application/json
hinzufügen. All das können wir in OpenRefine über den Befehl Spalte bearbeiten → Spalte aus URLs hinzufügen auf die ORCID-Spalte ausführen:
Extrahieren von Informationen aus JSON-Antworten
Wenn der Befehl ausgeführt wurde, erhältst du die JSON-Antworten in einer neuen Spalte. Hieraus müssen wir nun die Informationen extrahieren. Jede Antwort enthält potenziell eine Liste von Arbeitgebern, sodass wir zunächst die Information zu jedem einzelnen Arbeitgeber in einer eigenen Zeile extrahieren müssen, gemäß dem OpenRefine-Aufzeichnungsmodell. Dies ist leider ein relativ umständlicher Prozess:
- wir müssen zunächst die Liste der Arbeitgeber extrahieren, indem wir die JSON-Inhalte parsen, wofür wir die Funktion Spalte basierend auf dieser Spalte hinzufügen (im Menü Spalte bearbeiten der Spalte Anstellungen) mit dem Ausdruck
value.parseJson()["employment-summary"].join('###')
nutzen.
- dann nutzen wir die Funktion Zellen mit mehreren Werten aufteilen (im Menü Zellen bearbeiten der neuen Spalte), wobei wir den gleichen Trenner
###
nutzen, um jedes Objekt in einer eigenen Zeile zu erhalten.
Nachdem wir dies getan haben, können wir erneut parseJson()
nutzen, um Informationen aus diesem Objekt zu extrahieren. Wir werden die folgenden Felder extrahieren (erneut über den Befehl Spalte basierend auf dieser Spalte hinzufügen):[2]
- Name der Organisation mit
value.parseJson()["organization"]["name"]
- Staat der Organisation mit
value.parseJson()["organization"]["address"]["country"]
- Identifikator der Organisation mit
value.parseJson()["organization"]["disambiguated-organization"]["disambiguated-organization-identifier"]
- Startdatum der Position mit
value.parseJson()["start-date"]["year"]["value"]
- Enddatum der Position mit
value.parseJson()["end-date"]["year"]["value"]
- (Übung: du kannst auch Positionstitel extrahieren, da sie mit position held (P39) als Qualifikator hinzugefügt werden können).
Nun können wir die JSON-Spalten verwerfen und die anderen neu ordnen. Dies kann einfach mithilfe des Befehls Spalten neu ordnen / entfernen unter Spalten bearbeiten im Menü Alle der Hauptspalte erfolgen. Du solltest etwas ähnliches wie dies erhalten:
Abgleichen der Institutionen
Unsere Forscher sind bereits abgeglichen, da wir sie aus unserer SPARQL-Abfrage erhalten haben. Jedoch müssen noch die Institutionen zugeordnet werden. Wir können sie nach ihrem Namen abgleichen, was uns jedoch alleine nicht sehr weit bringen wird, da die Namen häufig mehrfach verwendet werden. Wir können dies durch zwei Parameter verbessern: das Land und den Identifikator.
- ORCID zeigt Länder mit ihrem ISO 3166-1 alpha-2 code (P297) an. Da sich diese Eigenschaft jedoch nicht in dem Datenobjekt der Institution sondern ihres country (P17) befindet, können wir nicht einfach ISO 3166-1 alpha-2 code (P297) im Abgleichsdialog nutzen. Wir müssen zwei Schritte nutzen, um vom Datenobjekt zum Ländercode zu gelangen. Der Wikidata-Abgleichsservice bietet hierfür eine Syntax (inspiriert von SPARQL):
P17/P297
.
- Die Identifikatoren, die wir dem JSON-Inhalt entnommen haben, gehören nicht immer zur gleichen Sorte von Identifikatoren. Sehr häufig ist die Ringgold ID (P3500), gelegentlich werden jedoch GRID ID (P2427) oder DOI (P356) (die in Wikidata als Open Funder Registry funder ID (P3153) gespeichert wird) genutzt. ORCID gibt den Typ des Identifikators an, der genutzt wird, sodass wir dies nutzen können, um sie in unterschiedliche Spalten aufzuteilen und jede Spalte für den Abgleichsdialog zu nutzen. Diese Mal nutzen wir stattdessen einen Trick: da wir wissen, dass die Formate dieser Identifikatoren inkompatibel sind, lassen wir die Abgleichsoberfläche direkt für uns den Identifikator auswählen. Dies kann durch Nutzung des Zeichens
|
für die Einführung einer neuen Disjunktion erreicht werden:P3500|P2427
trifft auf die Vereinigung beider Eigenschaften zu.[3] Wir erhalten den folgenden Abgleichsdialog:
Das Schema erstellen
Nachdem der Abgleich beendet ist, können wir das Schema erstellen. Besuche employer (P108), um etwas über die erwartete Struktur solcher Aussagen zu lernen. Es sieht so aus, als könnte der folgende Code funktionieren:
Wir sind noch nicht ganz fertig, da unsere Tabelle eine Aufzeichnungsstruktur hat: einige der Zellen in der Spalte Forscher sind leer (wenn ein Forscher in seinem Profil mehrere Arbeitgeber hat). Da die Schema-Auswertung zeilenweise erfolgt, werden die entsprechenden Aussagen derzeit übersprungen (da ihr Subjekt leer ist). Um dies zu beheben nutzen wir den Befehl Auffüllen (aus dem Menü Zellen bearbeiten der Spalte Forscher). Dies wird die Werte in der Aufzeichnung verteilen. Du solltest dies auch bei der Spalte "Orcid" machen, da diese Werte in Fundstellen genutzt werden. Nachdem dies erledigt ist, kannst du dir über die Schaltfläche Vorschau eine Vorschau der Bearbeitungen anzeigen lassen und sie über den Befehl Bearbeitungen nach Wikidata hochladen des Menüs Wikidata in der oberen rechten Ecke hochladen. Du solltest Bearbeitungen wie diese oder diese erhalten. Beachte, dass diese Bearbeitungen mehrere Aussagen gleichzeitig hinzufügen, weshalb die automatischen Bearbeitungszusammenfassungen weniger informativ sind. Daher ist es wichtig, eine aussagekräftige Bearbeitungszusammenfassung anzugeben, um dies zu kompensieren.
Was haben wir gemacht?
Wir haben einen ziemlich aufwändigen Prozess für nur 200 Forscher durchlaufen. Die gute Nachricht ist jedoch, das wir das beim nächsten Mal nicht alles machen müssen. Die Schaltfläche Zurücksetzen/Wiederholen bei OpenRefine ist im Vergleich zu dem, was du von anderer Software gewohnt bist, etwas umfangreicher. Du kannst dein Projekt nicht nur auf jeden beliebigen Status zurücksetzen, sondern auch die Beschreibung der Befehle als JSON-Objekt extrahieren und diese Befehle auf ein neues Projekt anwenden. Wenn du die SPARQL-Abfrage erneut (möglicherweise mit einem höheren Limit) ausführst, kannst du also deine Bearbeitungshistorie auf das neue Projekt anwenden. Wenn du OpenRefine nutzt, um deine Bearbeitungen direkt auszuführen, zählt dies auch als Befehl, sodass OpenRefine dies ebenfalls tun wird. Glückwunsch, du hast einen anspruchsvollen Wikidata-Bot geschrieben, ohne eine einzige Zeile Code zu nutzen.
Unten befindet sich eine einfache OpenRefine-Bearbeitungshistorie, die du auf die Ergebnisse der SPARQL-Abfrage anwenden kannst, um die gleichen Ergebnisse zu erreichen:
JSON-Bearbeitungsplan |
---|
[
{
"op": "core/recon",
"description": "Reconcile cells in column researcher to type Q5",
"columnName": "researcher",
"config": {
"mode": "standard-service",
"service": "https://wikidata.reconci.link/en/api",
"identifierSpace": "http://www.wikidata.org/entity/",
"schemaSpace": "http://www.wikidata.org/prop/direct/",
"type": {
"id": "Q5",
"name": "human"
},
"autoMatch": true,
"columnDetails": [],
"limit": 0
},
"engineConfig": {
"mode": "row-based",
"facets": []
}
},
{
"op": "core/column-addition-by-fetching-urls",
"description": "Create column employments at index 2 by fetching URLs based on column orcid using expression grel:\"https://pub.orcid.org/\"+value+\"/employments\"",
"engineConfig": {
"mode": "row-based",
"facets": []
},
"newColumnName": "employments",
"columnInsertIndex": 2,
"baseColumnName": "orcid",
"urlExpression": "grel:\"https://pub.orcid.org/v2.1/\"+value+\"/employments\"",
"onError": "set-to-blank",
"delay": 100,
"cacheResponses": true,
"httpHeadersJson": [
{
"name": "authorization",
"value": ""
},
{
"name": "user-agent",
"value": "OpenRefine 2.8 [TRUNK]"
},
{
"name": "accept",
"value": "application/json"
}
]
},
{
"op": "core/column-addition",
"description": "Create column employment_record at index 3 based on column employments using expression grel:value.parseJson()[\"employment-summary\"].join('###')",
"engineConfig": {
"mode": "row-based",
"facets": []
},
"newColumnName": "employment_record",
"columnInsertIndex": 3,
"baseColumnName": "employments",
"expression": "grel:value.parseJson()[\"employment-summary\"].join('###')",
"onError": "set-to-blank"
},
{
"op": "core/column-removal",
"description": "Remove column employments",
"columnName": "employments"
},
{
"op": "core/multivalued-cell-split",
"description": "Split multi-valued cells in column employment_record",
"columnName": "employment_record",
"keyColumnName": "researcher",
"mode": "separator",
"separator": "###",
"regex": false
},
{
"op": "core/column-addition",
"description": "Create column end_date at index 3 based on column employment_record using expression grel:value.parseJson()[\"end-date\"][\"year\"][\"value\"]+if(value.parseJson()[\"end-date\"][\"month\"] != null, \"-\"+value.parseJson()[\"end-date\"][\"month\"][\"value\"], \"\")",
"engineConfig": {
"mode": "row-based",
"facets": []
},
"newColumnName": "end_date",
"columnInsertIndex": 3,
"baseColumnName": "employment_record",
"expression": "grel:value.parseJson()[\"end-date\"][\"year\"][\"value\"]+if(value.parseJson()[\"end-date\"][\"month\"] != null, \"-\"+value.parseJson()[\"end-date\"][\"month\"][\"value\"], \"\")",
"onError": "set-to-blank"
},
{
"op": "core/column-addition",
"description": "Create column start_date at index 3 based on column employment_record using expression grel:value.parseJson()[\"start-date\"][\"year\"][\"value\"]+if(value.parseJson()[\"start-date\"][\"month\"] != null, \"-\"+value.parseJson()[\"start-date\"][\"month\"][\"value\"], \"\")",
"engineConfig": {
"mode": "row-based",
"facets": []
},
"newColumnName": "start_date",
"columnInsertIndex": 3,
"baseColumnName": "employment_record",
"expression": "grel:value.parseJson()[\"start-date\"][\"year\"][\"value\"]+if(value.parseJson()[\"start-date\"][\"month\"] != null, \"-\"+value.parseJson()[\"start-date\"][\"month\"][\"value\"], \"\")",
"onError": "set-to-blank"
},
{
"op": "core/column-addition",
"description": "Create column organization_id at index 3 based on column employment_record using expression grel:value.parseJson()[\"organization\"][\"disambiguated-organization\"][\"disambiguated-organization-identifier\"]",
"engineConfig": {
"mode": "row-based",
"facets": []
},
"newColumnName": "organization_id",
"columnInsertIndex": 3,
"baseColumnName": "employment_record",
"expression": "grel:value.parseJson()[\"organization\"][\"disambiguated-organization\"][\"disambiguated-organization-identifier\"]",
"onError": "set-to-blank"
},
{
"op": "core/column-addition",
"description": "Create column organization_country at index 3 based on column employment_record using expression grel:value.parseJson()[\"organization\"][\"address\"][\"country\"]",
"engineConfig": {
"mode": "row-based",
"facets": []
},
"newColumnName": "organization_country",
"columnInsertIndex": 3,
"baseColumnName": "employment_record",
"expression": "grel:value.parseJson()[\"organization\"][\"address\"][\"country\"]",
"onError": "set-to-blank"
},
{
"op": "core/column-addition",
"description": "Create column organization_name at index 3 based on column employment_record using expression grel:value.parseJson()[\"organization\"][\"name\"]",
"engineConfig": {
"mode": "row-based",
"facets": []
},
"newColumnName": "organization_name",
"columnInsertIndex": 3,
"baseColumnName": "employment_record",
"expression": "grel:value.parseJson()[\"organization\"][\"name\"]",
"onError": "set-to-blank"
},
{
"op": "core/column-removal",
"description": "Remove column employment_record",
"columnName": "employment_record"
},
{
"op": "core/recon",
"description": "Reconcile cells in column organization_name to type Q43229",
"columnName": "organization_name",
"config": {
"mode": "standard-service",
"service": "https://wikidata.reconci.link/en/api",
"identifierSpace": "http://www.wikidata.org/entity/",
"schemaSpace": "http://www.wikidata.org/prop/direct/",
"type": {
"id": "Q43229",
"name": "organization"
},
"autoMatch": true,
"columnDetails": [
{
"column": "organization_country",
"propertyName": "SPARQL: P17/P297",
"propertyID": "P17/P297"
},
{
"column": "organization_id",
"propertyName": "SPARQL: P3500|P2427",
"propertyID": "P3500|P2427"
}
],
"limit": 0
},
"engineConfig": {
"mode": "row-based",
"facets": []
}
},
{
"op": "core/fill-down",
"description": "Fill down cells in column researcher",
"engineConfig": {
"mode": "row-based",
"facets": []
},
"columnName": "researcher"
},
{
"op": "wikidata/save-wikibase-schema",
"description": "Save Wikibase schema skeleton",
"schema": {
"itemDocuments": [
{
"subject": {
"type": "wbitemvariable",
"columnName": "researcher"
},
"statementGroups": [
{
"property": {
"datatype": "wikibase-item",
"pid": "P108",
"label": "employer",
"type": "wbpropconstant"
},
"statements": [
{
"references": [
{
"snaks": [
{
"prop": {
"datatype": "external-id",
"pid": "P496",
"label": "ORCID iD",
"type": "wbpropconstant"
},
"value": {
"type": "wbstringvariable",
"columnName": "orcid"
}
},
{
"prop": {
"datatype": "time",
"pid": "P813",
"label": "retrieved",
"type": "wbpropconstant"
},
"value": {
"type": "wbdateconstant",
"value": "TODAY"
}
}
]
}
],
"qualifiers": [
{
"prop": {
"datatype": "time",
"pid": "P580",
"label": "start time",
"type": "wbpropconstant"
},
"value": {
"type": "wbdatevariable",
"columnName": "start_date"
}
},
{
"prop": {
"datatype": "time",
"pid": "P582",
"label": "end time",
"type": "wbpropconstant"
},
"value": {
"type": "wbdatevariable",
"columnName": "end_date"
}
}
],
"value": {
"type": "wbitemvariable",
"columnName": "organization_name"
}
}
]
}
],
"nameDescs": []
}
]
}
},
{
"op": "core/fill-down",
"description": "Fill down cells in column orcid",
"engineConfig": {
"mode": "row-based",
"facets": []
},
"columnName": "orcid"
}
]
|
Anmerkungen
- ↑ Dies kann nach Zeitverschwendung aussehen: der Abgleich sollte trivial sein. In der Praxis entnimmt OpenRefine den Datenobjekten Informationen: die Bezeichnung, aber auch den Typ, mit dem sie intern gespeichert werden und auf den du mit dem Ausdruck
cell.recon.match.type
zugreifen kannst. - ↑ Es ist möglich, kürzere Ausdrücke zu erhalten, indem man vorübergehende Spalten erstellt (wie
value.parseJson()["organization"]
). - ↑ Der Einfachheit halber ignorieren wir die DOI (P356) da ihr Format nicht dem in Wikidata genutzten entspricht. Übung: entferne das DOI-Präfix von diesen Werten, sodass du sie ebenfalls zum Abgleichen nutzen kannst.