J'ai créé une API de recherche de château avec Elasticsearch + Sudachi + Go + echo

J'ai créé une API de recherche après avoir appris le langage Elasticsearch et Go, j'ai donc résumé les problèmes liés à l'utilisation et à la configuration des livrables.

Environnement d'exécution https://github.com/takenoko-gohan/castle-search-api-environment API de recherche https://github.com/takenoko-gohan/castle-search-api

Environnement

Dans la construction d'environnement, docker et docker-compose sont utilisés.

git clone https://github.com/takenoko-gohan/castle-search-api-environment.git
cd castle-search-api-environment
docker-compose build --no-cache
docker-compose up -d
#Veuillez exécuter après un certain temps après avoir démarré elasticsearch
sh es/script/es_init.sh 

Comment utiliser

Lorsque vous utilisez l'API de recherche, faites une demande dans le formulaire suivant. Le paramètre de requête "mot-clé" spécifie le mot-clé lors de la recherche. Dans le paramètre de requête "prefecture", spécifiez la préfecture que vous souhaitez affiner. La commande suivante recherche les châteaux dont la préfecture est "Fukushima" et contient le mot-clé "Château de Tsuruga".

curl -XGET "http://localhost:8080/search?keyword=Château de Tsuruga&prefecture=Préfecture de Fukushima"

Constitution

Elasticsearch

Réglage de l'index

L'indice a été défini comme suit. Au moment de la recherche et de l'indexation, l'analyseur utilise le mode de recherche lors de la division en jetons, supprime les parties contenant des mots auxiliaires, des verbes auxiliaires, des signes de ponctuation et des points de lecture, et définit le jeton pour qu'il devienne SudachiNormalizedFormAttribute.

index_settings.json
{
  "settings": {
    "index": {
      "number_of_shards": 1,
      "number_of_replicas": 0,
      "analysis": {
        "tokenizer": {
          "sudachi_tokenizer": {
            "type": "sudachi_tokenizer",
            "split_mode": "C",
            "discard_punctuation": true,
            "resources_path": "/usr/share/elasticsearch/config/sudachi",
            "settings_path": "/usr/share/elasticsearch/config/sudachi/sudachi.json"
          }
        },
        "analyzer": {
          "sudachi_analyzer": {
            "filter": [
              "my_searchfilter",
              "my_posfilter",
              "sudachi_normalizedform"
            ],
            "tokenizer": "sudachi_tokenizer",
            "type": "custom"
          }
        },
        "filter":{
          "my_searchfilter": {
            "type": "sudachi_split",
            "mode": "search"
          },
          "my_posfilter":{
            "type":"sudachi_part_of_speech",
            "stoptags":[
              "Particule",
              "Verbe auxiliaire",
              "Symbole auxiliaire,Phrase",
              "Symbole auxiliaire,Point de lecture"
            ]
          }
        }
      }
    }
  }
}

Mappage d'index

Le mappage d'index est le suivant.

champ type Remarques
name text Nom du château
prefectures keyword Préfectures
rulers text Propriétaire du château
description text Vue d'ensemble du château
index_mappings.json
{
  "properties": {
    "name": {"type" : "text", "analyzer": "sudachi_analyzer"},
    "prefecture": {"type": "keyword"},
    "rulers": {"type": "text", "analyzer": "sudachi_analyzer"},
    "description": {"type": "text", "analyzer": "sudachi_analyzer"}
  }
}

document

L'index de recherche est "[Catégorie: 100 châteaux célèbres au Japon] de Wikipedia](https://ja.wikipedia.org/wiki/Category:%E6%97%A5%E6%9C%AC100%E5%90%8D%E5" Les données créées sur la base de «% 9F% 8E)» sont insérées.

API de recherche

L'API de recherche est le framework de langage Go "echo" et le client de recherche Elastic "go-elasticsearch". Je l'ai créé en utilisant. L'API facilite la création et l'exécution d'une requête à Elasticsearch en fonction des paramètres reçus en premier, et répond au client tel quel avec chaque champ du document qui a frappé la recherche.

