Nie czytaj plików. Elasticsearch zrobi to za Ciebie.

Pewnie się zdziwi Cie ta informacja. Elasticsearch służy do… szukania. Tak. To prawda. Okazuje się, że można go wykorzystać również do indeksowania zawartości plików typu doc, docx, pdf itp. W tym wpisie przyjrzymy się jak to zrobić, jak zmienić analizator oraz jak „zgubić” plik jeśli i tak trzymamy go np. na S3.

Psst! Link do repo na dole artykułu.

Po co?

Nie zawsze fraza, której szukamy, znajduje się w nazwie pliku, tytule i innych dostarczonych metadanych. Wyobraź sobie portal umożliwiający gromadzenie i wyszukiwanie artykułów naukowych. Każdy artykuł to osobny plik PDF. Dodanie do obszaru wyszukiwania abstraktów już zauważalnie zwiększyłoby satysfakcję korzystania z portalu. Nie bez powodu ScienceDirect korzysta z Elasticsearch.

Środowisko

Jak tylko mogę, korzystam z Docker. W tym przypadku nie jest inaczej. Ułatwia to Wam, czytelnikom, w łatwy sposób powtórzyć to co udało mi się pokazać w artykule.

Aby umożliwić analizę plików w Elasticsearch, potrzebny jest Ingest Attachment Processor Plugin. W przypadku Docker moglibyśmy ręcznie włączyć terminal wewnątrz kontenera poniższą komendą.

sudo docker exec -it nazwa-kontenera bash

I zainstalować plugin, ale jest to kiepski pomysł. W przypadku usunięcia kontenera będziemy musieli tę czynność powtórzyć. Aby tego uniknąć, lekko zmodyfikowałem Docker Compose z ELK-iem z wpisu o Dockerze i dodałem prosty Dockerfile.

FROM docker.elastic.co/elasticsearch/elasticsearch:7.6.0
RUN bin/elasticsearch-plugin install --batch ingest-attachment

version: '2.2'
services:
  elasticsearch:
    build: ./custom-elasticsearch/
#    image: docker.elastic.co/elasticsearch/elasticsearch:7.6.0
    restart: unless-stopped
...

Wszystkie operacje na Elasticsearch (oprócz dodania pliku) wykonałem w Dev Tools na Kibana.

Przygotowanie Pipeline

Co to w ogóle Pipeline? Jest to definicja serii procesorów, które będą wykonywane w tej samej kolejności, w jakiej zostały zadeklarowane. Innymi słowy, dokument, który wrzucamy do bazy, zostanie przepuszczony przez każdy zdefiniowany procesor. Możemy w ten sposób wzbogacać dokument o nowe pola, przekształcać, a nawet usuwać jeśli spełniony zostanie zdefiniowany warunek.

Dodany wcześniej plugin zawiera pipeline, który rozpakuje i przeanalizuje dodawany plik. Pliki przekazywane są w postaci Base64. Poniżej deklaracja prostego pipeline.

PUT _ingest/pipeline/attachment
{
  "description" : "What did you hide in this file? (¬‿¬)",
  "processors" : [
    {
      "attachment" : {
        "field" : "data"
      }
    }
  ]
}

Dodanie pliku

Przygotowałem pliki doc, docx, pdf. Wrzuciłem treść piosenki U2 – I Still Haven’t Found What I’m Looking For. Jeden z pdf składa się z obrazka zrzutu ekranu tekstu. Czy plugin ma w sobie OCR? Dowiemy się.

Fragment pliku w Base64

Wklejanie Base64 do Postmana lub Dev Tools w Kibanie jest słabe. Zabiera za dużo miejsca. Dlatego posłużymy się CURL-em. Dodamy dokument z parametrami filename i data do indeksu songs. Zwróć uwagę na ?pipeline=attachment w którym wskazujemy wcześniej zdefiniowany Pipeline.

(echo -n '{"filename":"U2.docx", "data": "'; base64 ./U2.docx; echo '"}') |
curl -H "Content-Type: application/json" -d @-  http://192.168.114.128:9200/songs/_doc/1?pipeline=attachment

Efektem jest dokument w bazie, który wygląda tak (pozwoliłem sobie usunąć base64 oraz część piosenki):

GET /songs/_doc/1
{
  "_index" : "songs",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 1,
  "_seq_no" : 0,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "filename" : "U2.docx",
    "data" : "UEsDBBQABgAIAAAAIQDfpNJsWgEAACAFAAATAAgCW0NvbnRlbnRfVHlwZXNdLnhtbCCiBAIooAACAAAAA...AsyoAAGRvY1Byb3BzL2FwcC54bWxQSwUGAAAAAAsACwDBAgAAXy0AAAAA",
    "attachment" : {
      "date" : "2020-02-21T19:14:00Z",
      "content_type" : "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
      "author" : "Maciej Szymczyk",
      "language" : "en",
      "title" : "U2 - I Still Haven't Found What I'm Looking For",
      "content" : """I have climbed highest mountain
I have run through the fields
Only to be with you
Only to be with you
...
But I still haven't found
What I'm looking for
But I still haven't found
What I'm looking for""",
      "content_length" : 931
    }
  }
}

