Elasticsearch Unschärfesuche und Gewichtung nutzen

Vor zwei Wochen hatte ich euch ja erzählt, die man bei ElasticPress die Volltextsuche anpassen kann. Hierbei habe ich einen Ausdruck aus der Suche entfernt, der für viele ungenaue Suchergebnisse zuständig war und somit zu einem schlechten Gesamtergebnis geführt hat.

Heute möchte ich ein wenig an diesen Artikel anknüpfen und die Suchanfrage noch ein wenig verbessern. Denn durch die vorherige Anpassung ist eine wichtige Funktion von Elasticsearch verloren gegangen: Die Unschärfesuche.

Standard Suchanfrage in ElasticPress

Sehen wir zunächst noch einmal die Suchanfrage an, die ElasticPress ohne Anpassungen an den Elasticsearch Server schickt:

"query": {
	"bool": {
		"should": [
			{
				"multi_match": {
					"query": "wordcamp frankfurt folien",
					"type": "phrase",
					"fields": [
						"post_title",
						"post_excerpt",
						"post_content"
					],
					"boost": 4,
					"fuzziness": 0
				}
			},
			{
				"multi_match": {
					"query": "wordcamp frankfurt folien",
					"fields": [
						"post_title",
						"post_excerpt",
						"post_content"
					],
					"boost": 2,
					"fuzziness": 0,
					"operator": "and"
				}
			},
			{
				"multi_match": {
					"fields": [
						"post_title",
						"post_excerpt",
						"post_content"
					],
					"query": "wordcamp frankfurt folien",
					"fuzziness": 1
				}
			}
		]
	}
},

Es werden hier drei boolesche Suchen ausgeführt und mit „should“ verknüpft. Die erste „multi_match“ Abfrage ist vom Typ „phrase„. Diese sucht auf allen drei Feldern (post_title, post_excerpt und post_content) nach dem Suchbegriff. Der Parameter „fuzziness“ ist hierbei allerdings auf 0 gesetzt, was eben bedeutet, dass kein Tippfehler vorkommen darf, da ansonsten die Suche kein Ergebnis liefert. Zuletzt wird die erste Suche noch um den Faktor 4 höher gewertet.

Die zweite Suche hat keinen Typ angegeben. Daher wird hier „best_fields“ verwendet. Es wird also das Feld genutzt, in dem alle Suchbegriffe vorkommen. Dies wird durch den „and“ Operator gesteuert. Auch hier werden Tippfehler nicht verziehen und die Suche wird um den Faktor 2 höher gewertet.

Die letzte Suche ist dann schließlich eine fehlerverzeihende Suche. Allerdings ist hier weder ein Typ, noch ein Operator angegeben. Es wird daher in allen Feldern gesucht und sobald eines der Suchwörter gefunden wird, liefert die Suche diesen Datensatz zurück. Zusätzlich darf es hier auch einen Tippfehler von einem Zeichen haben. Es werden hier also sehr viele Datensätze gefunden, die wir vermutlich nicht haben wollen. Zwar kommen diese ganz zum Schluss, da sie keinen „boost“ erhalten.

Suche verbessern

Meine erste Lösung war es ja noch gewesen, einfach die letzte Suche zu entfernen. Somit habe ich alle ungenauen Suchen Treffer entfernt. Aber ich habe eben auch die Unschärfesuche dadurch deaktiviert. Wie könnte also eine bessere Suche für einen Blog aussehen?

Wie würde man selbst denn nach etwas in einem Blog suchen? Also welche Suchanfrage würde man verwenden und welche Wörter an welche Stelle setzen? Vermutlich würde man die wichtigsten Wörter vorne angeben und unwichtigere weiter hinten.

Welche Beiträge würde man dann am liebsten finden? Solche, die die Suchanfrage um Inhalt enthalten, oder eher solche, die sie im Titel tragen? Vermutlich eher letztere. Daher habe ich mir ein wenig Gedanken gemacht, wie eine bessere Suche aussehen könnte und bin zu folgendem Ergebnis gekommen:

function ep_fine_tuning_ep_formatted_args( $formatted_args, $args ) {

	$search_fields = array(
		'post_title^4',
		'post_excerpt^2',
		'post_content',
	);

	$query = array(
		'bool' => array(
			'should' => array(
				array(
					'multi_match' => array(
						'query'                => $args['s'],
						'type'                 => 'phrase',
						'fields'               => $search_fields,
						'fuzziness'            => 'AUTO',
						'minimum_should_match' => '2<-25%',
					),
				),
				array(
					'multi_match' => array(
						'query'     => $args['s'],
						'fields'    => $search_fields,
						'operator'  => 'and',
						'fuzziness' => 'AUTO',
					),
				),
			),
		),
	);

	$formatted_args['query'] = $query;

	return $formatted_args;
}

add_filter( 'ep_formatted_args', 'ep_fine_tuning_ep_formatted_args', 10, 2 );

Ich überschreibe in diesem Fall den gesamten „query“ Teil der Anfrage an Elasticsearch. Da für mich das Feld Titel am wichtigsten ist, „booste“ ich dieses Feld um das Vierfache. Hierzu verwende ich „per-field boosting„. Das Feld mit dem Auszug werte ich doppelt, da hier in der Regel auch eher relevantere Wörter vorkommen, als im Rest des Textes.

Die „fuzziness“ stelle ich auf „AUTO“ ein, womit dynamisch, je nach Wortlänge, ein Wert von 0 – 2 angesetzt wird. Als letzte Maßnahme setze ich noch den Wert von „minimum_should_match„, der dafür sorgt, dass bis zu einer Länge von 2 Suchbegriffen beide gefunden werden müssen und bei mehr Begriffen alle bis auf 25% (also 75% oder einer mehr).

Fazit

Wie ihr vermutlich gemerkt habt, gibt es sehr viele Möglichkeiten, eine Suche zu optimieren. Da Elasticsearch sehr viel komplexer als MySQL ist und da es sehr viele unterschiedliche Daten gibt, in denen gesucht werden kann, ist eine optimale Konfiguration nicht immer einfach zu finden. ElasticPress gibt hier vermutlich eine sehr grobe Standardsuche vor, die aber eben für meinen Blog nicht wirklich zu befriedigenden Ergebnissen geführt hat.

Ich wünsche euch auf jeden Fall viel Spaß beim Experimentieren mit Elasticsearch. Vermutlich werde ich noch einen weiteren Artikel hierzu schreiben, da das Projekt, in dem ich Elasticsearch einsetze, noch nicht vorbei ist 🙂

P.S. Ich habe euch den Code als Plugin mal wieder als Gist gespeichert, damit ihr es gleich mal testen könnt. Falls ihr eine bessere Konfiguration gefunden habt, dann hinterlasst doch gerne einen Kommentar oder erstellt einen Fork 🙂

Veröffentlicht von

Bernhard ist fest angestellter Webentwickler, entwickelt in seiner Freizeit Plugins, schreibt in seinem Blog über WordPress und andere Themen, treibt sich gerne bei den WP Meetups in Berlin und Potsdam herum und läuft nach Feierabend den ein oder anderen Halbmarathon.

