PHP Wrapper für die Wortschatz Uni Leipzig API

Wortschatz Uni Leipzig PHP Wrapper

Wenn man sich etwas intensiver mit der algorithmischen Analyse von Sprache auseinandersetzt, dann trifft man früher oder später auf den Wortschatz der Uni Leipzig. Dabei handelt es sich um eine Sammlung von Webservices, die sowohl über eine Web-Oberfläche als auch via API genutzt werden können. Leider gibt es keine REST-API (dabei wäre eine solche mit JSON Output so viel einfacher für Clients und auch gar nich sooo schwer für Server…), sondern der Zugriff ist lediglich über SOAP möglich – was meiner Meinung nach die Einstiegshürde zur Verwendung der API unnötig erhöht. Das ist schade, weil man mit der API echt coole Daten bekommt und die momentanen PHP Beispiele ziemlich alt (und außerdem offline…) sind. Deshalb habe ich einen einfachen zu bedienenden Wrapper entwickelt, der sich automatisch aus der Übersichtsseite der Wotzschatz API generiert.

Inhalt

Die Wortschatz Uni Leipzig API
Kurze Einführung in die Wortschatz API
Von der Pike auf: Die Kommunikation via SOAP
Beispiel-Kommunikation des SOAP Protokolls und erste Implementierung in PHP. Zum technischen Verständnis hilfreich, kann aber übersprungen werden.
Generierung der Wrapper Klasse
Die Wrapper Klasse wird automatisch generiert, in dem Informationen von der Übersichtsseite der Webservices gescraped werden. Hier werden die verwendeten XPath-Ausdrücke vorgestellt.
Die Wortschatz API Wrapper Klasse
Vorstellung der generierten Wrapper Klasse und der dazu erzeugten Beispieldatei. Interessant wenn man schnell loslegen möchte.
Downloads
Fazit zum PHP Wrapper für die Wortschatz Uni Leipzig API

Die Wortschatz Uni Leipzig API

Die Wortschatz Uni Leipzig API ist mir zum ersten Mal begegnet, als ich nach Datenquellen für Synonyme meines Article Wizards Ausschau gehalten habe (kam dann mangels Komplett-Export nicht in Frage, Kai nutzt das aber zum Beispiel). Momentan suche ich nach einer API für IDF Werte und der Wortschatz bietet dankbarer Weise einen entsprechenden Service-Endpoint an, der zumindest einen Näherungswert in Form von Häufigkeitsklassen dafür liefert. Dazu wird es in einem späteren Beitrag noch weitere Infos geben 😉

Insgesamt werden die folgenden Services via Wortschatz API angeboten:

  • ServiceOverview
  • Cooccurrences
  • Baseform
  • Sentences
  • RightNeighbours
  • LeftNeighbours
  • Frequencies
  • Synonyms
  • MARSService
  • CooccurrencesAll
  • Thesaurus
  • Wordforms
  • Similarity
  • LeftCollocationFinder
  • RightCollocationFinder
  • ExperimentalSynonyms
  • Kookkurrenzschnitt
  • Sachgebiet
  • Kreuzwortraetsel
  • NGramReferences
  • NGrams

Die meisten Services der Wortschatz API können ohne eigene Anmeldedaten genutzt werden, indem als Nutzername und Passwort jeweils „anonymous“ verwendet wird.

Von der Pike auf: Die Kommunikation via SOAP

Die Kommunikation mit der API funktioniert über das SOAP Protokoll. Zum Verständnis des Prozesses gibt’s im folgenden ein Code-Beispiel – falls die technischen „Details“ nicht interessieren, kann dieser Part auch übersprungen werden.

SOAP ist ein Protokoll zum Datenaustausch zwischen verschiedenen Systemen. Die Daten werden dabei (meistens) in XML repräsentiert und als Transportprotokoll wird (meistens) HTTP verwendet. Im „industriellen“ Gerbauch hat SOAP aus meiner Sicht drei große Vorteile:

  1. Anerkannter Industriestandard beim W3C
  2. Einfach zu implementierende und sehr umfassende Security Features dank WS-Security
  3. Standardisierte Schnittstellendefinition dank WSDL

Leider erkauft man sich diese Vorteile durch einen massiven Overhead und übrigens können seit WSDL 2.0 auch REST-Services via WSDL beschrieben werden. Aber ich will hier kein Protokoll Bashing betreiben, zumal ich die genauen Anforderungen an die API nicht kenne – also weiter im Text 🙂

Eine entsprechende XML Anfrage via SOAP an den Frequencies-Service sieht so aus:

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope>
 <SOAP-ENV:Body>
  <ns1:execute>
   <ns1:objRequestParameters>
    <ns1:corpus>de</ns1:corpus>
    <ns1:parameters>
     <ns1:dataVectors>
      <ns2:dataRow>Wort</ns2:dataRow>
      <ns2:dataRow>Hund</ns2:dataRow>
     </ns1:dataVectors>
    </ns1:parameters>
   </ns1:objRequestParameters>
  </ns1:execute>
 </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Diese wird zusammen mit den folgenden HTTP Request Headern gesendet (Authorization setzt sich in dem Fall aus anonymous:anonymous zusammen):

POST /axis/services/Frequencies HTTP/1.1
Host: wortschatz.uni-leipzig.de:8100
Connection: Keep-Alive
User-Agent: PHP-SOAP/5.3.5
Content-Type: text/xml; charset=utf-8
SOAPAction: ""
Content-Length: 500
Authorization: Basic YW5vbnltb3VzOmFub255bW91cw==

Als Antwort liefert der Service daraufhin

HTTP/1.1 200 OK
Content-Type: text/xml;charset=utf-8
Transfer-Encoding: chunked
Date: Wed, 28 Aug 2013 12:32:03 GMT
Server: Apache-Coyote/1.1

mit dem Inhalt

<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
                  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <soapenv:Body>
  <executeResponse xmlns="urn:Frequencies">
   <executeReturn>
    <executionTime>0.021 s</executionTime>
    <result>
     <ns1:dataVectors xsi:type="ns2:DataVector" 
                      xmlns:ns1="http://datatypes.webservice.wortschatz.uni_leipzig.de" 
                      xmlns:ns2="urn:Frequencies">
      <ns1:dataRow>16837</ns1:dataRow>
      <ns1:dataRow>10</ns1:dataRow>
     </ns1:dataVectors>
    </result>
    <serviceMagnitude>0</serviceMagnitude>
    <userAmount>1999245431</userAmount>
    <userMaxLimit>-100</userMaxLimit>
   </executeReturn>
  </executeResponse>
 </soapenv:Body>
</soapenv:Envelope>

Insgesamt also äußerst unhandliche Daten, die hier über die Leitung gehen. Dank des PHP SoapClients wird mir zwar die Generierung des XMLs und die Authentifizierung bzw. der gesamte HTTP Request abgenommen, aber selbst damit ist der Aufruf aus dem Code heraus noch sehr komplex. Ich stelle das im Folgenden unter Anlehnung an den ursprünglichen Code von Nicolas Zimmer und den Service „Frequencies“ dar:

<?php
$wort= array('dataRow' => array('Wort','Hund'));
$dataVectors=array($wort);
$parameters=array('dataVectors' => $dataVectors);
$request = array(
    'objRequestParameters' => 
        array(
            'corpus' => 'de',
            'parameters' => $parameters
        )
);

$client = new SoapClient("http://wortschatz.uni-leipzig.de/axis/services/Frequencies?wsdl", 
                         array(
                                 'trace'=>1, 
                                 'exceptions' => 1, 
                                 'login'=>'anonymous', 
                                 'password'=>'anonymous'
                         )
                        );

try{
    $result = $client->execute($request);
    var_dump($result->executeReturn->result->dataVectors);
}catch(Exception $e){
    die("ERROR: ".$e->getMessage());
}
?>

Der gesamte $request  zusammen mit seinen $parameters  und $dataVectors  ist einfach unnötiger Overhead, nicht intuitiv bedienbar und man muss zum Beispiel wissen, dass der String „Wort“ den Parameter für diesen Service kennzeichnet. Wie gesagt – ziemlich „unhandlich“. Eigentlich müsste man das für jeden Service ausformulieren und in eine entsprechende Funktion/Methode packen, damit man zum Beispiel sowas aufrufen kann:

<?php
	$wrapper = new WortschatzUniLeipzigApiWrapper();
	$Wort = 'Hund';
	try{
		$result = $wrapper->Frequencies($Wort);
		var_dump($result);
	}
	catch(Exception $e){
		echo "ERROR! ".$e->getMessage()."\n";
	}
?>

Sieht schon besser aus – ist allerdings mühselig, das für alle Services „per Hand“ anzulegen…

Generierung der Wrapper Klasse

… deshalb habe ich mal geschaut, ob man sich mit den vorhandenen Infos automatisch einen Wrapper zusammenbasteln kann. Unter http://wortschatz.uni-leipzig.de/axis/servlet/ServiceOverviewServlet sind für jeden Service Meta-Informationen hinterlegt (Name, Beschreibung, Input-Parameter, etc.). Diese kann ich von der Seite scrapen und damit automatisch die Wrapper Klasse generieren. Dazu werden die folgenden XPath-Ausdrücke verwendet:

Ausdruck Information Beispiel
//b Name des Services Frequencies
//tr[4]/td[4] Beschreibung Returns the frequency and frequency class of the input word. Frequency class is computed in relation to the most frequent word in the corpus. The higher the class, the rarer the word.
//tr[6]/td[4] Input-Parameter Wort
//tr[8]/td[4] Status ACTIVE
//tr[10]/td[4] Authorisierungslevel FREE

Spannend sind hier vor allem die Input-Parameter, weil damit die spätere Benutzung des Wrappers wesentlich einfacher wird. Der komplette Code zur Generierung ist am Ende als Download verfügbar, das wäre hier etwas zu unübersichtlich.

Anmerkung: Ja, das Scrapen ist ziemlich „hacky“ und sobald sich etwas an der Struktur der Seite ändert, bricht der Code – aber momentan ist es die einfachste und schnellste Lösung 😉

Die Wortschatz API Wrapper Klasse

Die generierte Klasse stellt nun eine eigene Methode für jeden Service der Wortschatz API zur Verfügung. Exemplarisch (am Frequencies Service) sieht das so aus:

<?php
class WortschatzUniLeipzigApiWrapper{

    /**
    * @var string
    **/
    public $username;
    /**
    * @var string
    **/
    public $password;
    /**
    * @see http://php.net/manual/de/soapclient.soapclient.php
    * @var mixed[]
    **/
    public $options;

    /**
    * @param string $username [optional] - Default:"anonymous". The login name
    * @param string $password [optional] - Default:"anonymous". The login password
    * @param string[] $options [optional] - Default: [trace=>1, exceptions=>1]. Options array for SoapClient
    **/
    public function __construct($username = "anonymous", $password = "anonymous", $options = array("trace" => 1, "exceptions" => 1)){
        $this->username = $username;
        $this->password = $password;
        $this->options = $options;
    }

    /**
    * Returns the frequency and frequency class of the input word. 
    * Frequency class is computed in relation to the most frequent word in the corpus. 
    * The higher the class, the rarer the word.
    *
    * Status: ACTIVE
    *
    * Authorization: FREE
    *
    * WSDL-URL: @see http://wortschatz.uni-leipzig.de/axis/services/Frequencies?wsdl
    *
    * Service overview: @see http://wortschatz.uni-leipzig.de/axis/servlet/ServiceOverviewServlet
    *
    * @param mixed $Wort
    *
    * @return DataVector
    */
    public function Frequencies($Wort, $corpus = "de"){
        $url = "http://wortschatz.uni-leipzig.de/axis/services/Frequencies?wsdl";
        $options = $this->GetOptions();
        $service = new SoapClient($url, $options);
        $params = array();
        $params[] = array("Wort", $Wort);
        return $this->CallMethod($service, $params, $corpus);
    }

    /* ...
    * Definition der übrigen Service-Methoden-Wrapper
    * ...
    */

    /**
    *
    * @return string[]
    */
    private function GetOptions(){
        $options = $this->options;
        $options["login"] = $this->username;
        $options["password"] = $this->password;
        return $options;
    }

    /**
    *
    * @param SoapClient $service
    * @param array $params
    * @param string $corpus
    * @return DataVector
    */
    private function CallMethod(SoapClient $service, array $params, $corpus = "de"){
        $dataVectors = array();
        foreach($params as $p){
            $dataVectors[] = array("dataRow" => $p);
        }
        $parameters = array("dataVectors" => $dataVectors);
        $request = array("objRequestParameters" => array("corpus" => $corpus, "parameters" => $parameters));
        $result = $service->execute($request);
        if(isset($result->executeReturn->result->dataVectors)){
            return $result->executeReturn->result->dataVectors;
        }
        return null;
    }   
}
?>

Achtung: Der Rückgabewert $result->executeReturn->result->dataVectors  kann abhängig vom verwendeten Service sowohl ein DataVector  Objekt als auch ein Array von DataVector  Objekten sein – Letzteres tritt zum Beispiel beim Service Baseform  mit dem Parameter „müßten“  auf!

Mit dieser Klasse können nun sämtliche Services über fest definierte Parameter aufgerufen werden (ich „weiß“ also/kann mir durch Code Completion zeigen lassen, dass der Service „Frequencies“ den Parameter „Wort“ erwartet). Insgesamt also wie gewünscht:

<?php
	$wrapper = new WortschatzUniLeipzigApiWrapper();
	$Wort = 'Hund';
	try{
		$result = $wrapper->Frequencies($Wort);
		var_dump($result);
	}
	catch(Exception $e){
		echo "ERROR! ".$e->getMessage()."\n";
	}
?>

Ein weiterer Knackpunkt bei der Verwendung von APIs sind meistens noch die tatsächlichen Werte der Input-Parameter. Sprich:

  • Welcher Datentyp wird erwartet?
  • Gibt es fest definierte Werte?
  • Ist der Input ein Wort, ein ganzer Satz, ein Buchstabe, …?

Deshalb wird zusätzlich noch eine Beispiel-Datei erzeugt, die für jeden Funktionsaufruf ein Beispiel inklusive der möglichen Parameter generiert. Leider werden diese nicht von der Uni Leipzig zur Verfügung gestellt, deshalb musste ich nach bestem Wissen und Gewissen selbst sinnvolle repräsentative Werte auswählen.

Bei den Funktionen ServiceOverview (Parameter $Name), NGramReferences und NGram (Parameter $Pattern) habe ich noch nicht rausgefunden, welche Werte dort erwartet werden – Mail an die Uni ging aber bereits raus 😉

Bezüglich der Funktionen ServiceOverview, NGramReferences und NGram habe ich folgende Infos bekommen:

Sehr geehrter Herr Landau,
es hat etwas gedauert, aber nun liegt eine Antwort vor:
Der Service „ServiceOverview“ funktioniert nur mit der Datenbank (bzw.dem „corpus“) „webservices“.

Bezüglich der Services NGrams und NGramReferences:
Diese wurden nur testweise (für einen Studenten) „entwickelt“ und deployt und funktionieren vermutlich nur für corpus=ngrams. Am Besten wäre es wohl diese zu ignorieren…

Der Similarity-Service bringt dieser Fehlermeldung gelegentlich aufgrund von Zeitüberschreitungen: Die dahinter liegenden Anfragen werden in einigen Fällen zu komplex und werden daher nach einer gewissen Zeit automatisch abgebrochen… je nach Last auf dem Server etc. kann dies auch zu nicht reproduzierbarem (allenfalls provozierbarem) Verhalten führen.

Anyway, hier ein Auszug aus der erzeugten Beispieldatei:

<?php
require_once 'WortschatzUniLeipzigApiWrapper.class.php';

    $username = "anonymous";
    $password = "anonymous";
    $options = array("trace" => 1, "exceptions" => 1);
    $wrapper = new WortschatzUniLeipzigApiWrapper($username, $password, $options);

    echo "Test of method \"Frequencies\".\n";
    echo "This method is currently ACTIVE.\n";
    echo "Input parameters:\n";
    $Wort = 'Hund';
    echo '$Wort'." = 'Hund'\n";
    try{
        $result = $wrapper->Frequencies($Wort);
        echo "SUCCESS!\n";
        echo "Result:\n";
        var_dump($result);
    }
    catch(Exception $e){
        echo "ERROR! ".$e->getMessage()."\n";
    }
    echo "\n\n";

    /* Beispiele der übrigen Services */
?>

Deren Aufruf erzeugt folgende Ausgabe (wieder am Beispiel des „Frequencies“ Service):

Test of method "Frequencies".
This method is currently ACTIVE.
Input parameters:
$Wort = 'Hund'
SUCCESS!
Result:
object(stdClass)#7 (1) {
  ["dataRow"]=>
  array(2) {
    [0]=>
    string(5) "16837"
    [1]=>
    string(2) "10"
  }
}

16837 stellt in diesem Fall die Anzahl der Vorkommen von „Hund“ im de-Korpus dar und 10 ist die dazu gehörige Häufigkeitsklasse. Was man damit in der Praxis anstellen kann, zeige ich in einem Folgebeitrag (Hint: Subscribe via RSS and/or Follow me).

Downloads

Zum Schluss noch der Download des Wrappers:

Wrapper selbst erstellen

Download der Datei zur Generierung des API Wrappers
PS: Keine Lust zum erstellen? Dann hier den bereits generierten Wrapper downloaden und bei Schritt 5 einsteigen 😉

Generierter Wrapper

Download des erstellten API Wrappers

1. Datei downloaden und entpacken

Installationsdatei zur Wortschatz API
Installationsdatei zur Wortschatz API

2. Datei InstallWortschatzApi.php öffnen, Konfiguration anpassen (optional) und sicherstellen, dass die Requirements erfüllt sind.

Readme und Konfiguration in der InstallWortschatzApi.php
Readme und Konfiguration in der InstallWortschatzApi.php

3. Script ausführen

Eingabeaufforderung im Ordner der php.exe öffnen
Eingabeaufforderung im Ordner der php.exe öffnen
Installation ausführen
Installation ausführen

4. Generierte Dateien kontrollieren

Die Worschatz API Wrapper Klasse und die Beispieldatei wurden erzeugt.
Die Worschatz API Wrapper Klasse und die Beispieldatei wurden erzeugt.

5. Beispiel-Datei ausführen (standardmäßig WortschatzUniLeipzigApiExample.php)

Beispieldatei ausführen
Beispieldatei ausführen

6. Fertig 🙂

Fazit zum PHP Wrapper für die Wortschatz Uni Leipzig API

In diesem Artikel habe ich einen Weg vorgestellt, automatisch einen PHP Wrapper für die Wortschatz API zu erstellen. Sowohl die Datei zur Generierung als auch der tatsächliche Wrapper inklusive Beispieldatei sind als Download verfügbar.

Key Facts:

  • Die Wortschatz API ist nur via SOAP nutzbar – das ist inhärent komplex – allerdings ist anscheinend eine REST API geplant (bisher aber ohne konkreten Zeitplan)
  • Aus „strukturierten“ Übersichtsseiten lassen sich automatisch Wrapper-Klassen generieren

Was noch fehlt:

  • Korrekte Beispiel-Parameter für die Funktionen ServiceOverviewNGramReferences  und NGram – Da experimental aber eher nicht so wichtig.
  • Kennzeichnung der Rückgabewerte der einzelnen Services – momentan ist das nämlich nur ein generisches Array ohne Typisierung für jeden Service

Bei Fragen und Problemen gerne kommentieren (optional) und bei Gefallen twittern, liken und plussen (obligatorisch) 🙂

Hirnhamster

hat einen Bachelor in Angewandter Informatik und bloggt auf MySEOSolution regelmäßig zu Updates im Bereich der Suchmaschinenoptimierung. Außerdem freut er sich über Kontakte auf Google+ 🙂

More Posts - Website - Twitter - Facebook - Google Plus