Вот код моего приложения:
import tkinter as tk
from tkinter import filedialog, ttk, messagebox, scrolledtext
import pandas as pd
import re
import numpy as np

# Глобальные переменные для хранения состояния
current_df = None
col_vars = {}
clean_column_combobox = None

def extract_price(text):
    """Извлечение цены из текста"""
    if not isinstance(text, str):
        return None
    
    # Ищем цену в формате "14 332 руб."
    pattern = r'(\d{1,3}(?:\s?\d{3})*)\s*руб\.'
    match = re.search(pattern, text)
    if match:
        price_str = match.group(1).replace(' ', '')
        try:
            return int(price_str)
        except:
            return None
    return None

def clean_description(text, article):
    """Очистка описания от лишних данных с учетом артикула"""
    if not isinstance(text, str):
        return "", ""
    
    # Удаляем лишние пробелы и пустые строки
    lines = [line.strip() for line in text.splitlines() if line.strip()]
    
    # Удаляем рекламные блоки и цены
    cleaned_lines = []
    for line in lines:
        if "Рекомендованнаярозничная цена" in line or "руб." in line:
            continue
        cleaned_lines.append(line)
    
    # Если первая строка содержит артикул - удаляем ее
    if cleaned_lines and article and article in cleaned_lines[0]:
        cleaned_lines = cleaned_lines[1:]
    
    # Формируем название и описание
    name = ""
    description = ""
    
    if cleaned_lines:
        # Если в первой строке есть ключевые слова камеры, используем ее как название
        if any(keyword in cleaned_lines[0] for keyword in ["купольная", "цилиндрическая", "IP-камера"]):
            name = cleaned_lines[0]
            # Если в названии нет артикула - добавляем его
            if article and article not in name:
                name += f" {article}"
            description = "\n".join(cleaned_lines[1:])
        else:
            # Иначе используем артикул как основу названия
            name = article if article else cleaned_lines[0]
            description = "\n".join(cleaned_lines)
    else:
        name = article if article else ""
    
    return name, description

def process_excel():
    """Обработка Excel-файла"""
    global current_df
    
    if current_df is None:
        messagebox.showwarning("Предупреждение", "Сначала загрузите файл!")
        return
    
    try:
        # Создаем копию данных для обработки
        df = current_df.copy()
        
        # Получение списка колонок для удаления
        cols_to_drop = [col for col, var in col_vars.items() if var.get()]
        
        # Удаление выбранных колонок
        df.drop(columns=cols_to_drop, inplace=True, errors='ignore')
        
        # Создаем новые столбцы
        df['Артикул'] = ""
        df['Наименование'] = ""
        df['Описание'] = ""
        df['РРЦ'] = np.nan
        
        # Обработка указанной колонки
        clean_col = clean_column_combobox.get()
        if clean_col and clean_col in df.columns:
            for idx, row in df.iterrows():
                # Извлекаем цену
                price = extract_price(row[clean_col])
                if price:
                    df.at[idx, 'РРЦ'] = price
                
                # Получаем артикул
                article = str(row['Text2']) if 'Text2' in df.columns and pd.notna(row['Text2']) else ""
                df.at[idx, 'Артикул'] = article
                
                # Очищаем и разделяем описание
                name, description = clean_description(row[clean_col], article)
                df.at[idx, 'Наименование'] = name
                df.at[idx, 'Описание'] = description
        
        # Формируем финальный DataFrame с нужными столбцами
        final_columns = ['Артикул', 'Наименование', 'Описание', 'РРЦ']
        final_df = df[final_columns].copy()
        
        # Удаляем пустые строки в описании
        final_df['Описание'] = final_df['Описание'].apply(
            lambda x: "\n".join([line.strip() for line in str(x).splitlines() if line.strip()])
        )
        
        # Сохранение результата
        save_path = filedialog.asksaveasfilename(
            defaultextension=".xlsx",
            filetypes=[("Excel files", "*.xlsx")]
        )
        
        if save_path:
            final_df.to_excel(save_path, index=False)
            messagebox.showinfo("Успех", f"Файл успешно сохранен:\n{save_path}")
            
            # Показываем превью результата
            show_preview(final_df)
    
    except Exception as e:
        messagebox.showerror("Ошибка", f"Произошла ошибка: {str(e)}")

def show_preview(df):
    """Показ превью результатов"""
    preview = tk.Toplevel()
    preview.title("Превью результатов")
    preview.geometry("800x600")
    
    text_area = scrolledtext.ScrolledText(preview, wrap=tk.WORD)
    text_area.pack(fill="both", expand=True, padx=10, pady=10)
    
    # Форматируем данные для отображения
    text_area.insert(tk.END, "Артикул\tНаименование\tОписание\tРРЦ\n")
    text_area.insert(tk.END, "-"*100 + "\n")
    
    for _, row in df.head(10).iterrows():
        preview_line = f"{row['Артикул']}\t{row['Наименование']}\t"
        preview_line += row['Описание'].replace('\n', ' | ') + "\t"
        preview_line += f"{row['РРЦ'] if not pd.isna(row['РРЦ']) else ''}\n"
        text_area.insert(tk.END, preview_line)
    
    text_area.config(state=tk.DISABLED)

def load_columns():
    """Загрузка колонок из Excel-файла"""
    global current_df
    
    file_path = filedialog.askopenfilename(
        filetypes=[("Excel files", "*.xlsx"), ("All files", "*.*")]
    )
    if not file_path:
        return
    
    try:
        # Загружаем файл и сохраняем в глобальную переменную
        current_df = pd.read_excel(file_path)
        setup_columns_ui(current_df.columns.tolist())
        messagebox.showinfo("Успех", f"Файл загружен: {file_path}")
    except Exception as e:
        messagebox.showerror("Ошибка", f"Ошибка загрузки файла: {str(e)}")

def setup_columns_ui(columns):
    """Настройка интерфейса для выбора колонок"""
    global col_vars, clean_column_combobox
    
    # Очистка предыдущих элементов
    for widget in cols_frame.winfo_children():
        widget.destroy()
    
    # Создание новых элементов
    col_vars = {}
    tk.Label(cols_frame, text="Выберите колонки для удаления:", anchor="w").pack(fill="x")
    
    for col in columns:
        var = tk.BooleanVar()
        cb = tk.Checkbutton(cols_frame, text=col, variable=var, anchor="w")
        cb.pack(fill="x", padx=5, pady=2)
        col_vars[col] = var
    
    # Combobox для выбора колонки очистки
    tk.Label(cols_frame, text="Колонка для извлечения данных (Описание и РРЦ):", anchor="w").pack(fill="x", pady=(10, 0))
    clean_column_combobox = ttk.Combobox(cols_frame, values=columns)
    clean_column_combobox.pack(fill="x", padx=5, pady=5)
    
    # Подсказка
    tk.Label(cols_frame, 
             text="Обычно это столбец 'Text3'", 
             foreground="gray", anchor="w").pack(fill="x", padx=5)
    
    # Кнопка обработки
    process_btn = ttk.Button(cols_frame, text="Обработать и сохранить файл", command=process_excel)
    process_btn.pack(pady=10)

# Создание основного окна
root = tk.Tk()
root.title("Обработчик Excel-файлов для Milesight")
root.geometry("500x600")

# Основные элементы интерфейса
header = tk.Label(root, text="Обработка файлов Milesight", font=("Arial", 14, "bold"))
header.pack(pady=10)

load_btn = ttk.Button(root, text="Загрузить Excel файл", command=load_columns)
load_btn.pack(pady=10)

cols_frame = ttk.Frame(root)
cols_frame.pack(fill="both", expand=True, padx=20, pady=10)

# Инструкция
instructions = tk.Label(root, 
                        text="1. Загрузите файл Excel\n"
                             "2. Выберите столбцы для удаления\n"
                             "3. Укажите столбец с описанием товара (обычно 'Text3')\n"
                             "4. Нажмите 'Обработать и сохранить'",
                        justify="left")
instructions.pack(side="bottom", fill="x", padx=20, pady=10)

root.mainloop()


Вот исходная строка с данными в эксель:
Text	Text1	Text2	Field	Text3
12 Мп панорамная Fisheye	IP-камера	Milesight MS-C9674-PA	"<table style=""border: 0px none;"" border=""0"" cellpadding=""0"" cellspacing=""0"" width=""100%"">
                <tbody>
                <tr>
                    <td style=""border: 0px none;"">
						<p>
</p>
<p>
 <b>Технические характеристики:</b><br>
 <b> </b><br>
 <b>
	Камера</b><br>
 <br>
	 Сенсор 1/1.7"" Progressive Scan CMOS<br>
	 Минимальная освещенность Цвет: 0.05 лк @F2.8, Ч/Б: 0 лк (ИК вкл.)<br>
	 Динамический диапазон 120 дБ Super WDR<br>
	 Тип объектива: с фиксированным фокусным расстоянием<br>
	 Фокусное расстояние: 1.98 мм @F2.8<br>
	 Крепление объектива M12<br>
	 Углы обзора: горизонтальный монтаж 360° / вертикальный монтаж 180°<br>
	 Скорость затвора: 1/100000 – 1 с<br>
	 Механический ИК-фильтр с автоматическим переключением<br>
	 Дальность ИК-подсветки: до 15 м<br>
 <br>
 <br>
 <b>Видео</b><br>
 <br>
	 Максимальное разрешение: 4000x3000 пикс<br>
	 Первый поток: 60 Гц - 30 кадр/c (3000x3000, 2560x2560, 1920x1920, 1280x1280); 50 Гц - 25 кадр/с (3000x3000, 2560x2560, 1920x1920, 1280x1280)<br>
 <br>
	 Второй поток: 60 Гц - 30 кадр/c (1280x960, 1024x1024, 736x736, 576x576); 50 Гц - 25 кадр/с (1280x960, 1024x1024, 736x736, 576x576)<br>
	 Кодек сжатия видео: H.265+ / H.265(HEVC) / H.264+ / H.264 / MJPEG<br>
	 Битрейт видеосигнала: 16 Кбит/с&lt;16 Мбит/с (CBR / VBR настраиваемый)<br>
	 Настройки изображения: насыщенность / яркость / контраст / резкость<br>
	 Поддерживаемые браузеры: IE / Firefox / Chrome / Safari / Edge<br>
 <br>
 <br>
 <b>Отображение</b><br>
 <br>
	 Способ установки: потолочный монтаж, настенная установка, монтаж на горизонтальную поверхность<br>
	 Метод декодирования (dewarping): встроенный / на клиентской стороне<br>
	 Способы представления изображения: шар (fisheye), панорама 360°, панорама 180°, 4xPTZ, шар + 3xPTZ, панорама + 3xPTZ, шар + панорама + 3xPTZ<br>
 <br>
 <br>
 <b>Сеть</b><br>
 <br>
	 Сетевой интерфейс: 1хRJ45 10M / 100M / 100M Ethernet порт<br>
	 Сетевое хранилище: NAS (поддержка NFS, SMB / CIFS), AMR<br>
	 Сетевые протоколы: IPv4/IPv6, ARP, TCP, UDP, RTCP, RTP, RTSP, RTMP, HTTP, HTTPS, DNS, DDNS, DHCP, FTP, NTP, SMTP, SNMP, UPnP, Bonjour, SIP, PPPoE, VLAN, 802.1x, QoS, IGMP, ICMP, SSL<br>
 <br>
 <br>
 <b>Аудио</b><br>
 <br>
	 Аудиоинтерфейсы: встроенный микрофон / 1 вход / 1 выход<br>
	 Аудиосжатие: G.711 / AAC / G.722 / G.726<br>
 <br>
 <b><br>
 </b>
</p>
<p>
 <b>Система</b><br>
</p>
<p>
</p>
<p>
	 Интерфейсы тревог: 1 вход / 1 выход
</p>
<p>
	 Слот для карты памяти: поддержка micro SD / SDHC / SDXC карт до 256 ГБ
</p>
<p>
	 Дополнительные функции: BLC, HLC, 2D DNR, 3D DNR, AGC, AWB, водяные знаки, анти-туман, устранение мерцания и размытия, коридорный режим, автотрекинг, тепловая карта, фильтрация IP адресов
</p>
<p>
	 Поддержка протоколов SIP / VoIP: да, Voice &amp; Video-over-IP
</p>
<p>
	 События: детекция движения, потеря сетевого подключения, детекция звука, тревожный вход<br>
	 Выполняемые по событию действия: загрузка по FTP, отправка через SMTP, запись на SD-карту, вызов по SIP, HTTP-уведомление, тревожный выход<br>
	 AI Видеоаналитика: вход в область, выход из области, расширенное обнаружение движения, антисаботаж, пересечение линии, праздношатание, детекция оставленных предметов, детекция исчезновения предметов, классификация человека/автомобиль, тепловая карта, подсчет людей<br>
	 Совместимость: ONVIF Profile S &amp; G &amp; T &amp; Q, API
</p>
<p>
</p>
<p>
 <br>
 <b>Основное</b>
</p>
<p>
	 Рабочая температура:- 40℃∼60℃<br>
	 Влажность: 0∼90% (без образования конденсата)<br>
	 Питание: PoE (802.3af) / DC 12В±10%<br>
	 Потребление: до 6 Вт (до 11 Вт с ИК)<br>
	 Степень пылевлагозащищенности: IP67<br>
	 Степень вандалозащищенности: IK10<br>
	 Размеры: 140x45.4 мм<br>
	 Вес: 700 г<br>
	 Гарантия: 3 года (5 лет опционально)<br>
</p>
<p>
 <span ame="""" width=""640"" height=""360"" src=""https://www.youtube.com/embed/EAjqxlHU4mg"" title=""YouTube video player"" frameborder=""0"" allow=""accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"" allowfullscreen=""""> <b><br>
 </b> </span>
</p>
<blockquote>
	<blockquote>
	</blockquote>
</blockquote>
 <br>                    </td>
                </tr>
                </tbody>
            </table>"	"12 Мп панорамная Fisheye
 


 IP-камера Milesight MS-C9674-PA


  

 

 
	 1/1.7"" Progressive Scan CMOS
	 Освещенность: 0.01 лк @F1.2 (цвет)
	 120 дБ Super WDR
	 Объектив:  1.98 мм 
	 Панорамное изображение 360° / 180° 
	 Smart ИК-подсветка до 15 м
	 Встроенный микрофон 
	 Аудиоинтерфейсы 
	 Интерфейсы тревог
	 Встроенная AI видеоаналитика
	 Защита: IP67; IK10
	 Питание: PoE (802.3af) / DC 12В±10%

 
 Гарантия: 3 года (5 лет опционально)
 
    
      
    
        


		            Рекомендованнаярозничная цена
            
					75 808 руб."

так сейчас выгружается:
Milesight MS-C9674-PA	Milesight MS-C9674-PA	"12 Мп панорамная Fisheye
IP-камера Milesight MS-C9674-PA
1/1.7"" Progressive Scan CMOS
Освещенность: 0.01 лк @F1.2 (цвет)
120 дБ Super WDR
Объектив:  1.98 мм
Панорамное изображение 360° / 180°
Smart ИК-подсветка до 15 м
Встроенный микрофон
Аудиоинтерфейсы
Интерфейсы тревог
Встроенная AI видеоаналитика
Защита: IP67; IK10
Питание: PoE (802.3af) / DC 12В±10%
Гарантия: 3 года (5 лет опционально)"	75808

А вот так должно выгружаться:
Milesight MS-C9674-PA	12 Мп панорамная Fisheye IP-камера Milesight MS-C9674-PA	"12 Мп панорамная Fisheye
IP-камера Milesight MS-C9674-PA
1/1.7"" Progressive Scan CMOS
Освещенность: 0.01 лк @F1.2 (цвет)
120 дБ Super WDR
Объектив:  1.98 мм
Панорамное изображение 360° / 180°
Smart ИК-подсветка до 15 м
Встроенный микрофон
Аудиоинтерфейсы
Интерфейсы тревог
Встроенная AI видеоаналитика
Защита: IP67; IK10
Питание: PoE (802.3af) / DC 12В±10%
Гарантия: 3 года (5 лет опционально)"	75808