Indeksator folderów ElasticSearch

Dec 18 2022
Czy kiedykolwiek chciałeś przeprowadzić wyszukiwanie w folderze w systemie Windows, ale wyszukiwanie tego pliku trwa wiecznie? Masz dużo plików pdf lub dokumentów docx i nie pamiętasz, gdzie przeczytałeś zdanie lub szukasz konkretnego artykułu? Chcesz zbudować małą wyszukiwarkę dla swojej aplikacji korporacyjnej / niekorporacyjnej? Jeśli znajdziesz się w jednej z powyższych sytuacji lub po prostu jesteś ciekawy, jak użyć ElasticSearch do indeksowania zawartości folderu i jego podfolderów, ten artykuł jest dla Ciebie. Konfigurowanie sceny Rozwój tego artykułu odbywa się w prostym środowisku Windows.

Czy kiedykolwiek chciałeś przeprowadzić wyszukiwanie w folderze w systemie Windows, ale wyszukiwanie tego pliku trwa wiecznie? Masz dużo plików pdf lub dokumentów docx i nie pamiętasz, gdzie przeczytałeś zdanie lub szukasz konkretnego artykułu? Chcesz zbudować małą wyszukiwarkę dla swojej aplikacji korporacyjnej / niekorporacyjnej? Jeśli znajdziesz się w jednej z powyższych sytuacji lub po prostu jesteś ciekawy, jak użyć ElasticSearch do indeksowania zawartości folderu i jego podfolderów, ten artykuł jest dla Ciebie.

Ustawiać scenę

Rozwój tego artykułu odbywa się w prostym środowisku Windows. W chwili pisania tego artykułu używana jest wersja ElasticSearch 8.5.3 . Na końcu tego artykułu stworzymy w pełni działającą aplikację GUI, która na podstawie adresu URL instancji ElasticSearch i lokalizacji folderu indeksuje zawartość tego folderu do tej instancji ELK. Nazwę indeksu można wprowadzić z interfejsu użytkownika i może to być nazwa istniejąca lub nowa.

Elastic Cluster można skonfigurować jak zwykle z wieloma instancjami. Ponieważ jednak uruchamiamy instancję na jednym komputerze, możemy skonfigurować instancję do uruchamiania w trybie pojedynczego węzła . Szczegóły konfiguracji dotyczące tego kroku są szczegółowo opisane w poniższych sekcjach.

Do zbudowania aplikacji GUI i indeksatora zdecydowałem się użyć Pythona , ze względu na jego bogate biblioteki, krótki i łatwy rozwój. W mniej niż 250 liniach kodu napisany jest w pełni funkcjonalny indeksator z możliwością indeksowania docx, pdf i tekstu.

Konfiguracja ElasticSearch

Po pobraniu pliku zip elasticsearch wyodrębniłem go do katalogu. Pierwszym krokiem jest wyłączenie zabezpieczeń, ponieważ instancja zawsze będzie działać lokalnie. Jednak w środowisku zdalnym na poziomie produkcyjnym nigdy nie należy rezygnować z bezpieczeństwa. Następnie określamy, że węzeł powinien działać na hoście lokalnym i wykrywać tylko jeden węzeł na hoście lokalnym. Porty nie są zmieniane. (9200 dla http, 9300 dla tcp)

Konfigurację yml (elasticsearch.yml) można znaleźć poniżej:

xpack.security.enabled: false
xpack.security.enrollment.enabled: false
xpack.security.http.ssl:
  enabled: false

xpack.security.transport.ssl:
  enabled: false

http.host: 0.0.0.0
network.host: 0.0.0.0
discovery.type: single-node

-Xms4g
 -Xmx4g

Ponieważ celem tego artykułu nie jest omawianie, jak uruchomić instancję elasticsearch, a raczej jak jej używać do indeksowania folderu i jego podfolderów przy użyciu języka programowania wysokiego poziomu, takiego jak Python, przejdźmy do „zabawnej części”

Projektowanie interfejsu użytkownika

Indeksator folderów elasticsearch był początkowo aplikacją wiersza poleceń, ale w miarę postępu prac zdecydowałem się zbudować dla niego prosty GUI. Aby skonfigurować GUI, użyłem tkintera, ale zrobi to każda inna biblioteka gui Pythona.

