Индексатор папок ElasticSearch

Dec 18 2022
Вы когда-нибудь хотели выполнить поиск внутри папки в Windows, но поиск этого файла занимает вечность? У вас много pdf-файлов или документов docx, и вы не помните, где прочитали предложение или ищете конкретную статью? Вы хотите создать небольшую поисковую систему для своего корпоративного/некорпоративного приложения? Если вы оказались в одной из вышеперечисленных ситуаций или вам просто интересно, как использовать ElasticSearch для индексации содержимого папки и ее подпапок, то эта статья для вас. Установка сцены Разработка для этой статьи происходит в простой среде Windows.

Вы когда-нибудь хотели выполнить поиск внутри папки в Windows, но поиск этого файла занимает вечность? У вас много pdf-файлов или документов docx, и вы не помните, где прочитали предложение или ищете конкретную статью? Вы хотите создать небольшую поисковую систему для своего корпоративного/некорпоративного приложения? Если вы оказались в одной из вышеперечисленных ситуаций или вам просто интересно, как использовать ElasticSearch для индексации содержимого папки и ее подпапок, то эта статья для вас.

Настройка сцены

Разработка для этой статьи происходит в простой среде Windows. На момент написания этой статьи использовалась версия ElasticSearch 8.5.3 . В конце этой статьи мы создадим полностью работающее приложение с графическим интерфейсом, которое с учетом URL-адреса экземпляра ElasticSearch и местоположения папки индексирует содержимое этой папки в этот экземпляр ELK. Имя индекса можно ввести из пользовательского интерфейса, оно может быть существующим или новым.

Elastic Cluster можно настроить как обычно с несколькими экземплярами. Однако, поскольку мы запускаем экземпляр внутри одного компьютера, мы можем настроить экземпляр для запуска в режиме одного узла . Детали конфигурации, относящиеся к этому шагу, подробно описаны в следующих разделах.

Для создания приложения с графическим интерфейсом и индексатора я решил использовать Python из-за его богатых библиотек, быстрой и простой разработки. Менее чем в 250 строк кода написан полнофункциональный индексатор с возможностями индексирования docx, pdf и текста.

Настройка эластичного поиска

После загрузки zip elasticsearch я распаковал его в каталог. Первый шаг — отключение безопасности, так как экземпляр всегда будет запускаться локально. Однако для удаленной среды производственного уровня никогда не рекомендуется оставлять безопасность в стороне. Затем мы указываем, что узел должен работать на локальном хосте и обнаруживать только один узел на локальном хосте. Порты не меняются. (9200 для http, 9300 для TCP)

Конфигурацию yml (elasticsearch.yml) можно найти ниже:

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

Поскольку цель этой статьи не в том, чтобы обсудить, как запустить экземпляр elasticsearch, а в том, как использовать его для индексации папки и ее подпапок с помощью языка программирования высокого уровня, такого как Python, давайте перейдем к «интересной части».

Проектирование пользовательского интерфейса

Индексатор папок elasticsearch сначала был приложением командной строки, но по мере разработки я решил создать для него простой графический интерфейс. Для настройки графического интерфейса я использовал tkinter , но подойдет и любая другая библиотека python gui.

Индексатор папок 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()

При нажатии кнопки Index начинается индексация ElasticSearch. Однако, если мы хотим показать что-то в консоли (текстовый объект в нашей реализации), мы должны иметь возможность отправлять текст в реальном времени для вставки в код. Если и графический интерфейс, и логика индексирования выполняются в одном потоке, то обработка графического интерфейса продолжится только после завершения всего процесса индексирования, и мы застрянем с зависанием пользовательского интерфейса, задаваясь вопросом, что не так в течение всего процесса. Чтобы избежать этого, мы запускаем индексацию в фоновом потоке. Это цель функции start_combine_in_bg , определенной ниже. Целью потока является вызываемая функция, которая должна быть выполнена, то есть индексная функция.

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

Есть 2 основные функции, в которых выполняется логика индексации. Первая — индексная функция, которая также является своего рода оболочкой. Его логика проста и показана ниже. Эта функция будет вызываться при нажатии кнопки индекса.

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
                }

Заключительные замечания

Индексатор папок ElasticSearch имеет множество вариантов использования не только для разработчиков, но и для других людей. Это может быть расширено, чтобы стать полнофункциональной платформой для индексации папок и поиска. На данный момент часть поиска можно выполнить с помощью внешнего приложения или через почтальона. Его можно использовать для поиска определенного файла в папке, определенных текстов внутри документов или определенных частей кода внутри файлов кодирования.

Дистрибутив этого приложения, который можно запустить на ПК с Windows без предустановленного Python, можно найти по следующей ссылке GitHub внутри папки dist. Чтобы индексатор папок заработал, достаточно запустить ui.exe внутри папки ui.

Гитхаб:https://github.com/joanjanku2000/elk-folder-indexer/tree/latest

Спасибо за ваш интерес, если вы добрались до сюда. Ваше здоровье.

© Джоан Янку 2022