Oprócz treści w attachment.content, dostaliśmy również metadane jakimi są tytuł, język, autor, data.

Wyszukiwanie

Okazuje się, że z wyszukiwaniem nie jest tak kolorowo. O ile szukanie słowa „kissed” zwraca nam dodany plik

POST /songs/_search
{
  "query":{
    "term":{
      "attachment.content": "kissed"
    }
  }
}
POST /songs/_search
{
  "query": {
    "query_string": {
      "default_field": "attachment.content", 
      "query": "kissed"
    }
  }
}

To słowo „kiss” nie zwraca nam żadnych wyników.

POST /songs/_search
{
  "query":{
    "term":{
      "attachment.content": "kiss"
    }
  }
}
POST /songs/_search
{
  "query": {
    "query_string": {
      "default_field": "attachment.content", 
      "query": "kiss"
    }
  }
}

Dzieje się tak, ponieważ nie przygotowaliśmy indeksu przed dodaniem pierwszego rekordu. Chodzi mi tu głównie o analizator dla języka angielskiego. Oprócz usunięcia znaków przystankowych i łączników, przekształci czasowniki do podstawowej formy ( kissed -> kiss ). Możemy sprawdzić jak taki analizator działa poniższym zapytaniem.

GET /_analyze
{
  "analyzer": "english",
  "text": """I have climbed highest mountain
              I have run through the fields"""
}

Poprawmy ten błąd. Usuńmy indeks, dodajmy definicje indeksu i plik z piosenką.

DELETE /songs

PUT /songs
{
  "mappings": {
    "properties": {
      "attachment.content": {
        "type": "text",
        "analyzer": "english"
      }
    }
  }
}

Teraz użycie query_string zwraca nam rekord niezależnie czy wpiszemy kiss, czy kissed. Natomiast użycie zapytania z term odwróciło się (kiss działa, a kissed nie). Czemu się tak dzieje? Pamiętaj, że w query_string podana fraza przechodzi przez analizator użyty w danym polu. Więc kissed jest i tak przekształcony w kiss. Jeśli chcesz, bym bardziej szczegółowo opisał analizatory. Daj znać w komentarzu.

Ale ja nie potrzebuję analizować całego pliku

Pliki bywają różne, kwadratowe i podłużne, a na pewno duże i małe. Aby nie tracić czasu na cały plik, możemy ograniczyć treści do przeanalizowania. To samo tyczy się metadanych pliku.

PUT _ingest/pipeline/better_attachment
{
  "description" : "What did you hide in this file? better version (¬‿¬)",
  "processors" : [
    {
      "attachment" : {
        "field" : "data",
        "properties": [ "content", "title" ],
        "indexed_chars" : 20,
        "indexed_chars_field" : "max_size"
      }
    }
  ]
}

Teraz rekord wygląda jak poniżej (pamietaj zmienić ?pipeline= przy dodawaniu).

{
  "_index" : "songs",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 1,
  "_seq_no" : 0,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "filename" : "U2.docx",
    "data" : "UEsDBBQABgAIAAAAIQDfpNJsWgEAACAFAAATAAgCW0NvbnRlbnRfVHlwZXNdLnhtbCCiBAIooAACAAAAA...AsyoAAGRvY1Byb3BzL2FwcC54bWxQSwUGAAAAAAsACwDBAgAAXy0AAAAA",
    "attachment" : {
      "title" : "U2 - I Still Haven't Found What I'm Looking For",
      "content" : "I have climbed highe"
    }
  }
}

Wystarczy mi treść. Pliki trzymam na S3

W takim przypadku możemy dodać kolejny procesor, a dokładnie Remove Processor.

PUT _ingest/pipeline/even_better_attachment
{
  "description" : "What did you hide in this file? even better version (¬‿¬)",
  "processors" : [
    {
      "attachment" : {
        "field" : "data",
        "properties": [ "content", "title" ],
        "indexed_chars" : 20,
        "indexed_chars_field" : "max_size"
      },
      "remove":{
        "field":"data"
      }
    }
  ]
}

Teraz dokument wygląda tak:

{
  "_index" : "songs",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 1,
  "_seq_no" : 0,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "filename" : "U2.docx",
    "attachment" : {
      "title" : "U2 - I Still Haven't Found What I'm Looking For",
      "content" : "I have climbed highe"
    }
  }
}

To co z tym OCR-em?

Niestety pdf z obrazkiem nie zadziałał 😊

A co z językiem polskim?

Dostępne są wtyczki dostarczające analizatory języka polskiego. O tym jak działają, przeczytasz na blogu Marcina Lewandowskiego (Cztery Tygodnie).

Repozytorium

https://github.com/zorteran/wiadro-danych-elasticsearch-ingest-attachment

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *