<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>FastAPI | FDMLab@LABW</title><link>https://fdmlab.landesarchiv-bw.de/tag/fastapi/</link><atom:link href="https://fdmlab.landesarchiv-bw.de/tag/fastapi/index.xml" rel="self" type="application/rss+xml"/><description>FastAPI</description><generator>Wowchemy (https://wowchemy.com)</generator><language>de-de</language><lastBuildDate>Wed, 14 May 2025 00:00:00 +0000</lastBuildDate><image><url>https://fdmlab.landesarchiv-bw.de/media/sharing.jpg</url><title>FastAPI</title><link>https://fdmlab.landesarchiv-bw.de/tag/fastapi/</link></image><item><title>Workshop - Python in OpenRefine</title><link>https://fdmlab.landesarchiv-bw.de/workshop/openrefine-fortgeschrittene/20-python-mit-openrefine/</link><pubDate>Wed, 14 May 2025 00:00:00 +0000</pubDate><guid>https://fdmlab.landesarchiv-bw.de/workshop/openrefine-fortgeschrittene/20-python-mit-openrefine/</guid><description>&lt;p>Wir verwenden Python zusammen mit OpenRefine.&lt;/p>
&lt;blockquote>
&lt;p>Jython in der &lt;a href="https://openrefine.org/docs/manual/jythonclojure#jython" target="_blank" rel="noopener">OpenRefine Dokumentation&lt;/a>.&lt;br>
Einsteiger Workshop &lt;a href="https://fdmlab.landesarchiv-bw.de/workshop/openrefine-fortgeschrittene/19-erweitertes-clustering/">19 Daten mit OpenRefine clustern&lt;/a>.&lt;br>
Blogbeitrag &lt;a href="https://fdmlab.landesarchiv-bw.de/post/2021-08-ner-mit-openrefine-und-spacy/">Named Entity Recognition mit OpenRefine und spaCy&lt;/a>.&lt;/p>
&lt;/blockquote>
&lt;div class="alert alert-note">
Dieser Workshop wurde erstellt mit OpenRefine Version 3.5.0.&lt;br>
Dieser Workshop wurde zuletzt getestet mit OpenRefine Version &lt;strong>3.9.3&lt;/strong>.
&lt;/div>
&lt;div class="alert alert-warning">
&lt;div>
Die in diesem Tutorial behandelten Funktionen sind teilweise erst verfügbar seit der Version OpenRefine &lt;strong>3.9.3&lt;/strong>.
&lt;/div>
&lt;/div>
&lt;h2 id="jython">Jython&lt;/h2>
&lt;p>Quasi überall, wo man in OpenRefine GREL-Expressions schreiben kann, kann man stattdessen auch auf Jython umstellen.
Also beispielsweise für den
&lt;a href="https://fdmlab.landesarchiv-bw.de/workshop/openrefine-einsteiger/03-transformieren/">Transformations-Dialog&lt;/a>,
&lt;a href="https://fdmlab.landesarchiv-bw.de/workshop/openrefine-fortgeschrittene/13-die-welt-der-facets/">Facets&lt;/a> oder neu auch für
&lt;a href="https://fdmlab.landesarchiv-bw.de/workshop/openrefine-fortgeschrittene/19-erweitertes-clustering/">eigene Clustering Methoden&lt;/a>.&lt;/p>
&lt;figure id="figure-bildschirmfoto-des-transformations-dialogs-mit-jython">
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img alt="Bildschirmfoto des Transformations Dialogs mit Jython." srcset="
/workshop/openrefine-fortgeschrittene/20-python-mit-openrefine/screenshot-openrefine-transform-with-jython_hu0d55e479ad7a3691a7fe4df239345fc3_13960_8a8daaf36e58895df4e2bb5849538213.webp 400w,
/workshop/openrefine-fortgeschrittene/20-python-mit-openrefine/screenshot-openrefine-transform-with-jython_hu0d55e479ad7a3691a7fe4df239345fc3_13960_c46dff229eb7cc4e0469107a1fc091c1.webp 760w,
/workshop/openrefine-fortgeschrittene/20-python-mit-openrefine/screenshot-openrefine-transform-with-jython_hu0d55e479ad7a3691a7fe4df239345fc3_13960_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://fdmlab.landesarchiv-bw.de/workshop/openrefine-fortgeschrittene/20-python-mit-openrefine/screenshot-openrefine-transform-with-jython_hu0d55e479ad7a3691a7fe4df239345fc3_13960_8a8daaf36e58895df4e2bb5849538213.webp"
width="530"
height="162"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;figcaption data-pre="Abbildung&amp;nbsp;" data-post=":&amp;nbsp;" class="numbered">
Bildschirmfoto des Transformations Dialogs mit Jython.
&lt;/figcaption>&lt;/figure>
&lt;p>Praktisch wird das Jython-Snippet, welches man wie in Abbildung 1 gezeigt im Dialog eingibt, in eine Python-Funktion gepackt und dann von Jython ausgeführt.
Daher benötigt man im Unterschied zu GREL ein &lt;code>return&lt;/code> Statement, um anzugeben, welcher Wert zurückgegeben wird.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">some_fun_name&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cell&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cells&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">row&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">rowIndex&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">value1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">value2&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Das eingegebene Snippet wird quasi anstelle von &lt;code>...&lt;/code> eingesetzt.&lt;/p>
&lt;p>Zu den in &lt;a href="https://fdmlab.landesarchiv-bw.de/workshop/openrefine-fortgeschrittene/09-arbeiten-mit-grel/#variablen">GREL zur Verfügung stehenden Variablen&lt;/a> haben wir schon einiges geschrieben. Diese stehen mit Ausnahmen in Jython unter OpenRefine ebenfalls zur Verfügung.
Beispielsweise fehlt die Variable &lt;code>columnName&lt;/code> mit dem aktuellen Spaltennamen.&lt;/p>
&lt;p>&lt;a href="https://www.jython.org/" target="_blank" rel="noopener">Jython&lt;/a> selbst ist eine Java Implementierung von Python.
Das bedeutet, dass es innerhalb von Java interpretiert wird und man auch von Jython Code aus auf verfügbare Java Methoden zugreifen kann.&lt;/p>
&lt;p>Leider hat das Projekt den Sprung auf Python 3 nicht geschafft und unterstützt daher nur die Funktionalität von &lt;a href="https://docs.python.org/2.7/" target="_blank" rel="noopener">Python 2.7&lt;/a>.
Das macht auch die &lt;a href="https://github.com/OpenRefine/OpenRefine/wiki/Extending-Jython-with-pypi-modules" target="_blank" rel="noopener">Verwendung von externen Python Bibliotheken&lt;/a> innerhalb von Jython deutlich komplexer.&lt;/p>
&lt;p>Es gibt nur noch selten Python Bibliotheken, die Python 2.7 unterstützen.
Und es können auch nur Python Bibliotheken verwendet werden, die komplett in Python geschrieben wurden.
Einige gerade sehr beliebte Python Projekte verwenden aus Performance Gründen unter der Haube kompilierte Programmiersprachen wie &lt;a href="https://www.c-language.org/" target="_blank" rel="noopener">C&lt;/a>, &lt;a href="https://fortran-lang.org/" target="_blank" rel="noopener">Fortran&lt;/a> oder &lt;a href="https://www.rust-lang.org/" target="_blank" rel="noopener">Rust&lt;/a>.
Diese können unter Jython &lt;strong>nicht&lt;/strong> direkt verwendet werden.&lt;/p>
&lt;p>Praktisch ist die Ausführung von Jython Code in OpenRefine auch deutlich langsamer, als z.B. GREL oder Clojure Code.
Das macht es eher schwierig komplexere Algorithmen z.B. für das Clustering direkt zu hinterlegen.&lt;/p>
&lt;p>Alternativ kann von OpenRefine aus, via Jython auf externen Python Code zugegriffen werden, was im folgenden Teil vertieft wird.&lt;/p>
&lt;h2 id="python">Python&lt;/h2>
&lt;h3 id="standard-setup">Standard Setup&lt;/h3>
&lt;p>Wir verfolgen die Idee von OpenRefine aus auf Python Code zuzugreifen, der &lt;strong>nicht&lt;/strong> innerhalb von Jython ausgeführt wird.
Dafür benötigen wir einen Blick in das Python Projekt Setup.&lt;/p>
&lt;div class="mermaid">---
title: Python Setup
config:
look: handDrawn
theme: neutral
---
flowchart LR
subgraph web[fas:fa-globe Web]
subgraph python[fab:fa-python Python]
python_3_12[fab:fa-python 3.12]
python_3_13[fab:fa-python 3.13]
end
subgraph packages[fas:fa-folder-tree Packages]
typer_0_15[Typer 0.15]
fastapi_0_115[FastAPI 0.115]
spacy_3_8[spaCy 3.8]
my_project_0_1[My Project 0.1]
end
end
subgraph laptop[fas:fa-laptop Laptop]
subgraph local_python[fab:fa-python Python]
local_python_3_12[fab:fa-python 3.12]
local_python_3_13[fab:fa-python 3.13]
end
subgraph local_packages[fas:fa-folder-tree Packages]
local_typer_0_15[Typer 0.15]
local_fastapi_0_115[FastAPI 0.115]
subgraph local_spacy_3_8[spaCy 3.8]
c_code[fab:fa-c Code]
compiled_c_code[fab:fa-c compiled]
end
end
subgraph my_project[My Project]
subgraph venv[fas:fa-folder-open venv]
venv_python_3_12[fab:fa-python 3.12]
venv_typer_0_15[Typer 0.15]
venv_fastapi_0_115[FastAPI 0.115]
venv_spacy_3_8[spaCy 3.8]
end
package_my_project[My Project]
end
end
python --(1)--> local_python
python_3_12 ~~~ local_python_3_12
python_3_13 ~~~ local_python_3_13
packages --(2)--> local_packages
typer_0_15 ~~~ local_typer_0_15
fastapi_0_115 ~~~ local_fastapi_0_115
spacy_3_8 ~~~ local_spacy_3_8
c_code --(3)--> compiled_c_code
local_python_3_12 --(4)--> venv_python_3_12
local_typer_0_15 --(4)--> venv_typer_0_15
local_fastapi_0_115 --(4)--> venv_fastapi_0_115
local_spacy_3_8 --(4)--> venv_spacy_3_8
my_project_0_1 ~~~ package_my_project
package_my_project --(5)--> my_project_0_1
&lt;/div>
&lt;p>Für ein typisches Python Projekt benötigt man fünf Schritte, die durch verschiedene Tools in unterschiedlichem Ausmaß unterstützt werden.&lt;/p>
&lt;h4 id="schritt-1-python-interpreter">Schritt 1: Python Interpreter&lt;/h4>
&lt;p>Zuerst benötigt man einen Python Interpreter, der es ermöglicht Python Code auf dem eigenen Rechner auszuführen.
Zur Verwaltung verschiedener Python Versionen auf dem Rechner gibt es zum Beispiel das Projekt &lt;a href="https://github.com/pyenv/pyenv" target="_blank" rel="noopener">pyenv&lt;/a>.&lt;/p>
&lt;h4 id="schritt-2-dependency-management">Schritt 2: Dependency Management&lt;/h4>
&lt;p>Üblicherweise werden verschiedene externe Bibliotheken benötigt.
Diese stehen zum Beispiel im &lt;a href="https://pypi.org/" target="_blank" rel="noopener">Python Package Index (PyPI)&lt;/a> zur Verfügung und können mit &lt;a href="https://pip.pypa.io/" target="_blank" rel="noopener">pip&lt;/a> verwaltet werden.&lt;/p>
&lt;p>Alternativen zur Verwaltung der Abhängigkeiten sind &lt;a href="https://python-poetry.org/" target="_blank" rel="noopener">Poetry&lt;/a>, &lt;a href="https://pip-tools.readthedocs.io/" target="_blank" rel="noopener">pip-tools&lt;/a> und &lt;a href="https://pipx.pypa.io/" target="_blank" rel="noopener">pipx&lt;/a>.&lt;/p>
&lt;h4 id="schritt-3-compiler">Schritt 3: Compiler&lt;/h4>
&lt;p>Wie schon angesprochen beinhalten manche Python Projekte nicht nur Python Code, sondern auch Code in kompilierten Sprachen.
Wenn für die von euch verwendete Kombination aus CPU und Betriebssystem (noch) kein kompiliertes Binärpaket für die Bibliothek zur Verfügung steht, dann muss das Paket ggf. auf eurem Rechner kompiliert werden.
Dafür werden entsprechende Compiler und Entwicklungspakete auf dem Rechner benötigt.&lt;/p>
&lt;p>Alternativ gibt es auf &lt;a href="https://conda-forge.org/" target="_blank" rel="noopener">conda-forge&lt;/a> eine Sammlung schon kompilierter Pakete.&lt;/p>
&lt;h4 id="schritt-4-virtuelle-umgebung">Schritt 4: Virtuelle Umgebung&lt;/h4>
&lt;p>Erfahrungsgemäß bleibt es nicht bei einem Python Projekt auf dem eigenen Rechner.
Um Konflikte zwischen den Python Versionen und/oder Abhängigkeiten zu vermeiden, ist es üblich für jedes Projekt eine eigene virtuelle Umgebung anzulegen.&lt;/p>
&lt;p>Dafür gibt es das Werkzeug &lt;a href="https://docs.python.org/3/library/venv.html" target="_blank" rel="noopener">venv&lt;/a>, welches früher ein separates Paket war, und seit Python 3.3 in den Standard übernommen wurde.&lt;/p>
&lt;h4 id="schritt-5-package-und-release-management">Schritt 5: Package und Release Management&lt;/h4>
&lt;p>Möchte man das Projekt anschließend für andere z.B. im &lt;em>Python Package Index&lt;/em> zur Verfügung stellen, dann muss es davor noch in das passende Paketformat umgewandelt werden.
Da das für die Benutzung mit OpenRefine nicht relevant ist, wird dieser Schritt hier nicht vertieft.&lt;/p>
&lt;h2 id="uv">uv&lt;/h2>
&lt;p>Das oben beschriebene Setup klingt recht kompliziert und daher abschreckend gerade für kleinere Experimente.
Spätestens seit 2025 setzt sich bei Python Entwicklern hier das Projekt &lt;a href="https://docs.astral.sh/uv/" target="_blank" rel="noopener">uv&lt;/a> durch, welches die Schritte 1, 2, 4 und 5 in einem Werkzeug vereint und das Python Setup dadurch deutlich vereinfacht.&lt;/p>
&lt;p>Für die &lt;a href="https://docs.astral.sh/uv/getting-started/installation/" target="_blank" rel="noopener">Installation von uv&lt;/a> werden mehrere einfache und direkte Wege beschrieben.&lt;/p>
&lt;div class="alert alert-info">
&lt;div>
Sollte man mit dem &lt;em>Windows-Subsystem for Linux&lt;/em> (WSL) arbeiten, dann benötigt man die Installationsanleitung für Linux, auch wenn man Windows als Betriebssystem hat.
&lt;/div>
&lt;/div>
&lt;p>&lt;strong>uv&lt;/strong> hat (quasi) drei für uns relevante Modi:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>uvx&lt;/strong> zum Ausführen von Tools.&lt;/li>
&lt;li>&lt;strong>uv run&lt;/strong> zum Ausführen von Skripten.&lt;/li>
&lt;li>&lt;strong>uv&lt;/strong> zum Arbeiten mit Projekten&lt;br>
(mehrere abhängige Python Dateien, separates Dependency Management).&lt;/li>
&lt;/ul>
&lt;p>Wir konzentrieren uns in dieser Anleitung auf die ersten beiden.&lt;/p>
&lt;h2 id="reconciliation-service">Reconciliation Service&lt;/h2>
&lt;p>Um die Verwendung von &lt;em>uvx&lt;/em> zum Ausführen von externen Python Werkzeugen zu demonstrieren, erstellen wir einen eigenen &lt;em>Reconciliation Service&lt;/em> basierend auf einer CSV-Datei mit &lt;em>uvx&lt;/em> und &lt;a href="https://github.com/gitonthescene/csv-reconcile" target="_blank" rel="noopener">csv-reconcile&lt;/a>.
Zum Herunterladen der CSV-Datei von &lt;a href="https://fdmlab.landesarchiv-bw.de/workshop/openrefine-fortgeschrittene/12-daten-zwischen-projekten-abgleichen/">12 Daten zwischen Projekten abgleichen&lt;/a> verwenden wir &lt;em>uvx&lt;/em> mit &lt;a href="https://httpie.io/" target="_blank" rel="noopener">httpie&lt;/a>.&lt;/p>
&lt;p>In einem Terminal wie iTerm, xTerm, Bash oder Powershell führen wir den folgenden Befehl aus.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">uvx --from httpie http --download GET https://fdmlab.landesarchiv-bw.de/data/openrefine-workshop/12_staedte-in-bw-geokoordinaten.csv
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Dieses kompakte Snippet lädt die zu diesem Zeitpunkt standardmäßige Python Version und die aktuellste Version von &lt;em>httpie&lt;/em> herunter, erstellt damit eine virtuelle Umgebung (&lt;em>venv&lt;/em>) und führt darin den Befehl &lt;code>http --download GET ...&lt;/code> aus.&lt;/p>
&lt;p>Den &lt;em>Reconciliation Service&lt;/em> basierend auf der im letzten Schritt heruntergeladenen Datei erstellen wir in zwei Schritten:&lt;/p>
&lt;ol>
&lt;li>Datenbank für &lt;em>Reconcilation Service&lt;/em> erstellen&lt;/li>
&lt;li>&lt;em>Reconciliation Service&lt;/em> starten&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">uvx csv-reconcile init 12_staedte-in-bw-geokoordinaten.csv &lt;span class="s2">&amp;#34;GND ID&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;Name&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Mit diesem Snippet erstellen wir die Datenbank für den &lt;em>Reconciliation Service&lt;/em> und legen die Spalte &amp;ldquo;GND ID&amp;rdquo; als ID-Spalte fest und die Spalte &amp;ldquo;Name&amp;rdquo; als Standardspalte für den Abgleich.
Den &lt;em>Reconciliation Service&lt;/em> selbst starten wir mit dem folgenden Befehl.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">uvx csv-reconcile serve
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In OpenRefine können wir den &lt;em>Reconciliation Service&lt;/em> anschließend hinzufügen mit: &lt;code>http://127.0.0.1:5000/reconcile&lt;/code>.&lt;/p>
&lt;h3 id="datenbank-mit-eigener-konfiguration">Datenbank mit eigener Konfiguration&lt;/h3>
&lt;p>Der oben erstelle &lt;em>Reconciliation Service&lt;/em> ist recht generisch und es fehlt z.B. noch die Vorschaufunktion für die einzelnen Elemente.
Um den Service nach unseren Anforderungen zu konfigurieren, erstellen wir eine Datei &lt;code>config.py&lt;/code> mit dem folgenden Inhalt.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">SERVER_NAME&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;localhost:5000&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">CSVKWARGS&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s1">&amp;#39;delimiter&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;,&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;quotechar&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;&amp;#34;&amp;#39;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">CSVENCODING&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;UTF-8&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">MANIFEST&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Städte in BW Reconciliation Service&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;identifierSpace&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;https://normdaten.landesarchiv-bw.de/ids&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;schemaSpace&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;https://normdaten.landesarchiv-bw.de/schema&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;extend&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;propose_properties&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;service_url&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;http://localhost:5000&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;service_path&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;/properties&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;preview&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;url&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;http://localhost:5000/preview/{{id}}&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;width&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">500&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;height&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">300&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Anschließend erstellen wir die &lt;em>Reconciliation&lt;/em> Datenbank mit der neuen Konfiguration.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">uvx csv-reconcile init --config config.py 12_staedte-in-bw-geokoordinaten.csv &lt;span class="s2">&amp;#34;GND ID&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;Name&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Der neue Service ist nach dem Starten unter &lt;code>http://localhost:5000/reconcile&lt;/code> erreichbar.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">uvx csv-reconcile serve
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="alert alert-info">
&lt;div>
In OpenRefine müssen wir den CSV &lt;em>Reconciliation Service&lt;/em> aktuell bei jeder Änderung der &lt;strong>Konfiguration&lt;/strong> neu hinzufügen.
&lt;/div>
&lt;/div>
&lt;p>Der &lt;em>Reconciliation Service&lt;/em> kann jetzt wie in Abbildung 2 mit dem Beispiel aus &lt;a href="https://fdmlab.landesarchiv-bw.de/workshop/openrefine-fortgeschrittene/12-daten-zwischen-projekten-abgleichen/">12 Daten zwischen Projekten abgleichen&lt;/a> getestet werden.&lt;/p>
&lt;figure id="figure-screencast-zum-reconciling-mit-openrefine-und-csv-datei">
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img alt="Screencast zum *Reconciling* mit OpenRefine und CSV-Datei."
src="https://fdmlab.landesarchiv-bw.de/workshop/openrefine-fortgeschrittene/20-python-mit-openrefine/screencast-openrefine-csv-reconciling.gif"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;figcaption data-pre="Abbildung&amp;nbsp;" data-post=":&amp;nbsp;" class="numbered">
Screencast zum &lt;em>Reconciling&lt;/em> mit OpenRefine und CSV-Datei.
&lt;/figcaption>&lt;/figure>
&lt;h2 id="eigene-skripte">Eigene Skripte&lt;/h2>
&lt;p>Um &lt;em>Command Line Interfaces&lt;/em> (CLIs) zu erstellen, gibt es in Python die Standardbibliothek &lt;a href="https://docs.python.org/3/library/argparse.html" target="_blank" rel="noopener">argparse&lt;/a>.
Meistens möchte man aber &amp;ldquo;nur mal schnell&amp;rdquo; aus einer Python Funktion ein Werkzeug erstellen, welches von der Kommandozeile aufgerufen werden kann.
Ein nützliches Python Projekt dafür ist &lt;a href="https://typer.tiangolo.com/" target="_blank" rel="noopener">typer&lt;/a>.&lt;/p>
&lt;h3 id="typer-hello-world-beispiel">Typer Hello World Beispiel&lt;/h3>
&lt;p>Ein einfaches &amp;ldquo;Hello World&amp;rdquo; Beispiel mit &lt;em>uv&lt;/em> und &lt;em>Typer&lt;/em> ist im folgenden Quellcode aus &lt;code>hello_world.py&lt;/code> abgebildet.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># /// script&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># requires-python = &amp;#34;&amp;gt;=3.12&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># dependencies = [&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># &amp;#34;typer&amp;#34;,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ///&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">typer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">hello_world&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Hello World&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">typer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hello_world&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Zu Beginn wird über &lt;a href="https://packaging.python.org/en/latest/specifications/inline-script-metadata/#script-type" target="_blank" rel="noopener">Inline Metadaten&lt;/a> definiert, dass das Skript mit Python 3.12 oder neuer ausgeführt werden soll und das Skript zusätzlich die externe Abhängigkeit &lt;a href="https://typer.tiangolo.com/" target="_blank" rel="noopener">typer&lt;/a> benötigt.&lt;/p>
&lt;p>Das Werkzeug &lt;em>uv&lt;/em> unterstützt diese inline Metadaten, so dass zum Ausführen dieses Skriptes in einem Terminal lediglich &lt;code>uv run hello_world.py&lt;/code> ausgeführt werden muss.&lt;/p>
&lt;p>Das Werkzeug &lt;em>Typer&lt;/em> übersetzt den CLI-Aufruf für die Python Funktion, und validiert und konvertiert dabei die Eingabedaten.&lt;/p>
&lt;h3 id="typer-beispiel-mit-parametern">Typer Beispiel mit Parametern&lt;/h3>
&lt;p>Wir beginnen mit einem einfachen Beispiel.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># /// script&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># requires-python = &amp;#34;&amp;gt;=3.12&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># dependencies = [&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># &amp;#34;typer&amp;#34;,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ///&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">typer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">typer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">add&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Die Funktion &lt;code>add&lt;/code> soll zwei Parameter &lt;code>a&lt;/code> und &lt;code>b&lt;/code> miteinander addieren und dann ausgeben.
Zum Ausgeben des Ergebnisses verwenden wir die Funktion &lt;code>print&lt;/code>.
Das Werkzeug &lt;code>typer&lt;/code> erkennt automatisch die beiden Parameter &lt;code>a&lt;/code> und &lt;code>b&lt;/code> und meldet, wenn diese nicht gesetzt sind.
Unter &lt;code>uv run add.py --help&lt;/code> erhalten wir einen Informationstext zur Benutzung unseres CLI-Tools.&lt;/p>
&lt;p>Standardmäßig werden die Parameter als Text interpretiert.
Mit so genannten &lt;a href="https://docs.python.org/3/library/typing.html" target="_blank" rel="noopener">Type Hints&lt;/a> können wir &lt;em>Typer&lt;/em> mitteilen, dass wir hier Zahlen erwarten.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Es ist auch möglich, optionale Parameter wie &lt;code>c&lt;/code> mit anzugeben.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">c&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">c&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Individuelle Konfigurationen können mit &lt;a href="https://docs.python.org/3/library/typing.html#typing.Annotated" target="_blank" rel="noopener">Type Annotations&lt;/a> hinterlegt werden.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">c&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Annotated&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">typer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Argument&lt;/span>&lt;span class="p">()]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">c&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Mit den Annotationen lässt sich bei Bedarf der Text für die Hilfe weiter spezifizieren.
Hier das fertige Skript mit ausführlicherem Hilfstext.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># /// script&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># requires-python = &amp;#34;&amp;gt;=3.12&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># dependencies = [&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># &amp;#34;typer&amp;#34;,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ///&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">typer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing_extensions&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Annotated&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">add&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">a&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Annotated&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">typer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Argument&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">help&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;The first number to add.&amp;#34;&lt;/span>&lt;span class="p">)],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">b&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Annotated&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">typer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Argument&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">help&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;The second number to add.&amp;#34;&lt;/span>&lt;span class="p">)],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">c&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Annotated&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">typer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Argument&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">help&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;The optionally third number to add.&amp;#34;&lt;/span>&lt;span class="p">)]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> Add the numbers in `a` and `b` and optionally `c` if `c` is provided.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">c&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">typer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">add&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Das Praktische an &lt;em>Typer&lt;/em> ist, dass man &amp;ldquo;nur zum Ausprobieren&amp;rdquo; lediglich die &lt;em>type hints&lt;/em> und den Aufruf von &lt;em>Typer&lt;/em> im Skript ergänzen muss.
Bei Bedarf können ausführlichere Hilfstexte oder Validierungen hinterlegt werden, deren Ergebnis in Abbildung 3 gezeigt ist.&lt;/p>
&lt;figure id="figure-screenshot-terminal-mit-von-typer-generierten-hilfstext">
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img alt="Screenshot Terminal mit von *Typer* generierten Hilfstext." srcset="
/workshop/openrefine-fortgeschrittene/20-python-mit-openrefine/screenshot-terminal-typer_hu0d8bcb7ebbd1732e66a8e438ddf2504c_22144_b0035b32e843cd9c416ec72c36cdae2a.webp 400w,
/workshop/openrefine-fortgeschrittene/20-python-mit-openrefine/screenshot-terminal-typer_hu0d8bcb7ebbd1732e66a8e438ddf2504c_22144_02ae2c3f464e13330fa05f52c91a98b6.webp 760w,
/workshop/openrefine-fortgeschrittene/20-python-mit-openrefine/screenshot-terminal-typer_hu0d8bcb7ebbd1732e66a8e438ddf2504c_22144_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://fdmlab.landesarchiv-bw.de/workshop/openrefine-fortgeschrittene/20-python-mit-openrefine/screenshot-terminal-typer_hu0d8bcb7ebbd1732e66a8e438ddf2504c_22144_b0035b32e843cd9c416ec72c36cdae2a.webp"
width="542"
height="231"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;figcaption data-pre="Abbildung&amp;nbsp;" data-post=":&amp;nbsp;" class="numbered">
Screenshot Terminal mit von &lt;em>Typer&lt;/em> generierten Hilfstext.
&lt;/figcaption>&lt;/figure>
&lt;h3 id="zugriff-von-openrefine-auf-python-skript">Zugriff von OpenRefine auf Python Skript&lt;/h3>
&lt;p>Von OpenRefine heraus, lässt sich mit Jython recht unkompliziert auf das externe Python Skripte zugreifen.
Es muss lediglich der Pfad des Skriptes in dem Jython Snippet unten angepasst werden.
Ggf. müssen auch noch Parameter für das Skript ergänzt werden.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">subprocess&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">script&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;C:&lt;/span>&lt;span class="se">\\&lt;/span>&lt;span class="s2">...&lt;/span>&lt;span class="se">\\&lt;/span>&lt;span class="s2">python_script.py&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">res&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">subprocess&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_output&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;uv&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;run&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">script&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\&amp;#34;&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\&amp;#34;&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">shell&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">return&lt;/span> &lt;span class="n">res&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="probleme-beim-zugriff-von-openrefine-auf-python-skripte">Probleme beim Zugriff von OpenRefine auf Python Skripte&lt;/h3>
&lt;p>Der direkte Zugriff auf ein Skript ist erst einmal relativ performant, hat in der Praxis aber mehrere Probleme.&lt;/p>
&lt;ol>
&lt;li>Die korrekte Übergabe von Daten an das Skript via Parametern ist nicht ganz so einfach. Besonders, wenn die Daten größer sind ggf. Sonderzeichen enthalten, &amp;hellip;&lt;/li>
&lt;li>Man ist selbst für die Fehlerbehandlung verantwortlich. Also dass die Skripte auch irgendwann beendet werden, bei zu langer Laufzeit abgebrochen werden, usw.&lt;/li>
&lt;li>Es wird lediglich Text zurückgegeben. Die Interpretation in ein bestimmtes Format muss dann in Jython oder in einem Nachbearbeitungsschritt erfolgen.&lt;/li>
&lt;li>Manche Skripte oder Tools müssen initial erst einmal Daten in den Arbeitsspeicher laden. Insbesondere bei Machine Learning Anwendungen kann das mehrere Sekunden dauern. Dies erfolgt bei jedem Skriptaufruf wieder erneut.&lt;/li>
&lt;/ol>
&lt;p>Daher ist es wesentlich stabiler stattdessen mit eigenen Web-APIs zu arbeiten.&lt;/p>
&lt;h2 id="eigene-web-apis-mit-fastapi">Eigene Web-APIs mit FastAPI&lt;/h2>
&lt;div class="mermaid">---
title: Script vs Web-API
config:
look: handDrawn
theme: neutral
---
flowchart LR
subgraph openrefine["OpenRefine"]
subgraph jython["Jython"]
jython_code["Jython Code"]
end
end
subgraph venv
script["fab:fa-python Skript"]
end
subgraph uvicorn["Uvicorn"]
subgraph fastapi["FastAPI"]
python_code["fab:fa-python Code"]
end
end
jython_code -.-> script
jython_code -.-> script
jython_code -.-> script
jython_code -.-> script
jython_code -.-> script
jython_code -.-> script
jython_code -.-> script
jython_code -.-> script
jython_code -.-> script
jython_code &lt;--HTTP--> python_code
&lt;/div>
&lt;p>Für Web-APIs gibt es etablierte und stabile Protokolle zum Austausch von Daten.
Ähnlich wie mit &lt;em>Typer&lt;/em> können wir mit &lt;a href="https://fastapi.tiangolo.com/" target="_blank" rel="noopener">FastAPI&lt;/a> unsere Python-Funktionen als Web-API zur Verfügung stellen.
Mit einem Server wie &lt;a href="https://www.uvicorn.org/" target="_blank" rel="noopener">uvicorn&lt;/a> sorgen wir dabei für eine Stabilität der angestoßenen Prozesse für die Abfragen.&lt;/p>
&lt;h3 id="beispielcode-für-spacy">Beispielcode für spaCy&lt;/h3>
&lt;p>Das folgende Skript lädt ein deutschsprachiges Modell von &lt;a href="https://spacy.io" target="_blank" rel="noopener">spaCy&lt;/a> und berechnet über &lt;a href="https://de.wikipedia.org/wiki/Worteinbettung" target="_blank" rel="noopener">Word Embeddings&lt;/a> die Ähnlichkeit bzw. hier die Distanz der beiden übergebenen Texte.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># /// script&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># requires-python = &amp;#34;&amp;gt;=3.12,&amp;lt;3.13&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># dependencies = [&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># &amp;#34;spacy&amp;#34;,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># &amp;#34;typer&amp;#34;,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># &amp;#34;de_core_news_lg @ https://github.com/explosion/spacy-models/releases/download/de_core_news_lg-3.8.0/de_core_news_lg-3.8.0.tar.gz&amp;#34;,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ///&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">typer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">de_core_news_lg&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">model&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">spacy_distance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">nlp&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">model&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">doc_a&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nlp&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">doc_b&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nlp&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">b&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">dist&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">doc_a&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">similarity&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">doc_b&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dist&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">typer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">spacy_distance&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Das Skript hat den eindeutigen Nachteil, dass es bei jedem Aufruf das &amp;ldquo;große&amp;rdquo; Sprachmodell laden muss, was mehrere Sekunden dauert.
Hier im Vergleich das Skript &lt;code>spacy_distance_fastapi.py&lt;/code>, welches die Funktionalität via &lt;a href="https://fastapi.tiangolo.com/" target="_blank" rel="noopener">FastAPI&lt;/a> zur Verfügung stellt.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># /// script&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># requires-python = &amp;#34;&amp;gt;=3.12,&amp;lt;3.13&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># dependencies = [&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># &amp;#34;spacy&amp;#34;,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># &amp;#34;fastapi&amp;#34;,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># &amp;#34;uvicorn&amp;#34;,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># &amp;#34;de_core_news_lg @ https://github.com/explosion/spacy-models/releases/download/de_core_news_lg-3.8.0/de_core_news_lg-3.8.0.tar.gz&amp;#34;,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ///&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">de_core_news_lg&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">model&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">uvicorn&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">fastapi&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">FastAPI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">FastAPI&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">nlp&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">model&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@app.get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;/distance&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">spacy_distance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">float&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">doc_a&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nlp&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">doc_b&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nlp&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">b&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">dist&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">doc_a&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">similarity&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">doc_b&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">dist&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">uvicorn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;spacy_distance_fastapi:app&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">host&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;127.0.0.1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">port&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">5000&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Die Verarbeitungspipeline für die Texte mit dem &amp;ldquo;großen&amp;rdquo; Sprachmodell wird außerhalb der Python Methode definiert, weshalb das Modell pro Aufruf nur einmal aufgerufen werden muss.
Im Vergleich zum direkten Skriptaufruf, ist der Zugriff auf unsere lokale Web-API zwar etwas langsamer.
Wir profitieren aber von der Müglichkeit ladeintensive Prozesse nur einmal beim Starten auszuführen, sowie von dem zusätzlichen Protokoll im Bereich Fehlerbehandlung, Validierung von Eingaben und Antworten.&lt;/p>
&lt;p>Ähnlich wie bei &lt;em>Typer&lt;/em> übernimmt &lt;em>FastAPI&lt;/em> hier das Mapping der Benutzeranfrage auf die entsprechende Python Funktion.
Gleich wie bei &lt;em>Typer&lt;/em> werden &lt;em>Type Hints&lt;/em> für das Mapping, die Dokumentation und die Validierung verwendet.
Auch bei &lt;em>FastAPI&lt;/em> kann die Dokumentation mit weiteren Annotationen erweitert und personalisiert werden.&lt;/p>
&lt;p>Somit lassen sich mit FastAPI eigene Web-APIs ähnlich leicht erstellen, wie einfache Python Skripte mit &lt;em>Typer&lt;/em>.
Ausführliche Beispiele stehen im Anhang bzw. als GitHub Gist unter &lt;a href="https://gist.github.com/b2m/cadf88263f7978be96c164a89968c44c" target="_blank" rel="noopener">spacy_fastapi.py&lt;/a> und &lt;a href="https://gist.github.com/b2m/91f3b812bcf0975c4d2cb3230099366a" target="_blank" rel="noopener">rapidfuzz_fastapi.py&lt;/a> zur Verfügung.&lt;/p>
&lt;p>Von OpenRefine heraus kann man via Jython auf die (lokalen) Web-APIs zugreifen.&lt;/p>
&lt;h3 id="zugriff-via-get">Zugriff via GET&lt;/h3>
&lt;p>Web-APIs unterscheiden für den Abruf von Daten zwischen &lt;em>GET-&lt;/em> und &lt;em>POST-Requests&lt;/em> (Anfragen).
Für &lt;em>GET-Requests&lt;/em> gibt es in OpenRefine die Möglichkeit den Dialog &lt;a href="https://openrefine.org/docs/manual/columnediting#add-column-by-fetching-urls" target="_blank" rel="noopener">Add column by fetching URLs&lt;/a> zum Nachladen von Daten zu verwenden.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="s2">&amp;#34;http://localhost:5000/ner?text=&amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">escape&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;url&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Hier kann man mit einfachem GREL die benötigte URL zusammen bauen.
Man sollte jedoch daran denken, die Parameter mit &lt;a href="https://openrefine.org/docs/manual/grelfunctions#escapes-s-mode" target="_blank" rel="noopener">escape&lt;/a> für die URL kompatibel aufzubereiten.&lt;/p>
&lt;p>In Abbildung 4 ist der Dialog aus &lt;a href="https://fdmlab.landesarchiv-bw.de/workshop/openrefine-fortgeschrittene/18-nachladen-von-geokoordinaten/">18 Nachladen von Geokoordinaten&lt;/a> gezeigt.&lt;/p>
&lt;figure id="figure-bildschirmfoto-des-dialogs-zum-nachladen-von-urls-für-geonames">
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img src="../18-nachladen-von-geokoordinaten/screenshot-openrefine-add-by-url.png" alt="Bildschirmfoto des Dialogs zum Nachladen von URLs für GeoNames." loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;figcaption data-pre="Abbildung&amp;nbsp;" data-post=":&amp;nbsp;" class="numbered">
Bildschirmfoto des Dialogs zum Nachladen von URLs für GeoNames.
&lt;/figcaption>&lt;/figure>
&lt;p>Möchte man eine Web-API z.B. von einem &lt;em>Facet&lt;/em> oder einem Clustering-Dialog heraus aufrufen, benötigt man dafür das folgende Jython-Snippet um einen validen &lt;em>GET-Request&lt;/em> zu erstellen.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">json&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nn">urllib&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nn">urllib2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;http://localhost:5000/ner&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">request_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">urllib&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">urlencode&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">encode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;utf-8&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">urllib2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">urlopen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s2">&amp;#34;?&amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">request_data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">return&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dumps&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">ensure_ascii&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="alert alert-note">
&lt;div>
&lt;strong>Zur Erinnerung:&lt;/strong> Es gibt Python Bibliotheken wie &lt;a href="https://requests.readthedocs.io/" target="_blank" rel="noopener">requests&lt;/a>, die das Erstellen und Verarbeiten von Web-Requests deutlich vereinfachen. Wir arbeiten in OpenRefine aber mit Jython und können daher nicht einfach auf externe Bibliotheken zugreifen.
&lt;/div>
&lt;/div>
&lt;h3 id="zugriff-via-get-und-form">Zugriff via GET und Form&lt;/h3>
&lt;p>Manche Web-APIs erwarten einen &lt;em>GET-Request&lt;/em> mit zusätzlichem Nutzdaten z.B. aus einem Formular.
Das lässt sich in Jython / Python 2.7 mit folgendem Snippet umsetzen.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">json&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nn">urllib&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nn">urllib2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;http://localhost:5000/ner&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">request_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">urllib&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">urlencode&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">encode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;utf-8&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">urllib2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">urlopen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">request_data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">return&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dumps&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">ensure_ascii&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Der Unterschied zum letzten Beispiel ist, dass &lt;code>request_data&lt;/code> als zusätzliche Nutzdaten mit einem Komma getrennt an &lt;code>urllib2&lt;/code> übergeben werden, anstatt sie mit an die URL zu kodieren.&lt;/p>
&lt;h3 id="zugriff-via-post">Zugriff via POST&lt;/h3>
&lt;p>Der in den Beispielen im Anhang verwende &lt;em>Use-Case&lt;/em> ist das Verwenden von &lt;em>POST-Requests&lt;/em>.
Hier werden die Nutzdaten als &lt;a href="https://www.json.org/" target="_blank" rel="noopener">JSON&lt;/a> kodiert an den &lt;em>Request&lt;/em> angehängt.
Das macht die Kodierung der Daten in der Anfrage komplexer, gleichzeitig aber die Übermittlung stabiler.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">json&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nn">urllib&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nn">urllib2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;http://localhost:5000/ner&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">request_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dumps&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">encode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;utf-8&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">request&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">urllib2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Request&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">request_data&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;Content-Type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;application/json&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">urllib2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">urlopen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">request&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">return&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dumps&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">ensure_ascii&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Der komplette Workflow vom Starten des Web-Service, bis zur Verwendung von &lt;a href="https://rapidfuzz.github.io/RapidFuzz/" target="_blank" rel="noopener">RapidFuzz&lt;/a> als eigene Clustering Methode in OpenRefine ist in dem Screencast in Abbildung 5 gezeigt.&lt;/p>
&lt;figure id="figure-screencast-zur-verwendung-von-uv-fastapi-und-rapidfuzz-als-eigene-clustering-methode-bei-openrefine">
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img alt="Screencast zur Verwendung von uv, FastAPI und RapidFuzz als eigene Clustering Methode bei OpenRefine."
src="https://fdmlab.landesarchiv-bw.de/workshop/openrefine-fortgeschrittene/20-python-mit-openrefine/screencast-openrefine-custom-clustering-distance-rapidfuzz.gif"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;figcaption data-pre="Abbildung&amp;nbsp;" data-post=":&amp;nbsp;" class="numbered">
Screencast zur Verwendung von uv, FastAPI und RapidFuzz als eigene Clustering Methode bei OpenRefine.
&lt;/figcaption>&lt;/figure>
&lt;h2 id="fazit">Fazit&lt;/h2>
&lt;p>Kleinere Skripte mit Jython / Python 2.7 in OpenRefine sind möglich, jedoch gerade beim Clustering oder bei der Nachnutzung von externen Python Projekten problematisch.&lt;/p>
&lt;p>Mit &lt;a href="https://typer.tiangolo.com/" target="_blank" rel="noopener">Typer&lt;/a> und &lt;a href="ttps://fastapi.tiangolo.com/">FastAPI&lt;/a> lassen sich aus Python Funktionen heraus schnell CLI-Tools oder Web-APIs erstellen.
Die beiden Werkzeuge ermöglichen es sowohl mit wenig Aufwand Prototypen zu erstellen, als auch diese zu dokumentierten praktischen Tools zu erweitern.&lt;/p>
&lt;p>Mit &lt;a href="https://docs.astral.sh/uv/" target="_blank" rel="noopener">uv&lt;/a> sind die Hürden für das Erstellen von indivduellen Python Umgebungen für einzelne Skripte sehr stark gesenkt worden.
Das reduziert nicht nur den Entwicklungsaufwand, sondern erleichtert auch die Nachnutzung von Skripten für Nutzerinnen und Nutzer.&lt;/p>
&lt;p>Mit der Kombination aus Jython, uv und wahlweise Typer oder FastAPI lassen sich dadurch modernste Python Anwendungen direkt mit OpenRefine verbinden.&lt;/p>
&lt;h2 id="anhang">Anhang&lt;/h2>
&lt;h3 id="fastapi-wrapper-für-spacy">FastAPI Wrapper für spaCy&lt;/h3>
&lt;script type="application/javascript" src="https://gist.github.com/b2m/cadf88263f7978be96c164a89968c44c.js?file=spacy_fastapi.py">&lt;/script>
&lt;h3 id="fastapi-wrapper-für-rapidfuzz">FastAPI Wrapper für RapidFuzz&lt;/h3>
&lt;script type="application/javascript" src="https://gist.github.com/b2m/91f3b812bcf0975c4d2cb3230099366a.js?file=rapidfuzz_fastapi.py">&lt;/script></description></item><item><title>Named Entity Recognition mit OpenRefine und spaCy</title><link>https://fdmlab.landesarchiv-bw.de/post/2021-08-ner-mit-openrefine-und-spacy/</link><pubDate>Tue, 10 Aug 2021 00:00:00 +0000</pubDate><guid>https://fdmlab.landesarchiv-bw.de/post/2021-08-ner-mit-openrefine-und-spacy/</guid><description>&lt;p>In diesem Artikel beschreiben wir unsere Versuche mit OpenRefine ein &amp;ldquo;Named Entity Recognition&amp;rdquo; mit spaCy durchzuführen, um die Entities anschließend mit der Gemeinsamen Normdatei (GND) abzugleichen.&lt;/p>
&lt;p>OpenRefine kann &lt;a href="https://docs.openrefine.org/manual/jythonclojure#jython" target="_blank" rel="noopener">Python Code ausführen&lt;/a>.
Dann sollte es doch auch möglich sein, damit eine &amp;ldquo;Named Entity Recognition&amp;rdquo; mit dem auf Python basierten Framework &lt;a href="https://spacy.io" target="_blank" rel="noopener">spaCy&lt;/a> durchzuführen.
⚠️ Leider hat es sich gezeigt, dass sich die Umsetzung technischer gestaltet als erwartet.&lt;/p>
&lt;p>Da wir uns &lt;a href="https://fdmlab.landesarchiv-bw.de/post/2021-06-der-fdmlab-blog/">vornahmen auch negative Erfahrungen zu berichten&lt;/a> gehen wir wie folgt vor:&lt;/p>
&lt;ol>
&lt;li>Wir beschreiben, wie wir uns die Umsetzung vorgestellt hätten.&lt;/li>
&lt;li>Wir erklären, weshalb das nicht funktioniert.&lt;/li>
&lt;li>Wir zeigen eine flexible, dafür technisch aufwendigere Lösung.&lt;/li>
&lt;/ol>
&lt;h2 id="versuch-spacy-in-openrefine-ausführen">Versuch: spaCy in OpenRefine ausführen&lt;/h2>
&lt;p>OpenRefine bringt mit &lt;a href="https://www.jython.org/" target="_blank" rel="noopener">Jython&lt;/a> eine Java Implementierung für &lt;a href="https://www.python.org/" target="_blank" rel="noopener">Python&lt;/a> mit.
Theoretisch ist es also möglich das Python Framework spaCy als Python Paket zu installieren und direkt in OpenRefine auszuführen.&lt;/p>
&lt;p>Unser gedachtes Vorgehen: wir nehmen an, dass wir auf einem Windows System sind.
Dort gehen wir mit dem Dateiexplorer in den OpenRefine Ordner mit integrierter Java Umgebung und öffnen dort eine PowerShell.
Dann aktivieren wir mit Jython den Python Paketmanager pip (Zeile 1), installieren spaCy (Zeile 2) und laden ein deutsches Sprachmodell (Zeile 3).&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="p">.\&lt;/span>&lt;span class="n">server&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">target&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">jre&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">bin&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">java&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">exe&lt;/span> &lt;span class="n">-jar&lt;/span> &lt;span class="p">.\&lt;/span>&lt;span class="n">webapp&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">extensions&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">jython&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">module&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="nb">MOD-INF&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">lib&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="nb">jython-standalone&lt;/span>&lt;span class="p">-&lt;/span>&lt;span class="mf">2.7&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">2&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">jar&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">ensurepip&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">.\&lt;/span>&lt;span class="n">server&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">target&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">jre&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">bin&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">java&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">exe&lt;/span> &lt;span class="n">-jar&lt;/span> &lt;span class="p">.\&lt;/span>&lt;span class="n">webapp&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">extensions&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">jython&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">module&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="nb">MOD-INF&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">lib&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="nb">jython-standalone&lt;/span>&lt;span class="p">-&lt;/span>&lt;span class="mf">2.7&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">2&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">jar&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">pip&lt;/span> &lt;span class="n">install&lt;/span> &lt;span class="n">spacy&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">.\&lt;/span>&lt;span class="n">server&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">target&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">jre&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">bin&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">java&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">exe&lt;/span> &lt;span class="n">-jar&lt;/span> &lt;span class="p">.\&lt;/span>&lt;span class="n">webapp&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">extensions&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">jython&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">module&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="nb">MOD-INF&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">lib&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="nb">jython-standalone&lt;/span>&lt;span class="p">-&lt;/span>&lt;span class="mf">2.7&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">2&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">jar&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">spacy&lt;/span> &lt;span class="n">download&lt;/span> &lt;span class="n">de_core_news_sm&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Hätte das funktioniert, könnten wir nun im &amp;ldquo;expression&amp;rdquo; Editor zum Beispiel beim Hinzufügen oder Transformieren von Spalten von GREL auf Jython umstellen und mit folgendem Code ein &amp;ldquo;Named Entity Recognition&amp;rdquo; mit spaCy auf den Inhalten durchführen.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">spacy&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">nlp&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">spacy&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;de_core_news_sm&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">doc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nlp&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">text&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s2">&amp;#34;||&amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">ent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">label_&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">ent&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">doc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ents&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>🚫 Leider funktioniert die Installation von spaCy in Jython via pip nicht.&lt;/p>
&lt;h3 id="technische-hintergründe">Technische Hintergründe&lt;/h3>
&lt;p>Jython implementiert aktuell Python in Version &lt;strong>2.7&lt;/strong>.
Seit Januar 2020 wird Python in Version &lt;strong>2&lt;/strong> jedoch nicht mehr weiterentwickelt und auch nicht mehr mit Bugfixes versorgt (Status &lt;em>end-of-life&lt;/em>).
Die aktuelle Version von Python ist Version &lt;strong>3.9&lt;/strong>.&lt;/p>
&lt;p>Entsprechend sind Bibliotheken in aktueller Version häufig auch nur noch mit Unterstützung für Python &lt;strong>3&lt;/strong> zu finden.
Eine Neuentwicklung von Jython um Python in Version &lt;strong>3&lt;/strong> zu unterstützen, oder basierend auf neuer Technologie wie &lt;a href="https://www.graalvm.org/" target="_blank" rel="noopener">GraalVM&lt;/a> nutzbar zu machen, ist nach wie vor in Planung/Umsetzung (siehe &lt;a href="https://www.jython.org/jython-3-roadmap" target="_blank" rel="noopener">Python 3 Roadmap&lt;/a>).&lt;/p>
&lt;p>Aber auch ältere Versionen von Bibliotheken mit Unterstützung für Python &lt;strong>2&lt;/strong> sind nicht &lt;em>einfach&lt;/em> via pip zu installieren.
Das liegt daran, dass Jython auf Java basiert, viele Python Bibliotheken unter der Haube jedoch Abhängigkeiten zur Programmiersprache C haben.&lt;/p>
&lt;h2 id="alternative-spacy-separat-ausführen">Alternative: spaCy separat ausführen&lt;/h2>
&lt;p>Wenn wir spaCy nicht in OpenRefine zum Laufen bekommen, dann können wir es stattdessen separat als Webservice betreiben.&lt;/p>
&lt;div class="alert alert-note">
&lt;div>
In &lt;a href="https://programminghistorian.org/en/lessons/fetch-and-parse-data-with-openrefine#post-request" target="_blank" rel="noopener">&amp;ldquo;Fetching and Parsing Data from the Web with OpenRefine&amp;rdquo;&lt;/a> wird gezeigt, wie mit OpenRefine das &lt;a href="http://text-processing.com/" target="_blank" rel="noopener">Text Processing API&lt;/a> verwendet werden kann. Das Projekt unterstützt aktuell Englisch, Holländisch, Portugiesisch und Spanisch. Ähnliche Freemium Angebote gibt es auch mit deutschen Sprachmodellen.
&lt;/div>
&lt;/div>
&lt;p>Anstatt einen externen Dienst zu bemühen, wollen wir den Dienst lokal laufen lassen.
Dafür packen wir &lt;a href="https://spacy.io" target="_blank" rel="noopener">spaCy&lt;/a> mit &lt;a href="https://fastapi.tiangolo.com/" target="_blank" rel="noopener">FastAPI&lt;/a> in ein API und verwenden &lt;a href="https://www.uvicorn.org/" target="_blank" rel="noopener">Uvicorn&lt;/a> als Server.&lt;/p>
&lt;h3 id="python-39-installieren">Python 3.9 installieren&lt;/h3>
&lt;p>Python 3.9 kann in Form einer ausführbaren Datei (&lt;code>.exe&lt;/code>) von &lt;a href="https://www.python.org/downloads/" target="_blank" rel="noopener">Python Downloads&lt;/a> heruntergeladen und lokal installiert werden.&lt;/p>
&lt;h3 id="virtuelle-umgebung-aufsetzen">Virtuelle Umgebung aufsetzen&lt;/h3>
&lt;p>Wir erstellen einen Ordner &lt;code>ner-service&lt;/code>. In dem Ordner starten wir eine PowerShell.
Dann erstellen wir mit Python eine so genannte virtuelle Umgebung (Zeile 1). &lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>
Die virtuelle Umgebung aktivieren wir anschließend (Zeile 2), installieren die benötigten Programme (Zeile 3) und laden das Sprachmodell mit spaCy herunter (Zeile 4).&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">python&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">venv&lt;/span> &lt;span class="n">venv&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">.\&lt;/span>&lt;span class="n">venv&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">Scripts&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">activate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">pip&lt;/span> &lt;span class="n">install&lt;/span> &lt;span class="n">fastapi&lt;/span> &lt;span class="nb">python-multipart&lt;/span> &lt;span class="n">spacy&lt;/span> &lt;span class="n">uvicorn&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">python&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">spacy&lt;/span> &lt;span class="n">download&lt;/span> &lt;span class="n">de_core_news_sm&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="ner-service-erstellen">NER Service erstellen&lt;/h3>
&lt;p>Den folgenden Python Code speichern wir in dem Ordner &lt;code>ner-service&lt;/code> in der Datei &lt;code>simple-ner-service.py&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">spacy&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">uvicorn&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">fastapi&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">FastAPI&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Form&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">FastAPI&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">nlp&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">spacy&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;de_core_news_sm&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@app.post&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;/ner&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">ner&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Form&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">)):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">doc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nlp&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">[{&lt;/span>&lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">ent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;label&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">ent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">label_&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">ent&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">doc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ents&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">uvicorn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;simple-ner-service:app&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">host&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;127.0.0.1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">port&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">5000&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Mit aktivierter virtueller Umgebung können wir den Server mit der PowerShell aus dem Ordner &lt;code>ner-service&lt;/code> heraus starten:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="p">.\&lt;/span>&lt;span class="n">venv&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">Scripts&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">activate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">python&lt;/span> &lt;span class="p">.\&lt;/span>&lt;span class="nb">simple-ner&lt;/span>&lt;span class="n">-service&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">py&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="ner-service-in-openrefine-nutzen">NER Service in OpenRefine nutzen&lt;/h3>
&lt;p>In OpenRefine schicken wir im &amp;ldquo;expression&amp;rdquo; Editor den Inhalt einer Zelle (&lt;code>value&lt;/code>) mit Jython an den &lt;code>ner-service&lt;/code> unter &lt;code>http://localhost:5000/ner&lt;/code> und geben das Ergebnis im &lt;code>JSON&lt;/code> Format zurück.
Dabei haben wir auf die Kodierung (&amp;ldquo;encoding&amp;rdquo;) zu achten, da Python &lt;strong>2&lt;/strong> standardmäßig nicht mit UTF-8 arbeitet.
Ein Problem, das in englischsprachigen Anleitungen häufig nicht relevant ist und daher ignoriert wird.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">json&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nn">urllib&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nn">urllib2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;http://localhost:5000/ner&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">request_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">urllib&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">urlencode&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">encode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;utf-8&amp;#39;&lt;/span>&lt;span class="p">)})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">urllib2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">urlopen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">request_data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">return&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dumps&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">ensure_ascii&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Die Vorschau des &amp;ldquo;expression&amp;rdquo; Editors in OpenRefine zeigt die ersten Ergebnisse.&lt;/p>
&lt;div class="alert alert-note">
&lt;div>
OpenRefine ermöglicht es &lt;a href="https://docs.openrefine.org/manual/columnediting#add-column-by-fetching-urls" target="_blank" rel="noopener">Spalten basierend auf URLs hinzuzufügen&lt;/a>.
Dabei werden so genannte &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET" target="_blank" rel="noopener">GET-Requests&lt;/a> ausgeführt, die von der Zeichenbeschränkung für URLs betroffen sind. Daher ist es empfehlenswert Texte via &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" target="_blank" rel="noopener">POST-Requests&lt;/a> zu versenden, weshalb wir die Anfrage mit Python und &lt;a href="https://docs.python.org/2/library/urllib2.html" target="_blank" rel="noopener">urllib2&lt;/a> ausführen.
&lt;/div>
&lt;/div>
&lt;h3 id="zusammenfassung-und-erweiterung">Zusammenfassung und Erweiterung&lt;/h3>
&lt;p>Mit etwa 15 Zeilen Python Code haben wir die Daten aus OpenRefine heraus mit einen (lokalen) Service verknüpft, der aufbauend auf der neuesten Technologie eine &amp;ldquo;Named Entity Recognition&amp;rdquo; durchführen kann.&lt;/p>
&lt;p>Dabei ist der Ansatz so generisch, dass er auf ähnliche Anwendungen übertragen werden kann.
Daher haben wir den minimalen Ansatz noch optimiert, dokumentiert und als &lt;a href="https://gist.github.com/b2m/6e2697ce182548a98320e4b7b7b885b6" target="_blank" rel="noopener">GitHub Gist&lt;/a> zur Verfügung gestellt.&lt;/p>
&lt;script type="application/javascript" src="https://gist.github.com/b2m/6e2697ce182548a98320e4b7b7b885b6.js?file=ner-service.py">&lt;/script>
&lt;h2 id="ner-mit-openrefine-und-spacy">NER mit OpenRefine und spaCy&lt;/h2>
&lt;div class="mermaid">---
title: Prozess
config:
look: handDrawn
theme: neutral
---
flowchart LR
text[fas:fa-file-alt Text]
spacy[fab:fa-python spaCy]
data[fas:fa-list-alt Entities]
gnd[fas:fa-database GND]
semantic[fas:fa-project-diagram Graph]
text --> data --> semantic
spacy --> data
gnd --> semantic
&lt;/div>
&lt;p>Mit dem funktionierenden NER Service können wir nun unserer ursprünglichen Idee nachgehen und ein &amp;ldquo;Named Entity Recognition&amp;rdquo; mit spaCy durchführen und die &amp;ldquo;Entities&amp;rdquo; anschließend mit der Gemeinsamen Normdatei (GND) abgleichen.&lt;/p>
&lt;h3 id="ner-service-starten">NER Service starten&lt;/h3>
&lt;p>Zuerst starten wir unseren NER Service. Dafür benötigen wir - wie im oberen Abschnitt beschrieben - Python 3 und eine virtuelle Umgebung mit &lt;a href="https://spacy.io" target="_blank" rel="noopener">spaCy&lt;/a>, &lt;a href="https://fastapi.tiangolo.com/" target="_blank" rel="noopener">FastAPI&lt;/a> und &lt;a href="https://www.uvicorn.org/" target="_blank" rel="noopener">Uvicorn&lt;/a>. Dort starten wir die Datei &lt;code>ner-service.py&lt;/code> aus dem &lt;a href="https://gist.github.com/b2m/6e2697ce182548a98320e4b7b7b885b6" target="_blank" rel="noopener">GitHub Gist&lt;/a>.&lt;/p>
&lt;p>Der NER Service ist nach dem Start unter &lt;code>http://localhost:5000&lt;/code> zu erreichen und kann wie in Abbildung 1 in einer interaktiven Dokumentation auch gleich getestet werden.&lt;/p>
&lt;figure id="figure-bildschirmfoto-der-interaktiven-api-dokumentation-des-ner-service">
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img alt="Bildschirmfoto der interaktiven API Dokumentation des NER Service." srcset="
/post/2021-08-ner-mit-openrefine-und-spacy/screenshot-ner-service-docs_hu4ade3a9214cede4be608667f96e8b843_41672_4c2ef6b72a5d5fe04f909cf4d6a26d50.webp 400w,
/post/2021-08-ner-mit-openrefine-und-spacy/screenshot-ner-service-docs_hu4ade3a9214cede4be608667f96e8b843_41672_b328450ad0047b3d5b85e9b5d61ee5ed.webp 760w,
/post/2021-08-ner-mit-openrefine-und-spacy/screenshot-ner-service-docs_hu4ade3a9214cede4be608667f96e8b843_41672_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://fdmlab.landesarchiv-bw.de/post/2021-08-ner-mit-openrefine-und-spacy/screenshot-ner-service-docs_hu4ade3a9214cede4be608667f96e8b843_41672_4c2ef6b72a5d5fe04f909cf4d6a26d50.webp"
width="760"
height="395"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;figcaption data-pre="Abbildung&amp;nbsp;" data-post=":&amp;nbsp;" class="numbered">
Bildschirmfoto der interaktiven API Dokumentation des NER Service.
&lt;/figcaption>&lt;/figure>
&lt;h3 id="openrefine-projekt-erstellen">OpenRefine Projekt erstellen&lt;/h3>
&lt;blockquote>
&lt;p>💾 Für OpenRefine haben wir einige Textschnippsel aus Wikipedia zusammengesucht (&lt;a href="https://creativecommons.org/licenses/by-sa/3.0/" target="_blank" rel="noopener">CC-BY-SA-3.0&lt;/a>) und bieten diese mit Quellangabe unter gleichen Lizenzbedingungen in einer JSON Datei zum Download an (Rechtsklick und &amp;ldquo;Ziel speichern unter&amp;hellip;&amp;rdquo;):&lt;/p>
&lt;/blockquote>
&lt;a href="https://fdmlab.landesarchiv-bw.de/data/wikipedia-schnippsel.json" target="_blank">
&lt;i class="fas fa-file-code pr-1 fa-fw">&lt;/i>Wikipedia Textschnippsel als JSON&lt;/a>
.
&lt;p>Mit der JSON Datei erzeugen wir wie in Abbildung 2 ein neues Projekt in OpenRefine.&lt;/p>
&lt;figure id="figure-bildschirmfoto-zur-erstellung-des-projektes-mit-einer-json-datei-in-openrefine">
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img alt="Bildschirmfoto zur Erstellung des Projektes mit einer JSON Datei in OpenRefine." srcset="
/post/2021-08-ner-mit-openrefine-und-spacy/openrefine-view-load-json_huf3aa10d5fab2c08f73be6bfb9c273572_91615_25eb405b39e843bbc51cce95d4a74656.webp 400w,
/post/2021-08-ner-mit-openrefine-und-spacy/openrefine-view-load-json_huf3aa10d5fab2c08f73be6bfb9c273572_91615_15276d06678ffdbe1f56d82949f26e6a.webp 760w,
/post/2021-08-ner-mit-openrefine-und-spacy/openrefine-view-load-json_huf3aa10d5fab2c08f73be6bfb9c273572_91615_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://fdmlab.landesarchiv-bw.de/post/2021-08-ner-mit-openrefine-und-spacy/openrefine-view-load-json_huf3aa10d5fab2c08f73be6bfb9c273572_91615_25eb405b39e843bbc51cce95d4a74656.webp"
width="760"
height="333"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;figcaption data-pre="Abbildung&amp;nbsp;" data-post=":&amp;nbsp;" class="numbered">
Bildschirmfoto zur Erstellung des Projektes mit einer JSON Datei in OpenRefine.
&lt;/figcaption>&lt;/figure>
&lt;p>Anschließend benennen wir die Spalten zur Übersichtlichkeit in Quelle und Text um.&lt;/p>
&lt;h3 id="ner-auf-text-durchführen">NER auf Text durchführen&lt;/h3>
&lt;p>Um eine &amp;ldquo;Named Entity Recognition&amp;rdquo; durchzuführen, erzeugen wir eine neue Spalte NER basierend auf der Spalte Text und verwenden wie in Abbildung 3 eine Jython Expression.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">json&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nn">urllib&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nn">urllib2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;http://localhost:5000/ner&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">request_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dumps&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s1">&amp;#39;text&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">encode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;utf-8&amp;#39;&lt;/span>&lt;span class="p">)})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">request&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">urllib2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Request&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">request_data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s1">&amp;#39;Content-Type&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;application/json&amp;#39;&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">urllib2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">urlopen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">request&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">return&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dumps&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">ensure_ascii&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="alert alert-note">
&lt;div>
Wir haben die &amp;ldquo;expression&amp;rdquo; im Vergleich zu unserem Test abgeändert, da wir bei der Überarbeitung des NER Service von &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" target="_blank" rel="noopener">form-urlencoded&lt;/a> zu JSON wechselten.
&lt;/div>
&lt;/div>
&lt;figure id="figure-bildschirmfoto-mit-dialog-zur-durchführung-von-ner-in-openrefine">
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img alt="Bildschirmfoto mit Dialog zur Durchführung von NER in OpenRefine." srcset="
/post/2021-08-ner-mit-openrefine-und-spacy/openrefine-dialog-ner_hu8c3f9cecb80f8b2fc346b845d3ab90c4_41020_6e9d33a7a7552ad5eff1877604ba307c.webp 400w,
/post/2021-08-ner-mit-openrefine-und-spacy/openrefine-dialog-ner_hu8c3f9cecb80f8b2fc346b845d3ab90c4_41020_ef3f1dfcd7eead17b58b2bb8d350aab5.webp 760w,
/post/2021-08-ner-mit-openrefine-und-spacy/openrefine-dialog-ner_hu8c3f9cecb80f8b2fc346b845d3ab90c4_41020_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://fdmlab.landesarchiv-bw.de/post/2021-08-ner-mit-openrefine-und-spacy/openrefine-dialog-ner_hu8c3f9cecb80f8b2fc346b845d3ab90c4_41020_6e9d33a7a7552ad5eff1877604ba307c.webp"
width="712"
height="522"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;figcaption data-pre="Abbildung&amp;nbsp;" data-post=":&amp;nbsp;" class="numbered">
Bildschirmfoto mit Dialog zur Durchführung von NER in OpenRefine.
&lt;/figcaption>&lt;/figure>
&lt;h3 id="informationen-aus-json-extrahieren">Informationen aus JSON extrahieren&lt;/h3>
&lt;p>Um die Informationen aus dem JSON formatierten Text zu extrahieren, verwenden wir die Werkzeuge von GREL.
Dafür erzeugen wir wie in Abbildung 4 eine neue Spalte Entity aus der Spalte NER mit einer&amp;quot;expression&amp;quot;.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">forEach&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">parseJson&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">value&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="nx">v&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">v&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">text&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;||&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;figure id="figure-bildschirmfoto-mit-dialog-zur-erzeugung-einer-neuen-spalte-mit-dem-entity-text-in-openrefine">
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img alt="Bildschirmfoto mit Dialog zur Erzeugung einer neuen Spalte mit dem Entity Text in OpenRefine." srcset="
/post/2021-08-ner-mit-openrefine-und-spacy/openrefine-dialog-entity_hu1cfead628cf246b3e50a14ae654a9f50_32816_9845c0443a4df6c1c71d23010e958e0d.webp 400w,
/post/2021-08-ner-mit-openrefine-und-spacy/openrefine-dialog-entity_hu1cfead628cf246b3e50a14ae654a9f50_32816_274bbd2271eb24d822ffaf6e1de6fe9c.webp 760w,
/post/2021-08-ner-mit-openrefine-und-spacy/openrefine-dialog-entity_hu1cfead628cf246b3e50a14ae654a9f50_32816_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://fdmlab.landesarchiv-bw.de/post/2021-08-ner-mit-openrefine-und-spacy/openrefine-dialog-entity_hu1cfead628cf246b3e50a14ae654a9f50_32816_9845c0443a4df6c1c71d23010e958e0d.webp"
width="717"
height="525"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;figcaption data-pre="Abbildung&amp;nbsp;" data-post=":&amp;nbsp;" class="numbered">
Bildschirmfoto mit Dialog zur Erzeugung einer neuen Spalte mit dem Entity Text in OpenRefine.
&lt;/figcaption>&lt;/figure>
&lt;p>Für den Typ der Entities erzeugen wir wie in Abbildung 5 eine neue Spalte Label aus der Spalte NER mit einer &amp;ldquo;expression&amp;rdquo;.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">forEach&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">parseJson&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">value&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="nx">v&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">v&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">label&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;||&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;figure id="figure-bildschirmfoto-mit-dialog-zur-erzeugung-einer-neuen-spalte-mit-dem-entity-label-in-openrefine">
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img alt="Bildschirmfoto mit Dialog zur Erzeugung einer neuen Spalte mit dem Entity Label in OpenRefine." srcset="
/post/2021-08-ner-mit-openrefine-und-spacy/openrefine-dialog-label_hu875e316715c66aa0a53b32733dd872be_29536_2bfed72345b70944758211ccef514127.webp 400w,
/post/2021-08-ner-mit-openrefine-und-spacy/openrefine-dialog-label_hu875e316715c66aa0a53b32733dd872be_29536_16e29ecdfd845314e1686d81875fc5ff.webp 760w,
/post/2021-08-ner-mit-openrefine-und-spacy/openrefine-dialog-label_hu875e316715c66aa0a53b32733dd872be_29536_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://fdmlab.landesarchiv-bw.de/post/2021-08-ner-mit-openrefine-und-spacy/openrefine-dialog-label_hu875e316715c66aa0a53b32733dd872be_29536_2bfed72345b70944758211ccef514127.webp"
width="716"
height="527"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;figcaption data-pre="Abbildung&amp;nbsp;" data-post=":&amp;nbsp;" class="numbered">
Bildschirmfoto mit Dialog zur Erzeugung einer neuen Spalte mit dem Entity Label in OpenRefine.
&lt;/figcaption>&lt;/figure>
&lt;p>Wir haben nun mehrere Werte in einer Zelle, weshalb wir diese wie in Abbildung 6 mit &amp;ldquo;Entity&amp;quot;
&lt;i class="far fa-caret-square-down pr-1 fa-fw">&lt;/i>&amp;quot;Edit Cells&amp;quot;
&lt;i class="fas fa-caret-right pr-1 fa-fw">&lt;/i>&amp;quot;Split multi-valued cells&amp;hellip;&amp;rdquo; und &amp;ldquo;Label&amp;quot;
&lt;i class="far fa-caret-square-down pr-1 fa-fw">&lt;/i>&amp;quot;Edit Cells&amp;quot;
&lt;i class="fas fa-caret-right pr-1 fa-fw">&lt;/i>&amp;quot;Split multi-valued cells&amp;hellip;&amp;rdquo; an der Trennsequenz &lt;code>||&lt;/code> auftrennen.&lt;/p>
&lt;figure id="figure-bildschirmfoto-mit-dialog-zur-aufteilung-der-entities-in-mehrere-zeilen-in-openrefine">
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img alt="Bildschirmfoto mit Dialog zur Aufteilung der Entities in mehrere Zeilen in OpenRefine." srcset="
/post/2021-08-ner-mit-openrefine-und-spacy/openrefine-dialog-split_hu654a038295f31ea0122a9cf7c050f24f_16130_b0553baeff46521690a0bf363eeeaf02.webp 400w,
/post/2021-08-ner-mit-openrefine-und-spacy/openrefine-dialog-split_hu654a038295f31ea0122a9cf7c050f24f_16130_84b0c040b7648800a50d24fba37b6050.webp 760w,
/post/2021-08-ner-mit-openrefine-und-spacy/openrefine-dialog-split_hu654a038295f31ea0122a9cf7c050f24f_16130_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://fdmlab.landesarchiv-bw.de/post/2021-08-ner-mit-openrefine-und-spacy/openrefine-dialog-split_hu654a038295f31ea0122a9cf7c050f24f_16130_b0553baeff46521690a0bf363eeeaf02.webp"
width="599"
height="383"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;figcaption data-pre="Abbildung&amp;nbsp;" data-post=":&amp;nbsp;" class="numbered">
Bildschirmfoto mit Dialog zur Aufteilung der Entities in mehrere Zeilen in OpenRefine.
&lt;/figcaption>&lt;/figure>
&lt;h3 id="daten-mit-gnd-abgleichen">Daten mit GND abgleichen&lt;/h3>
&lt;p>Nun haben wir die gefundenen Entities in einzelnen Zeilen und können sie nach Belieben bearbeiten, löschen oder mit externen Diensten abgleichen.
Dafür verwenden wir den &lt;a href="https://lobid.org/gnd/reconcile" target="_blank" rel="noopener">GND Reconciliation Service von lobid&lt;/a>. Mit &amp;ldquo;Entity&amp;quot;
&lt;i class="far fa-caret-square-down pr-1 fa-fw">&lt;/i>&amp;quot;Reconcile&amp;quot;
&lt;i class="fas fa-caret-right pr-1 fa-fw">&lt;/i>&amp;quot;Start reconciling&amp;rdquo; starten wir den Abgleichsvorgang.
Siehe auch &lt;a href="https://fdmlab.landesarchiv-bw.de/post/2021-07-findbuch-index-mit-openrefine-aufbereiten/">Findbuch Index mit OpenRefine aufbereiten&lt;/a> für Details zum Datenabgleich von Daten in OpenRefine mit der Gemeinsamen Normdatei (GND).
In Abbildung 7 haben wir einen Screenshot der Daten, nachdem die Entities mit der GND abgeglichen und mit der GND-ID angereichert wurden.&lt;/p>
&lt;figure id="figure-bildschirmfoto-der-zugeordneten-entities-in-openrefine">
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img alt="Bildschirmfoto der zugeordneten Entities in OpenRefine." srcset="
/post/2021-08-ner-mit-openrefine-und-spacy/openrefine-view-ner-cleaned_hu985a1b73348435cf4675cba90d8ff620_97872_55ffd92c73d733d588078f3d91c8060c.webp 400w,
/post/2021-08-ner-mit-openrefine-und-spacy/openrefine-view-ner-cleaned_hu985a1b73348435cf4675cba90d8ff620_97872_0903d881b03356976567224863c84137.webp 760w,
/post/2021-08-ner-mit-openrefine-und-spacy/openrefine-view-ner-cleaned_hu985a1b73348435cf4675cba90d8ff620_97872_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://fdmlab.landesarchiv-bw.de/post/2021-08-ner-mit-openrefine-und-spacy/openrefine-view-ner-cleaned_hu985a1b73348435cf4675cba90d8ff620_97872_55ffd92c73d733d588078f3d91c8060c.webp"
width="760"
height="381"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;figcaption data-pre="Abbildung&amp;nbsp;" data-post=":&amp;nbsp;" class="numbered">
Bildschirmfoto der zugeordneten Entities in OpenRefine.
&lt;/figcaption>&lt;/figure>
&lt;h2 id="fazit">Fazit&lt;/h2>
&lt;p>In diesem Beitrag haben wir eine Möglichkeit gezeigt, wie generell mit OpenRefine Daten an einen lokalen Service übergeben und verarbeitet werden können.
Das haben wir genutzt um ein &amp;ldquo;Named Entity Recognition&amp;rdquo; mit spaCy durchzuführen.&lt;/p>
&lt;p>Wir hätten uns gewünscht, dass die Integration von Python Frameworks in OpenRefine direkter und weniger technisch zu bewerkstelligen wäre.
Andererseits haben wir mit &lt;a href="https://fastapi.tiangolo.com/" target="_blank" rel="noopener">FastAPI&lt;/a> eine Framework getestet, mit dem unkompliziert Dienste zur Verfügung gestellt werden können.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>Eine virtuelle Umgebung ermöglicht es uns, die für dieses Projekt benötigten Abhängigkeiten direkt in den Ordner zu installieren, und bei Bedarf das komplette Projekt mit allen Abhängigkeiten wieder aufzuräumen.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item></channel></rss>