import logging import os from typing import Iterable, List from dotenv import load_dotenv import gradio as gr import pandas as pd import requests logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) LATEST_DF_BASE = None SAMPLE_RESPONSE: List[dict] = [ { "название": "дрель-шуруповерт аккумуляторная 18 В, Li-Ion, быстрозажимной патрон 13 мм, 0-400/2000 об/мин, 91/58 Нм, 2-х скоростная, 21 уровень крутящего момента, встроенная подсветка, электрический тормоз, реверс, вес 2.3 кг, металлический редуктор, без аккумулятора и зарядного устройства DHP458Z", "вид": "дрель-шуруповерт", "тип": "аккумуляторная", "характеристики": "18 В, Li-Ion, быстрозажимной патрон 13 мм, 0-400/2000 об/мин, 91/58 Нм, 2-х скоростная, 21 уровень крутящего момента, встроенная подсветка, электрический тормоз, реверс, вес 2.3 кг, металлический редуктор, без аккумулятора и зарядного устройства", "производитель": "Makita", "модель": "DHP458Z", "артикул": "DHP458Z", "единицы измерения": "шт", "вес": 2.3, "размер упаковки": "225 x 79 x 259 мм", "English Name": "Makita DHP458Z Cordless Drill Driver", "описание": "Аккумуляторная дрель-шуруповерт Makita DHP458Z – профессиональный инструмент с напряжением 18 В и технологией XPT для защиты от пыли и влаги. Оборудован металлическим редуктором, двухскоростной передачей (0-2000 и 0-400 об/мин), 21 уровнем крутящего момента и встроенной подсветкой. Подходит для сверления в дереве (до 76 мм), металле (до 13 мм) и бетоне (до 16 мм). Имеет реверс, электронную регулировку оборотов, боковую рукоятку, клипсу для ремня и быстрозажимной патрон. Продается без аккумулятора и зарядного устройства. Выпущена в 2019 году как преемник модели BHP458, совместима с аккумуляторами 3 и 4 Ач.", "ГАУ": "Инструменты (основное средство)", } ] load_dotenv() PRIORITY_COLUMNS = [ "название", "производитель", "модель", "артикул", "вид", "тип", "характеристики", ] WEBHOOK_URL = os.getenv("WEBHOOK_URL") WEBHOOK_TIMEOUT = int(os.getenv("WEBHOOK_TIMEOUT_SECONDS", "15")) def _reorder_columns(columns: Iterable[str]) -> List[str]: ordered = [col for col in PRIORITY_COLUMNS if col in columns] remaining = [col for col in columns if col not in ordered] return ordered + remaining def _call_webhook(target_url: str) -> List[dict]: if not WEBHOOK_URL: logger.info("WEBHOOK_URL not configured. Returning sample payload.") return SAMPLE_RESPONSE try: response = requests.post( WEBHOOK_URL, json={"url": target_url}, timeout=WEBHOOK_TIMEOUT ) response.raise_for_status() payload = response.json() if not isinstance(payload, list): raise ValueError("Webhook response must be a list of objects.") return payload except (requests.RequestException, ValueError) as exc: logger.exception("Failed to fetch attributes from webhook.") raise gr.Error("Не удалось получить атрибуты с вебхука.") from exc def _dataframe_to_tsv(df: pd.DataFrame) -> str: return df.to_csv(sep="\t", index=False, header=False) def _format_vertical(df: pd.DataFrame) -> pd.DataFrame: indexed = df.reset_index().rename(columns={"index": "Позиция"}) indexed["Позиция"] = indexed["Позиция"] + 1 column_order = list(df.columns) vertical = indexed.melt( id_vars="Позиция", var_name="Атрибут", value_name="Значение" ) vertical["Атрибут"] = pd.Categorical( vertical["Атрибут"], categories=column_order, ordered=True ) vertical = ( vertical.sort_values(["Позиция", "Атрибут"]) .reset_index(drop=True) .drop(columns="Позиция") ) return vertical def extract_attributes(target_url: str): global LATEST_DF_BASE if not target_url or not target_url.strip(): raise gr.Error("Пожалуйста, введите URL для извлечения атрибутов.") data = _call_webhook(target_url.strip()) if not data: raise gr.Error("Вебхук вернул пустой результат.") df = pd.DataFrame(data) df = df[_reorder_columns(df.columns)] # Save base dataframe (without forced GAU) for future GAU updates LATEST_DF_BASE = df.copy() gau_choices: List[str] = [] selected_gau: str | None = None if "ГАУ" in df.columns: first_value = df["ГАУ"].iloc[0] if isinstance(first_value, list): gau_choices = [str(x).strip() for x in first_value if x] elif pd.notna(first_value): gau_choices = [str(first_value)] if gau_choices: selected_gau = gau_choices[0] df["ГАУ"] = selected_gau tsv_payload = _dataframe_to_tsv(df) vertical_df = _format_vertical(df) dropdown_update = gr.update(choices=gau_choices, value=selected_gau) return vertical_df, tsv_payload, dropdown_update def update_gau(selected_gau: str): global LATEST_DF_BASE if LATEST_DF_BASE is None: raise gr.Error("Сначала получите атрибуты по URL.") df = LATEST_DF_BASE.copy() if "ГАУ" in df.columns and selected_gau: df["ГАУ"] = selected_gau tsv_payload = _dataframe_to_tsv(df) vertical_df = _format_vertical(df) return vertical_df, tsv_payload def main() -> None: with gr.Blocks(title="URL Attribute Extractor") as demo: gr.HTML( """ """ ) gr.Markdown( "### Получите атрибуты товара\n" "Вставьте ссылку на карточку товара, чтобы извлечь структурированные данные." ) with gr.Row(): with gr.Column(scale=2): url_input = gr.Textbox( label="Target URL", placeholder="https://example.com/product/123", lines=1, ) submit_btn = gr.Button("Извлечь атрибуты", variant="primary") gau_dropdown = gr.Dropdown( label="Вероятные ГАУ (выберите наиболее подходящий)", choices=[], interactive=True, ) results_table = gr.Dataframe( label="Результаты атрибутов", interactive=False, wrap=True, type="pandas", elem_id="results-table", ) clipboard_box = gr.Textbox( label=None, interactive=False, lines=1, elem_id="tsv-output", elem_classes=["tsv-hidden"], container=False, ) gr.HTML( """ """ ) with gr.Column(scale=1): gr.Markdown( """ ### 📚 Как пользоваться инструментом Этот сервис помогает автоматически собрать характеристики товара по ссылке и подготовить их для Excel. #### 🛠 Пошаговая инструкция: 1. **Найдите товар**: Откройте страницу товара в интернет-магазине и скопируйте ссылку из адресной строки. 2. **Вставьте ссылку**: Поместите её в поле **Target URL** слева. 3. **Запустите поиск**: Нажмите кнопку **"Извлечь атрибуты"**. 4. **Проверьте данные**: - Результаты появятся в таблице ниже. - Если система найдет несколько вариантов **ГАУ**, выберите нужный в выпадающем списке над таблицей. 5. **Сохраните**: Нажмите кнопку **"📋 Скопировать атрибуты"** под таблицей, затем перейдите в Excel и нажмите `Ctrl+V` (Вставить). > **💡 Совет:** Если возникла ошибка, убедитесь, что ссылка ведет на карточку конкретного товара, а не на общий каталог. """ ) submit_btn.click( fn=extract_attributes, inputs=url_input, outputs=[results_table, clipboard_box, gau_dropdown], ) gau_dropdown.change( fn=update_gau, inputs=gau_dropdown, outputs=[results_table, clipboard_box], ) demo.launch() if __name__ == "__main__": main()