Indeksator folderów ElasticSearch

import tkinter as tk
from PIL import ImageTk, Image
import tkinter.ttk as ttk

root= tk.Tk('Chipster')
root.title("ElasticSearch Folder Indexer ")
root.resizable(False,False)

canvas1 = tk.Canvas(root, width=400, height=600, relief='raised')

img = ImageTk.PhotoImage(Image.open("elk.png"))
img.width = 0.1
img.height = 0.1
imglabel = tk.Label(root,image=img)
canvas1.create_window(20,30,window = imglabel)

label1 = tk.Label(root, text='ElasticSearch Folder Indexer', background="lightblue")
label1.config(font=('Serif', 14, "bold"))
canvas1.create_window(230, 78, window=label1)



label2 = tk.Label(root, text='Enter the path of the folder to be indexed:')
label2.config(font=('helvetica', 10))
canvas1.create_window(200, 140, window=label2)

entry1 = tk.Entry(root , width=40) 
canvas1.create_window(200, 160, window=entry1)

label3 = tk.Label(root, text='ElasticSearch Instance (ex: http://localhost:9200):')
label3.config(font=('helvetica', 10))
canvas1.create_window(200, 200, window=label3)

entry2 = tk.Entry(root,width=40) 
canvas1.create_window(200, 220, window=entry2)


label4 = tk.Label(root, text='Index Name:')
label4.config(font=('helvetica', 10))
canvas1.create_window(200, 260, window=label4)

entry3 = tk.Entry(root,width=40) 
canvas1.create_window(200, 280, window=entry3)

text = tk.Text(root, height=10,width=40)

progress_label = tk.Label(root,text="")
progress_label.config(font=("helvetica",12,"bold"))


client = ''
connected= False
def connect():
      # Code that handles elasticsearch connection
     return

def index():
  # Code that handles elasticsearch indexing
 return


def start_combine_in_bg():
    # Threading to show real time logs in the Text Widget

style = ttk.Style()
style.theme_use('alt')
style.configure('TButton', background = 'red', foreground = 'white', width = 10, borderwidth=1, focusthickness=4, focuscolor='none' , font=('Sans serif', 12, "bold"))
style.map('TButton', background=[('active','indianred')])


button1 = ttk.Button(text='Index', command=start_combine_in_bg)
button12 = tk.Button(text='Connect' , command = connect , background="green" , foreground = 'white' ,width = 8, font=('Sans serif', 10, "bold"))
# button1.pack()
canvas1.create_window(250, 340, window=button1)
canvas1.create_window(150, 340, window=button12)


canvas1.create_window(200,370, window=progress_label)

canvas1.create_window(200,490,window=text)


canvas1.pack()
root.mainloop()

Po kliknięciu przycisku Indeks rozpoczyna się indeksowanie ElasticSearch. Jeśli jednak chcemy móc coś pokazać w konsoli (obiekt tekstowy w naszej implementacji) musimy mieć możliwość wysyłania tekstu w czasie rzeczywistym do wstawienia do kodu. Jeśli zarówno GUI, jak i logika indeksowania działają w tym samym wątku, przetwarzanie GUI będzie kontynuowane dopiero po zakończeniu całego procesu indeksowania, a my utkniemy z zamarzającym interfejsem użytkownika, zastanawiając się, co jest nie tak podczas całego procesu. Aby tego uniknąć, uruchamiamy indeksowanie w wątku w tle. Do tego służy funkcja start_combine_in_bg zdefiniowana poniżej. Celem wątku jest wywoływana funkcja do wykonania, czyli funkcja indeksu.

def start_combine_in_bg():
   threading.Thread(target=index).start()

def connect():
     global client 
     elk_url = entry2.get()
    
     if elk_url is None or not elk_url:
        client = Elasticsearch("http://localhost:9200")
     else: 
         client = Elasticsearch(elk_url)
     global connected 
     connected = True
     text.insert(tk.END ,'Existing Indices')
     indices = client.indices.get_alias()
     for index in indices:
        text.insert(tk.END ,'\n'+str(index))

     print(indices)
     return

Istnieją 2 główne funkcje, w których wykonywana jest logika indeksowania. Pierwszym z nich jest funkcja indeksu, która jest również swego rodzaju opakowaniem. Jego logika jest prosta i pokazana poniżej. Ta funkcja zostanie wywołana po kliknięciu przycisku indeksu.

def index():
 if connected is False:
    connect()
 print("INDEX Clicked")
 dir_to_index = entry1.get()

 if os.path.isdir(dir_to_index) is False:  
   all_files = get_files_in_dir('.')
 else: 
  all_files = get_files_in_dir(dir_to_index)
   
 text.insert(tk.END,"TEST" ) # "\n " + "TOTAL FILES:", len( all_files )
 try:
   resp = helpers.bulk(
       client,
       yield_docs( all_files,text,entry3.get() , progress_label )
   )
   text.insert (tk.END,"\nhelpers.bulk() RESPONSE:"+ str(resp))
   text.insert (tk.END,"RESPONSE TYPE:"+ str(type(resp)))
 except Exception as err:
   print("\nhelpers.bulk() ERROR:", str(err))
 text.see(tk.END)
 return

def yield_docs(all_files, textB: tk.Text , index, label: tk.Label):
    if  not index or index is None or len(index) == 0:
        textB.insert(tk.END ,"\nNo Index Provided")
        return
    count = 0
    for _id, _file in enumerate(all_files):
        count+=1
        label.configure(text="File:"+str(count) + "/" + str(len(all_files)))

        textB.insert(tk.END ,"\nIndexing : " + _file)
        textB.see(tk.END)
        file_name = _file[ _file.rfind(slash)+1:]
        
        try:    
              if file_name.lower().endswith(('.html' , '.txt' , '.php' ,'.htm')) is True :
                 data = get_data_from_text_file( _file )
                 data = "".join( data )

                 doc_source = {
                    "file_name": file_name,
                    "data": data ,
                    "file_path":_file
                }
              elif file_name.lower().endswith((".docx", ".doc")) is True :
                   pages = getText(_file)
                   for page in pages:
                        doc_source = {
                            "file_name": file_name,
                            "data": page,
                            "file_path":_file
                        }
                        yield {
                        "_index": index,
                        "_source": doc_source
                        }
              elif file_name.lower().endswith((".pdf")) is True :
                    print("Ends with pdf")
                    pages = get_text(_file)
                    for page in pages:
                        doc_source = {
                            "file_name": file_name,
                            "data": page,
                            "file_path":_file
                        }
                        yield {
                        "_index": index,
                        "_source": doc_source
                        }   
              else:
                    doc_source = {
                    "file_name": file_name,
                    "data": _file,
                    "file_path":_file
                }
        
                    yield {
                    "_index": index,
                    "_source": doc_source
                }  
             
        except Exception as err:
          print('\nError ',err)
          doc_source = {
                    "file_name": file_name,
                    "data": _file,
                    "file_path":_file
                }
          yield {
                    "_index": index,
                    "_source": doc_source
                }

Uwagi końcowe

Indeksator folderów ElasticSearch ma wiele możliwych zastosowań nie tylko dla programistów, ale także innych osób. Można to rozszerzyć, aby stać się w pełni funkcjonalną platformą do indeksowania i wyszukiwania folderów. W tej chwili część wyszukiwania można wykonać za pomocą zewnętrznej aplikacji lub za pośrednictwem listonosza. Może być używany do wyszukiwania określonego pliku w folderze, określonych tekstów w dokumentach lub określonych części kodu w plikach kodowania.

Dystrybucję tej aplikacji, którą można uruchomić na komputerach z systemem Windows bez preinstalowanego Pythona, można znaleźć w następującym łączu GitHub, w folderze dist. Aby uruchomić indeksator folderów, wystarczy uruchomić ui.exe w folderze ui.

Github:https://github.com/joanjanku2000/elk-folder-indexer/tree/latest

Dziękujemy za zainteresowanie, jeśli dotarłeś aż tutaj. Dzięki.

© Joan Janku 2022