Dans la recherche utilisant le paramètre de requête "mot-clé", le score est pondéré dans l'ordre "nom> règles> description" à l'aide de boost. Lors de la recherche à l'aide du paramètre de requête "prefecture", nous essayons d'effectuer une recherche de correspondance exacte pour le champ "prefecture".

Créer une requête
package search

func createQuery(q *Query) map[string]interface{} {
	query := map[string]interface{}{}
	if q.Keyword != "" && q.Prefecture != "" {
		query = map[string]interface{}{
			"query": map[string]interface{}{
				"bool": map[string]interface{}{
					"must": []map[string]interface{}{
						{
							"bool": map[string]interface{}{
								"should": []map[string]interface{}{
									{
										"match": map[string]interface{}{
											"name": map[string]interface{}{
												"query": q.Keyword,
												"boost": 3,
											},
										},
									},
									{
										"match": map[string]interface{}{
											"rulers": map[string]interface{}{
												"query": q.Keyword,
												"boost": 2,
											},
										},
									},
									{
										"match": map[string]interface{}{
											"description": map[string]interface{}{
												"query": q.Keyword,
												"boost": 1,
											},
										},
									},
								},
								"minimum_should_match": 1,
							},
						},
						{
							"bool": map[string]interface{}{
								"must": []map[string]interface{}{
									{
										"term": map[string]interface{}{
											"prefecture": q.Prefecture,
										},
									},
								},
							},
						},
					},
				},
			},
		}
	} else if q.Keyword != "" && q.Prefecture == "" {
		query = map[string]interface{}{
			"query": map[string]interface{}{
				"bool": map[string]interface{}{
					"should": []map[string]interface{}{
						{
							"match": map[string]interface{}{
								"name": map[string]interface{}{
									"query": q.Keyword,
									"boost": 3,
								},
							},
						},
						{
							"match": map[string]interface{}{
								"rulers": map[string]interface{}{
									"query": q.Keyword,
									"boost": 2,
								},
							},
						},
						{
							"match": map[string]interface{}{
								"description": map[string]interface{}{
									"query": q.Keyword,
									"boost": 1,
								},
							},
						},
					},
					"minimum_should_match": 1,
				},
			},
		}
	} else if q.Keyword == "" && q.Prefecture != "" {
		query = map[string]interface{}{
			"query": map[string]interface{}{
				"bool": map[string]interface{}{
					"must": []map[string]interface{}{
						{
							"term": map[string]interface{}{
								"prefecture": q.Prefecture,
							},
						},
					},
				},
			},
		}
	}

	return query
}

Endroit gênant

Lorsque j'ai vérifié l'opération après l'avoir créée pour le moment, j'ai reçu la réponse suivante.

curl -XGET "http://localhost:8080/search?keyword=Château de Wakamatsu&prefectures=Préfecture de Fukushima"
{
    "message": "La recherche a réussi.",
    "Results": [
        {
            "name": "Château de Wakamatsu",
            "prefecture": "Préfecture de Fukushima",
            "rulers": [
                "M. Gamo, M. Uesugi, M. Kato, M. Hoshina, Matsudaira Aizu"
            ],
            "description": "Le château de Wakamatsu est situé à 1 Pursuit Town, Aizu Wakamatsu City, Fukushima Prefecture.-C'est un château japonais qui était en 1. Il est généralement appelé Tsurugajo dans la région, et est souvent appelé château Aizu Wakamatsu en dehors de la région. Dans l'histoire de la littérature, il est parfois appelé château de Kurokawa ou château d'Aizu. En tant que site historique national, il est désigné sous le nom de Ruines du château de Wakamatsu."
        },
        {
            "name": "Château de Nihonmatsu",
            "prefecture": "Préfecture de Fukushima",
            "rulers": [
                "M. Kato",
                "M. Niwa",
                "M. Gamo",
                "M. Nihonmatsu",
                "M. Uesugi",
                "M. Date"
            ],
            "description": "Nihonmatsujo est un château japonais (château Hirayama) situé dans le Guo, ville de Nihonmatsu, préfecture de Fukushima. L'un des 100 châteaux célèbres du Japon. Aussi connu sous le nom de château de Kasumiga et de château de Shirahata. Le 26 juillet 2007, il a été désigné lieu historique national comme site du château Nihonmatsu. Il a été sélectionné comme l'un des 100 meilleurs sites d'observation des fleurs de cerisier au Japon sous le nom de "Kasumigajo Park"."
        },
        {
            "name": "Château de Shirakawa Komine",
            "prefecture": "Préfecture de Fukushima",
            "rulers": [
                "M. Matsudaira",
                "M. Niwa",
                "M. Yuki Shirakawa",
                "M. Gamo",
                "M. Abe_(Tokugawa Fuyo)"
            ],
            "description": "Le château de Shirakawa Komine est un château japonais situé dans la ville de Shirakawa, préfecture de Fukushima (Shirakawa, Shirakawa-gun, Rikuokukoku). Aussi appelé simplement château de Shirakawa ou château de Komine. Il est désigné lieu historique national. De plus, c'est l'un des 100 châteaux célèbres du Japon."
        }
    ]
}

Les résultats de la recherche ont supposé que seul le château de Wakamatsu serait touché, mais d'autres châteaux de la préfecture de Fukushima ont également été touchés. Ainsi, quand j'ai vérifié comment la commande suivante a été analysée, le château de Wakamatsu semble être divisé par "Wakamatsu / Castle". Par conséquent, il semble que d'autres châteaux aient également été touchés par le "château" qui a été écrit séparément au moment de la recherche.

curl -XGET 'http://localhost:9200/castle/_analyze?pretty' -H 'Content-Type: application/json' -d '
{
  "text": "Château de Wakamatsu",
  "analyzer": "sudachi_analyzer"
}'
{
  "tokens" : [
    {
      "token" : "Wakamatsu",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "Château",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "word",
      "position" : 1
    }
  ]
}

Par conséquent, en vous référant à cet article, créez un fichier CSV au format suivant, et créez un dictionnaire utilisateur dans lequel les noms de chaque château sont répertoriés dans l'analyseur. S'est enregistré.

Château de Wakamatsu,4786,4786,5000,Château de Wakamatsu,nom,固有nom,Général,*,*,*,Wakamatsujo,Château de Wakamatsu,*,*,*,*,*

Après m'être enregistré dans le dictionnaire utilisateur, j'ai vérifié les résultats de l'analyse, mais cette fois, c'est devenu la nomenclature appropriée "Château de Wakamatsu".

curl -XGET 'http://localhost:9200/castle/_analyze?pretty' -H 'Content-Type: application/json' -d '
{
  "text": "Château de Wakamatsu",
  "analyzer": "sudachi_analyzer"
}'
{
  "tokens" : [
    {
      "token" : "Château de Wakamatsu",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "word",
      "position" : 0
    }
  ]
}

Quand j'ai cherché à nouveau avec l'API de recherche, seul le château de Wakamatsu est venu frapper comme prévu.

curl -XGET "http://localhost:8080/search?keyword=Château de Wakamatsu&prefecture=Préfecture de Fukushima"
{
    "message": "La recherche a réussi.",
    "Results": [
        {
            "name": "Château de Wakamatsu",
            "prefecture": "Préfecture de Fukushima",
            "rulers": [
                "M. Gamo, M. Uesugi, M. Kato, M. Hoshina, Matsudaira Aizu"
            ],
            "description": "Le château de Wakamatsu est situé à 1 Pursuit Town, Aizu Wakamatsu City, Fukushima Prefecture.-C'est un château japonais qui était en 1. Il est généralement appelé Tsurugajo dans la région, et est souvent appelé château Aizu Wakamatsu en dehors de la région. Dans l'histoire de la littérature, il est parfois appelé château de Kurokawa ou château d'Aizu. En tant que site historique national, il est désigné sous le nom de Ruines du château de Wakamatsu."
        }
    ]
}

Mais un autre problème s'est posé. Cette fois, lorsque j'ai cherché des mots-clés à Wakamatsu et dans la préfecture de Fukushima, rien n'a été trouvé. En enregistrant le dictionnaire utilisateur, il semble que le château de Wakamatsu n'ait pas frappé car il n'était plus divisé par "Wakamatsu / Château".

curl -XGET "http://localhost:8080/search?keyword=Wakamatsu&prefecture=Préfecture de Fukushima"
{
    "message": "La recherche a réussi.",
    "Results": null
}

Selon here, les informations pour diviser en unités A peuvent être décrites dans la 16e colonne du fichier CSV. est. Par conséquent, j'ai modifié le fichier CSV sous la forme suivante afin qu'il puisse être divisé en unités C et unités A en mode de recherche. (Seulement le château de Wakamatsu, le château de Nihonmatsu et le château de Shirakawa Komine ...)

Château de Wakamatsu,4786,4786,5000,Château de Wakamatsu,nom,固有nom,Général,*,*,*,Wakamatsujo,Château de Wakamatsu,*,C,650091/368637,*,*
Château de Nihonmatsu,4786,4786,5000,Château de Nihonmatsu,nom,固有nom,Général,*,*,*,Japonais Matsujo,Château de Nihonmatsu,*,C,281483/368637,*,*
Château de Shirakawa Komine,4786,4786,5000,Château de Shirakawa Komine,nom,固有nom,Général,*,*,*,Shirakawa Kominejo,Château de Shirakawa Komine,*,C,584799/394859/368637,*,*
curl -XGET 'http://localhost:9200/castle/_analyze?pretty' -H 'Content-Type: application/json' -d '
{
  "text": "Château de Wakamatsu",
  "analyzer": "sudachi_analyzer"
}'
{
  "tokens" : [
    {
      "token" : "Château de Wakamatsu",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "word",
      "position" : 0,
      "positionLength" : 2
    },
    {
      "token" : "Wakamatsu",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "Château",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "word",
      "position" : 1
    }
  ]
}

Maintenant, même si vous recherchez un mot-clé à Wakamatsu et dans la préfecture de la préfecture de Fukushima, le château de Wakamatsu sera un succès. Il est difficile d'obtenir la recherche souhaitée.

curl  -XGET "http://localhost:8080/search?keyword=Château de Wakamatsu&prefecture=Préfecture de Fukushima"
{
    "message": "La recherche a réussi.",
    "Results": [
        {
            "name": "Château de Wakamatsu",
            "prefecture": "Préfecture de Fukushima",
            "rulers": [
                "M. Gamo, M. Uesugi, M. Kato, M. Hoshina, Matsudaira Aizu"
            ],
            "description": "Le château de Wakamatsu est situé à 1 Pursuit Town, Aizu Wakamatsu City, Fukushima Prefecture.-C'est un château japonais qui était en 1. Il est généralement appelé Tsurugajo dans la région, et est souvent appelé château Aizu Wakamatsu en dehors de la région. Dans l'histoire de la littérature, il est parfois appelé château de Kurokawa ou château d'Aizu. En tant que lieu historique national, il est désigné sous le nom de Ruines du château de Wakamatsu."
        }
    ]
}

référence

Travaux pratiques pour créer un dictionnaire utilisateur avec Elasticsearch + Sudachi + Docker Comment créer un dictionnaire utilisateur Sudachi elasticsearch-sudachi README go-elasticsearch README

Recommended Posts

J'ai créé une API de recherche de château avec Elasticsearch + Sudachi + Go + echo
J'ai créé une API Web
J'ai essayé de créer un mécanisme de contrôle exclusif avec Go
J'ai essayé de créer un LINE BOT "Sakurai-san" avec API Gateway + Lambda
J'ai essayé de créer une API de reconnaissance d'image simple avec Fast API et Tensorflow
[5e] J'ai essayé de créer un certain outil de type Authenticator avec python
Rubyist a essayé de créer une API simple avec Python + bouteille + MySQL
[2nd] J'ai essayé de créer un certain outil de type Authenticator avec python
[3ème] J'ai essayé de créer un certain outil de type Authenticator avec python
Je souhaite rechercher le texte intégral avec elasticsearch + python
J'ai essayé de faire un processus d'exécution périodique avec Selenium et Python
J'ai essayé de créer une application de notification de publication à 2 canaux avec Python
J'ai essayé de créer une application todo en utilisant une bouteille avec python
[4th] J'ai essayé de créer un certain outil de type Authenticator avec python
[1er] J'ai essayé de créer un certain outil de type Authenticator avec python
J'ai essayé de faire une étrange citation pour Jojo avec LSTM
J'ai essayé de faire un signal avec Raspeye 4 (édition Python)
J'ai essayé de créer un service de raccourcissement d'url sans serveur avec AWS CDK
Je veux faire un jeu avec Python
J'ai créé un jeu ○ ✕ avec TensorFlow
J'ai essayé de faire un processus périodique avec CentOS7, Selenium, Python et Chrome
J'ai fait une application d'envoi de courrier simple avec tkinter de Python
Quand j'ai essayé de créer un VPC avec AWS CDK mais que je n'ai pas pu le faire
[Analyse des brevets] J'ai essayé de créer une carte des brevets avec Python sans dépenser d'argent
J'ai essayé de faire un "putain de gros convertisseur de littérature"
[Go + Gin] J'ai essayé de créer un environnement Docker
J'ai essayé de découvrir notre obscurité avec l'API Chatwork
J'ai essayé de créer une application OCR avec PySimpleGUI
J'ai essayé de faire quelque chose comme un chatbot avec le modèle Seq2Seq de TensorFlow
J'ai essayé Learning-to-Rank avec Elasticsearch!
J'ai essayé de faire de l'art créatif avec l'IA! J'ai programmé une nouveauté! (Article: Réseau Adversaire Créatif)
J'ai essayé de créer une classe pour rechercher des fichiers avec la méthode Glob de Python dans VBA
J'ai essayé d'implémenter une ligne moyenne mobile de volume avec Quantx
J'ai essayé de rechercher des vidéos à l'aide de l'API de données Youtube (débutant)
J'ai essayé de créer diverses "données factices" avec Python faker
Je veux créer un éditeur de blog avec l'administrateur de django
Je veux faire une macro de clic avec pyautogui (désir)
J'ai essayé de résoudre le problème d'optimisation des combinaisons avec Qiskit
Je veux faire une macro de clic avec pyautogui (Outlook)
J'ai essayé de commencer avec Hy ・ Définir une classe
J'ai essayé de trier une colonne FizzBuzz aléatoire avec un tri à bulles.
J'ai fait un chronomètre en utilisant tkinter avec python
J'ai essayé de créer une interface graphique à trois yeux côte à côte avec Python et Tkinter
J'ai essayé d'écrire dans un modèle de langage profondément appris
J'ai créé un éditeur de texte simple en utilisant PyQt
[1 hour challenge] J'ai essayé de créer un site de bonne aventure qui soit trop adapté à Python
J'ai essayé de créer une commande de recherche de documents slack à l'aide de Kendra annoncée immédiatement à re: Invent 2019
J'ai essayé de créer un générateur qui génère une classe conteneur C # à partir de CSV avec Python
J'ai essayé de créer une caméra de surveillance à détection de mouvement avec OpenCV en utilisant une caméra WEB avec Raspberry Pi
J'ai essayé de créer l'API Quip
J'ai touché l'API de Tesla
J'ai essayé de rendre le deep learning évolutif avec Spark × Keras × Docker
Un mémorandum lors de l'acquisition automatique avec du sélénium
J'ai essayé de créer une expression régulière de "montant" en utilisant Python