import sys import asyncio import streamlit as st from bs4 import BeautifulSoup from openai import OpenAI import json import pandas as pd import re from playwright.sync_api import sync_playwright # ========================================== # 🎨 НАСТРОЙКИ СТРАНИЦЫ И КАСТОМНЫЙ ДИЗАЙН # ========================================== st.set_page_config(page_title="AI Scraper Pro", page_icon="🕷️", layout="wide", initial_sidebar_state="expanded") st.markdown(""" """, unsafe_allow_html=True) import os import subprocess # Принудительная установка браузера ДО запуска основной логики try: import playwright # Проверяем, установлен ли браузер, если нет - ставим subprocess.run(["playwright", "install", "chromium"], check=True) except Exception as e: print(f"Установка браузера: {e}") # ========================================== # 🧠 ЛОГИКА ПАРСИНГА (PLAYWRIGHT + DEEPSEEK) # ========================================== def fetch_and_clean_page(url: str) -> str: import sys import asyncio # 1. ЖЕСТКИЙ ФИКС ДЛЯ WINDOWS (Умеет запускать подпроцессы) if sys.platform == 'win32': asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) # 2. Создаем новый цикл для потока Streamlit loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) with sync_playwright() as p: browser = p.chromium.launch(headless=True) # Маскируемся под обычный Chrome на Windows context = browser.new_context( user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", viewport={'width': 1920, 'height': 1080} ) page = context.new_page() try: # Заходим на сайт и даем время прогрузиться page.goto(url, wait_until="domcontentloaded", timeout=30000) page.wait_for_timeout(3000) html = page.content() except Exception as e: st.error(f"❌ Ошибка загрузки страницы браузером: {e}") return "" finally: browser.close() # Сжимаем HTML soup = BeautifulSoup(html, 'html.parser') for element in soup(["script", "style", "noscript", "svg", "nav", "footer", "header"]): element.decompose() clean_text = soup.get_text(separator=' | ', strip=True) return clean_text[:15000] def extract_data_with_ai(text: str, user_request: str, api_key: str) -> list: """Отправляет сжатый текст в нейросеть для извлечения JSON.""" client = OpenAI(api_key=api_key, base_url="https://api.deepseek.com") system_prompt = """Ты — универсальный экстрактор данных. Твоя задача: 1. Проанализировать запрос пользователя и понять, какие именно данные он хочет вытащить. 2. Создать массив JSON с объектами, где ключи соответствуют смыслу запроса. 3. Если в тексте есть информация, не подходящая под запрос — игнорируй её. 4. Верни СТРОГО чистый JSON массив. Без пояснений и без markdown-разметки (```json).""" user_prompt = f"Запрос: {user_request}\n\nТекст:\n{text}" try: response = client.chat.completions.create( model="deepseek-chat", messages=[{"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}], temperature=0.0, response_format={"type": "json_object"} ) result_text = response.choices[0].message.content.strip() result_text = re.sub(r'^```json\s*', '', result_text) result_text = re.sub(r'\s*```$', '', result_text) data = json.loads(result_text) if isinstance(data, dict): for key, val in data.items(): if isinstance(val, list): return val return [data] return data if isinstance(data, list) else [] except Exception as e: st.error(f"❌ Ошибка ИИ (проверь API-ключ): {e}") return [] # ========================================== # 🖥️ ПОЛЬЗОВАТЕЛЬСКИЙ ИНТЕРФЕЙС (UI) # ========================================== # --- БОКОВАЯ ПАНЕЛЬ --- with st.sidebar: st.image("https://cdn-icons-png.flaticon.com/512/2103/2103837.png", width=80) st.title("⚙️ Настройки") st.markdown("Для работы парсера нужен API-ключ DeepSeek.") api_key_input = st.text_input("DeepSeek API Key", type="password", placeholder="sk-...") st.divider() st.markdown("### 💡 Как использовать:") st.markdown("1. Вставь ссылку на любой магазин.\n2. Напиши, что именно нужно найти.\n3. Скачай готовую таблицу!") st.caption("Создано тобой © 2026") # --- ГЛАВНЫЙ ЭКРАН --- st.title("🕷️ Smart AI Web Scraper") st.markdown("Извлекай структурированные данные с **любого сайта**, обходя блокировки.") with st.container(): col1, col2 = st.columns([1, 1]) with col1: url_input = st.text_input("🌐 Ссылка на сайт", placeholder="https://...") with col2: query_input = st.text_input("🎯 Что ищем?", placeholder="Например: Собери 10 товаров, названия и цены") submit_button = st.button("🚀 ЗАПУСТИТЬ ПАРСИНГ", type="primary", use_container_width=True) if submit_button: if not api_key_input: st.warning("⚠️ Пожалуйста, введи API-ключ DeepSeek в боковой панели слева!") elif not url_input or not query_input: st.warning("⚠️ Заполни поля ссылки и запроса!") else: with st.status("Идет магия парсинга...", expanded=True) as status: st.write("🕵️‍♂️ Открываем невидимый браузер и обходим защиту...") text = fetch_and_clean_page(url_input) if text: st.write("🧠 ИИ анализирует текст и собирает JSON...") data = extract_data_with_ai(text, query_input, api_key_input) if data: status.update(label="✅ Данные успешно собраны!", state="complete", expanded=False) st.success("Ура! ИИ успешно структурировал информацию.") df = pd.DataFrame(data) st.dataframe(df, use_container_width=True) st.divider() col_csv, col_json = st.columns(2) with col_json: json_str = json.dumps(data, ensure_ascii=False, indent=4) st.download_button("📥 Скачать JSON", data=json_str, file_name="parsed.json", mime="application/json", use_container_width=True) with col_csv: csv_str = df.to_csv(index=False).encode('utf-8') st.download_button("📊 Скачать CSV", data=csv_str, file_name="parsed.csv", mime="text/csv", use_container_width=True) else: status.update(label="❌ ИИ не нашел данные", state="error")