Проведи глубокий анализ кода: import tkinter as tk
from tkinter import messagebox, filedialog, scrolledtext, ttk
import requests
from bs4 import BeautifulSoup
import feedparser
from tkhtmlview import HTMLLabel
import threading
import re
import logging
from urllib.parse import urlparse

# Настройка логирования
logging.basicConfig(level=logging.INFO, filename='news_parser.log',
                    format='%(asctime)s - %(levelname)s - %(message)s')


class NewsParser:
    def __init__(self, master):
        self.master = master
        master.title("Парсер новостных лент")
        master.geometry("1000x800")
        master.resizable(True, True)

        # Настройка стилей
        style = ttk.Style()
        style.configure("TLabel", font=('Arial', 10))
        style.configure("TButton", font=('Arial', 10))
        style.configure("TCheckbutton", font=('Arial', 10))

        # Основной фрейм
        self.main_frame = ttk.Frame(master, padding="10")
        self.main_frame.pack(fill=tk.BOTH, expand=True)

        # Ввод URL
        ttk.Label(self.main_frame, text="Введите URL новостной ленты:").grid(row=0, column=0, sticky=tk.W, pady=5)
        self.url_entry = ttk.Entry(self.main_frame, width=80)
        self.url_entry.grid(row=0, column=1, sticky=tk.W, pady=5)

        # Ввод тега (для HTML)
        ttk.Label(self.main_frame, text="Введите тег для парсинга (например, 'h2'):").grid(row=1, column=0, sticky=tk.W, pady=5)
        self.tag_entry = ttk.Entry(self.main_frame, width=80)
        self.tag_entry.grid(row=1, column=1, sticky=tk.W, pady=5)

        # Фильтрация по ключевым словам
        ttk.Label(self.main_frame, text="Введите ключевые слова для фильтрации (через запятую):").grid(row=2, column=0, sticky=tk.W, pady=5)
        self.filter_entry = ttk.Entry(self.main_frame, width=80)
        self.filter_entry.grid(row=2, column=1, sticky=tk.W, pady=5)

        # Опции фильтрации
        self.case_sensitive = tk.BooleanVar()
        self.exact_match = tk.BooleanVar()

        ttk.Checkbutton(self.main_frame, text="Чувствительность к регистру", variable=self.case_sensitive).grid(row=3, column=1, sticky=tk.W, pady=2)
        ttk.Checkbutton(self.main_frame, text="Точный поиск фраз", variable=self.exact_match).grid(row=4, column=1, sticky=tk.W, pady=2)

        # Кнопки управления
        self.button_frame = ttk.Frame(self.main_frame)
        self.button_frame.grid(row=5, column=0, columnspan=2, pady=10)

        self.parse_button = ttk.Button(self.button_frame, text="Парсить", command=self.start_parse)
        self.parse_button.grid(row=0, column=0, padx=5)

        self.save_html_button = ttk.Button(self.button_frame, text="Сохранить как HTML", command=lambda: self.save_to_file(format='html'))
        self.save_html_button.grid(row=0, column=1, padx=5)

        self.save_txt_button = ttk.Button(self.button_frame, text="Сохранить как TXT", command=lambda: self.save_to_file(format='txt'))
        self.save_txt_button.grid(row=0, column=2, padx=5)

        self.copy_button = ttk.Button(self.button_frame, text="Копировать текст", command=self.copy_text)
        self.copy_button.grid(row=0, column=3, padx=5)

        # Индикатор прогресса
        self.progress = ttk.Progressbar(self.main_frame, mode='indeterminate')
        self.progress.grid(row=6, column=0, columnspan=2, sticky=tk.W+tk.E, pady=5)

        # Вывод HTML результатов
        ttk.Label(self.main_frame, text="HTML результаты:").grid(row=7, column=0, sticky=tk.W, pady=5)
        self.result_label = HTMLLabel(self.main_frame, width=120, height=20, html="")
        self.result_label.grid(row=8, column=0, columnspan=2, pady=5)

        # Вывод готового текста
        ttk.Label(self.main_frame, text="Готовый текст:").grid(row=9, column=0, sticky=tk.W, pady=5)
        self.output_text = scrolledtext.ScrolledText(self.main_frame, width=120, height=20, wrap=tk.WORD)
        self.output_text.grid(row=10, column=0, columnspan=2, pady=5)

        # Лимит отображаемых результатов
        ttk.Label(self.main_frame, text="Лимит результатов (0 - без ограничений):").grid(row=11, column=0, sticky=tk.W, pady=5)
        self.limit_var = tk.IntVar(value=0)
        self.limit_entry = ttk.Entry(self.main_frame, width=10, textvariable=self.limit_var)
        self.limit_entry.grid(row=11, column=1, sticky=tk.W, pady=5)

    def start_parse(self):
        # Запуск парсинга в отдельном потоке
        parse_thread = threading.Thread(target=self.parse)
        parse_thread.start()

    def parse(self):
        # Запуск индикатора прогресса
        self.progress.start()
        self.parse_button.config(state=tk.DISABLED)

        url = self.url_entry.get().strip()
        tag = self.tag_entry.get().strip()
        filter_keywords_input = self.filter_entry.get().strip()
        limit = self.limit_var.get()

        if limit < 0:
            limit = 0  # 0 - без ограничений

        # Валидация URL
        if not self.validate_url(url):
            messagebox.showerror("Ошибка", "Введен некорректный URL.")
            self.progress.stop()
            self.parse_button.config(state=tk.NORMAL)
            return

        filter_keywords = [kw.strip() for kw in filter_keywords_input.split(",") if kw.strip()]
        exact = self.exact_match.get()
        case = self.case_sensitive.get()

        self.result_label.set_html("")  # Очистка поля вывода
        self.output_text.delete(1.0, tk.END)  # Очистка текстового поля

        # Проверка, является ли URL RSS/XML лентой или обычной HTML-страницей
        is_rss = url.endswith(('.rss', '.xml'))

        if not tag and not is_rss:
            messagebox.showwarning("Предупреждение", "Пожалуйста, заполните хотя бы поле URL или тег.")
            self.progress.stop()
            self.parse_button.config(state=tk.NORMAL)
            return

        try:
            if is_rss:
                self.parse_rss(url, filter_keywords, exact, case, limit)
            else:
                self.parse_html(url, tag, filter_keywords, exact, case, limit)
        except requests.exceptions.RequestException as e:
            logging.error(f"Ошибка при запросе: {e}")
            messagebox.showerror("Ошибка", f"Ошибка при запросе: {e}")
        except Exception as e:
            logging.error(f"Неизвестная ошибка: {e}")
            messagebox.showerror("Ошибка", f"Неизвестная ошибка: {e}")

        # Остановка индикатора прогресса
        self.progress.stop()
        self.parse_button.config(state=tk.NORMAL)

    def validate_url(self, url):
        try:
            result = urlparse(url)
            return all([result.scheme, result.netloc])
        except:
            return False

    def parse_html(self, url, tag, filter_keywords, exact, case, limit):
        response = requests.get(url)
        response.raise_for_status()  # Проверка на ошибки
        soup = BeautifulSoup(response.text, 'html.parser')

        results = []
        count = 0
        for item in soup.find_all(tag):
            # Получаем полный HTML-текст элемента
            html_content = str(item)
            # Извлекаем текст без тегов для фильтрации
            text = item.get_text(separator=' ', strip=True)

            if self.filter_text(text, filter_keywords, exact, case):
                formatted_html = self.format_html_content(title=text, content=html_content)
                results.append(formatted_html)
                self.output_text.insert(tk.END, text + '\n\n')
                count += 1
                if limit != 0 and count >= limit:
                    break

        self.display_results(results)

    def parse_rss(self, url, filter_keywords, exact, case, limit):
        feed = feedparser.parse(url)
        results = []
        count = 0

        for entry in feed.entries:
            title = entry.get('title', 'Нет заголовка')
            description = entry.get('description', 'Нет описания')
            link = entry.get('link', '#')
            published = entry.get('published', 'Нет даты публикации')

            # Используем BeautifulSoup для обработки HTML в описании
            soup = BeautifulSoup(description, 'html.parser')
            # Опционально: можно очистить нежелательные теги
            # Например, удалить скрипты:
            for script in soup(["script", "style"]):
                script.extract()

            cleaned_description = str(soup)

            combined_text = f"{title} {soup.get_text(separator=' ', strip=True)}"
            if self.filter_text(combined_text, filter_keywords, exact, case):
                # Формирование HTML-контента
                entry_html = self.format_html_content(
                    title=title,
                    description=cleaned_description,
                    link=link,
                    published=published
                )
                results.append(entry_html)
                # Отображение в текстовом поле без HTML
                self.output_text.insert(tk.END, f"{title}\n{cleaned_description}\n{link}\nОпубликовано: {published}\n\n")
                count += 1
                if limit != 0 and count >= limit:
                    break

        self.display_results(results)

    def format_html_content(self, title="", description="", link="#", published="", content=""):
        """
        Форматирует контент для отображения в HTMLLabel.
        Приоритет: если content предоставлен, используется вместо описания.
        """
        if content:
            # Используем предоставленный контент (например, для HTML парсинга)
            formatted = f"""
                <div style="margin-bottom: 20px;">
                    <h2>{title}</h2>
                    <div>{content}</div>
                </div>
                <hr>
            """
        else:
            # Формируем контент для RSS
            formatted = f"""
                <div style="margin-bottom: 20px;">
                    <h2>{title}</h2>
                    <div>{description}</div>
                    <p><a href="{link}" target="_blank">Читать далее</a></p>
                    <p><em>Опубликовано: {published}</em></p>
                </div>
                <hr>
            """
        return formatted

    def filter_text(self, text, keywords, exact, case):
        if not keywords:
            return True

        flags = 0 if case else re.IGNORECASE
        for keyword in keywords:
            if exact:
                # Поиск точной фразы
                pattern = re.escape(keyword)
                if re.search(rf'\b{pattern}\b', text, flags):
                    return True
            else:
                # Поиск подстроки
                if re.search(re.escape(keyword), text, flags):
                    return True
        return False

    def display_results(self, results):
        if results:
            # Соединить все результаты в одну строку HTML
            html_content = "<br>".join(results)
            # Добавляем базовую стилизацию для лучшего отображения
            full_html = f"""
                <html>
                <head>
                    <style>
                        body {{
                            font-family: Arial, sans-serif;
                            line-height: 1.6;
                        }}
                        h2 {{
                            color: #2E86C1;
                        }}
                        a {{
                            color: #117A65;
                            text-decoration: none;
                        }}
                        a:hover {{
                            text-decoration: underline;
                        }}
                        hr {{
                            border: 0;
                            height: 1px;
                            background: #ccc;
                            margin: 20px 0;
                        }}
                    </style>
                </head>
                <body>
                    {html_content}
                </body>
                </html>
            """
            self.result_label.set_html(full_html)
        else:
            self.result_label.set_html("<p>Нет новостей, соответствующих критериям.</p>")

    def copy_text(self):
        text = self.output_text.get(1.0, tk.END).strip()
        if text:
            self.master.clipboard_clear()
            self.master.clipboard_append(text)
            messagebox.showinfo("Копирование", "Текст скопирован в буфер обмена.")
        else:
            messagebox.showwarning("Предупреждение", "Нет текста для копирования.")

    def save_to_file(self, format='html'):
        if format == 'html':
            content = self.result_label.get_html()
            default_ext = ".html"
            filetypes = [("HTML files", "*.html"), ("All files", "*.*")]
        elif format == 'txt':
            content = self.output_text.get(1.0, tk.END)
            default_ext = ".txt"
            filetypes = [("Text files", "*.txt"), ("All files", "*.*")]
        else:
            messagebox.showerror("Ошибка", "Неподдерживаемый формат сохранения.")
            return

        if not content.strip():
            messagebox.showwarning("Предупреждение", "Нет данных для сохранения.")
            return

        file_path = filedialog.asksaveasfilename(defaultextension=default_ext,
                                                 filetypes=filetypes)
        if file_path:
            try:
                if format == 'html':
                    # Добавляем базовую структуру HTML, если её нет
                    if not re.search(r'<html.*?>', content, re.IGNORECASE):
                        content = f"""
                            <html>
                            <head>
                                <meta charset="UTF-8">
                                <title>News Parser Results</title>
                            </head>
                            <body>
                                {content}
                            </body>
                            </html>
                        """
                with open(file_path, 'w', encoding='utf-8') as file:
                    file.write(content)
                messagebox.showinfo("Сохранение", "Данные успешно сохранены.")
            except Exception as e:
                logging.error(f"Ошибка при сохранении файла: {e}")
                messagebox.showerror("Ошибка", f"Не удалось сохранить файл: {e}")


if __name__ == "__main__":
    root = tk.Tk()
    app = NewsParser(root)
    root.mainloop()