5 Kommentare » Schreibe einen Kommentar

  1. Moin!

    So wie ich Suchmaschinen-Anbieter immer wieder verstehe, werden vermehrt ganze Sätze/Fragen gesucht werden.
    Ist die Frage ob das auch für einen Blog gilt. Oder ob durch die thematische Beschränkung die Stichwort weiterhin stärker im Focus stehen.

    es grüßt
    derRALF

    • Ich denke für Google und Co. stimmt das mit ganzen Sätzen. Auf einem einzelnen Blog erwartet man aber eher nicht, dass dann auch etwas gefunden wird und würde daher eher nach maximal 5 Wörtern suchen. Aber das ist natürlich nur eine Vermutung von mir.

      Glücklicherweise hat Elasticsearch auch Statistiken zu den Suchanfragen. Vielleicht bietet mir das ja einen besseren Einblick, was genau gesucht wird. Falls es hierzu dann interessante Erkenntnisse gibt, werde ich natürlich darüber berichten 🙂

  2. Hallo Bernhard,

    vielen Dank für die Einführung. Auch ich kämpfe mit der ElasticSearch. Ein schönes Beispiel von schlechten Ergebnissen gibt es bei z.B. beim Querry „Spinne“:
    traum-deutung.de/?s=spinne

    Es kommt nur ein „relevanter“ Artikel ( traum-deutung.de/vogelspinne/ )obwohl wir 8 Artikel zur Spinne (auch im Titel/Content/URL) haben. Die irgendwo nach Platz 20 der Ergebnisliste erschienen (oder auch garnicht).

    1 traum-deutung.de/spinnennetz/
    2 traum-deutung.de/spinnenbiss/
    3 traum-deutung.de/viele-spinnen/
    4 traum-deutung.de/spinne-im-bett/
    5 traum-deutung.de/grosse-spinne/
    6 traum-deutung.de/giftige-spinne/
    7 traum-deutung.de/ekelige-spinne/
    8 traum-deutung.de/spinne/

    Ich nutze WordPressplugin „ElasticPress“ und als Hoster Amazon mit Elasticsearch 2.3. Habe das gleiche Problem aber auch bei Searchly gehabt. Die Artikel sind in den Index übertragen worden und auch vorhanden.

    Ich bin ein wenig Ahnungslos den Einstellungsmöglichkeiten habe ich weder im Plugin noch bei Amazon.

    PS: Ich habe das http bei den URL’s mal entfernt…

    • Hallo Gerald,

      schön, dass dir mein Artikel gefallen hat. Die Einrichtung von Elasticsearch für optimale Suchergebnisse ist wirklich nicht gerade einfach, da man sich mit einer ganz neuen Abfragesprache vertraut machen muss.

      Ich kann dir hier nur empfehlen, dir mal die offizielle Dokumentation der Query DSL anzusehen. Um festzustellen, welche Anfrage ElasticPress an den Elasticsearch Server schickt, verwende am besten das Plugin Debug Bar ElasticPress, eine Erweiterung für das Plugin Debug Bar. Hiermit konnte ich die Abfrage weiter optimieren.

      Eventuell hat ja eine Amazon Elasticsearch Instanz auch eine Konsole, auf der du Suchanfragen direkt testen kannst. Das geht vermutlich noch schneller.

      Viel Erfolg beim Experimentieren.

      • Hallo Bernhard,

        vielen Dank für deine Antwort. Heute habe ich noch einmal einen Anlauft gewagt. Bei Amz. Elasticsearch gibt es keine Console, aber für den Crome eine schöne Extention „elasticsearch toolbox“ hier kann man gut diverse Kombionationen durchtesten.
        Interessant ist das einfache Querrys wie:

        {
            "query": {
                "query_string": {
                    "query": "spine",
                  	"fields": ["post_title","post_content"],
                  	"fuzziness": "AUTO"
                  
                }
            }
        }
        

        über toolbox ein korrektes Ergebnis bringen. Allerdings „NICHT“ mit der Erweiterung, denn hier ist weiterhin (bei gleichem Querry) die Sortierung vollkommen anders. Über dein Plugin müsste der Querry doch aussehen wie:

        $query = array(
        	"query" => array(
        		"query_string" => array(
        			"query" => $args['s'],
        			"fields"  =>  $search_fields,
        			"fuzziness"  => "AUTO"
        		)
        	)
        );
        

        Die Debugbar bringt leider keine Erkenntnisse :

        {
            "from": 0,
            "size": 10,
            "sort": [
                {
                    "_score": {
                        "order": "desc"
                    }
                }
            ],
            "query": {
                "function_score": {
                    "query": {
                        "bool": {
                            "should": [
                                {
                                    "multi_match": {
                                        "query": "spinne",
                                        "fields": [
                                            "post_title",
                                            "post_content"
                                        ]
                                    }
                                }
                            ]
                        }
                    },
                    "exp": {
                        "post_date_gmt": {
                            "scale": "14d",
                            "decay": 0.25,
                            "offset": "7d"
                        }
                    }
                }
            },
            "post_filter": {
                "bool": {
                    "must": [
                        {
                            "terms": {
                                "post_type.raw": [
                                    "post",
                                    "page",
                                    "attachment"
                                ]
                            }
                        },
                        {
                            "term": {
                                "post_status": "publish"
                            }
                        }
                    ]
                }
            }
        }
        

        Als nächstes habe ich es mit dem Hochsetzen des per-field boostings leider auch ohne nennenswerte umsortierung. Bist du dort mit deiner Umsetzung vielleicht weitergekommen?

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert