| import streamlit as st
|
| import requests
|
| import pandas as pd
|
| import os
|
| from datetime import datetime
|
| import json
|
| import time
|
| import threading
|
| from database_manager import DatabaseManager
|
| from contact_scraper import ContactScraper
|
|
|
|
|
| try:
|
| from huggingface_hub import HfApi
|
| HF_AVAILABLE = True
|
| except ImportError:
|
| HF_AVAILABLE = False
|
|
|
|
|
| def load_hf_token():
|
| """Wczytaj token Hugging Face z pliku"""
|
| try:
|
| if os.path.exists('hf_token.txt'):
|
| with open('hf_token.txt', 'r', encoding='utf-8') as f:
|
| token = f.read().strip()
|
| if token and token.startswith('hf_'):
|
| return token
|
| return None
|
| except Exception as e:
|
| print(f"Błąd wczytywania tokenu: {e}")
|
| return None
|
|
|
| HF_TOKEN = None
|
|
|
|
|
| from dotenv import load_dotenv
|
| load_dotenv()
|
|
|
|
|
| POLAND_REGIONS = {
|
| "Mazowieckie": {
|
| "major_cities": ["Warszawa"],
|
| "large_cities": ["Radom", "Płock", "Siedlce", "Ostrołęka"],
|
| "medium_cities": ["Pruszków", "Legionowo", "Otwock", "Piaseczno", "Wołomin"],
|
| "small_cities": ["Grodzisk Mazowiecki", "Marki", "Nowy Dwór Mazowiecki", "Wyszków", "Kobyłka"]
|
| },
|
| "Pomorskie": {
|
| "major_cities": ["Gdańsk", "Gdynia"],
|
| "large_cities": ["Słupsk", "Tczew", "Wejherowo", "Rumia"],
|
| "medium_cities": ["Starogard Gdański", "Chojnice", "Kwidzyn", "Malbork", "Lębork"],
|
| "small_cities": ["Reda", "Puck", "Kartuzy", "Bytów", "Człuchów"]
|
| },
|
| "Śląskie": {
|
| "major_cities": ["Katowice", "Częstochowa"],
|
| "large_cities": ["Sosnowiec", "Gliwice", "Zabrze", "Bytom", "Ruda Śląska", "Rybnik"],
|
| "medium_cities": ["Tychy", "Dąbrowa Górnicza", "Chorzów", "Jaworzno", "Jastrzębie-Zdrój"],
|
| "small_cities": ["Mikołów", "Piekary Śląskie", "Racibórz", "Wodzisław Śląski", "Będzin"]
|
| },
|
| "Wielkopolskie": {
|
| "major_cities": ["Poznań"],
|
| "large_cities": ["Kalisz", "Konin", "Piła", "Ostrów Wielkopolski"],
|
| "medium_cities": ["Gniezno", "Leszno", "Śrem", "Września", "Krotoszyn"],
|
| "small_cities": ["Rawicz", "Jarocin", "Wągrowiec", "Środa Wielkopolska", "Gostyń"]
|
| },
|
| "Małopolskie": {
|
| "major_cities": ["Kraków"],
|
| "large_cities": ["Tarnów", "Nowy Sącz", "Oświęcim", "Chrzanów"],
|
| "medium_cities": ["Wieliczka", "Bochnia", "Zakopane", "Skawina", "Brzesko"],
|
| "small_cities": ["Andrychów", "Kęty", "Trzebinia", "Olkusz", "Myślenice"]
|
| },
|
| "Dolnośląskie": {
|
| "major_cities": ["Wrocław"],
|
| "large_cities": ["Wałbrzych", "Legnica", "Jelenia Góra", "Lubin"],
|
| "medium_cities": ["Głogów", "Świdnica", "Bolesławiec", "Oława", "Dzierżoniów"],
|
| "small_cities": ["Zgorzelec", "Oleśnica", "Trzebnica", "Bielawa", "Kłodzko"]
|
| },
|
| "Łódzkie": {
|
| "major_cities": ["Łódź"],
|
| "large_cities": ["Piotrków Trybunalski", "Pabianice", "Tomaszów Mazowiecki", "Bełchatów"],
|
| "medium_cities": ["Zgierz", "Skierniewice", "Kutno", "Sieradz", "Łowicz"],
|
| "small_cities": ["Radomsko", "Wieluń", "Opoczno", "Rawa Mazowiecka", "Brzeziny"]
|
| },
|
| "Lubelskie": {
|
| "major_cities": ["Lublin"],
|
| "large_cities": ["Chełm", "Zamość", "Puławy", "Świdnik"],
|
| "medium_cities": ["Kraśnik", "Lubartów", "Łuków", "Biłgoraj", "Dęblin"],
|
| "small_cities": ["Tomaszów Lubelski", "Hrubieszów", "Janów Lubelski", "Krasnystaw", "Łęczna"]
|
| },
|
| "Podlaskie": {
|
| "major_cities": ["Białystok"],
|
| "large_cities": ["Suwałki", "Łomża"],
|
| "medium_cities": ["Augustów", "Hajnówka", "Sokółka", "Grajewo", "Kolno"],
|
| "small_cities": ["Zambrów", "Wysokie Mazowieckie", "Bielsk Podlaski", "Sejny", "Ciechanowiec"]
|
| },
|
| "Podkarpackie": {
|
| "major_cities": ["Rzeszów"],
|
| "large_cities": ["Przemyśl", "Stalowa Wola", "Mielec", "Tarnobrzeg"],
|
| "medium_cities": ["Jarosław", "Krosno", "Dębica", "Nisko", "Sanok"],
|
| "small_cities": ["Przeworsk", "Ropczyce", "Łańcut", "Leżajsk", "Kolbuszowa"]
|
| },
|
| "Kujawsko-Pomorskie": {
|
| "major_cities": ["Bydgoszcz", "Toruń"],
|
| "large_cities": ["Włocławek", "Grudziądz", "Inowrocław"],
|
| "medium_cities": ["Brodnica", "Świecie", "Chełmno", "Nakło nad Notecią", "Rypin"],
|
| "small_cities": ["Golub-Dobrzyń", "Lipno", "Radziejów", "Żnin", "Mogilno"]
|
| },
|
| "Warmińsko-Mazurskie": {
|
| "major_cities": ["Olsztyn"],
|
| "large_cities": ["Elbląg", "Ełk", "Ostróda", "Iława"],
|
| "medium_cities": ["Giżycko", "Bartoszyce", "Kętrzyn", "Szczytno", "Mrągowo"],
|
| "small_cities": ["Pisz", "Braniewo", "Lidzbark Warmiński", "Działdowo", "Nowe Miasto Lubawskie"]
|
| },
|
| "Zachodniopomorskie": {
|
| "major_cities": ["Szczecin"],
|
| "large_cities": ["Koszalin", "Stargard", "Kołobrzeg", "Świnoujście"],
|
| "medium_cities": ["Szczecinek", "Police", "Wałcz", "Białogard", "Goleniów"],
|
| "small_cities": ["Choszczno", "Drawsko Pomorskie", "Myślibórz", "Chojna", "Pyrzyce"]
|
| },
|
| "Lubuskie": {
|
| "major_cities": ["Zielona Góra", "Gorzów Wielkopolski"],
|
| "large_cities": ["Żary", "Żagań", "Sulechów"],
|
| "medium_cities": ["Nowa Sól", "Kostrzyn nad Odrą", "Świebodzin", "Międzyrzecz", "Słubice"],
|
| "small_cities": ["Wschowa", "Lubsko", "Krosno Odrzańskie", "Gubin", "Cybinka"]
|
| },
|
| "Opolskie": {
|
| "major_cities": ["Opole"],
|
| "large_cities": ["Kędzierzyn-Koźle", "Nysa", "Brzeg", "Kluczbork"],
|
| "medium_cities": ["Strzelce Opolskie", "Krapkowice", "Głubczyce", "Głuchołazy", "Paczków"],
|
| "small_cities": ["Zdzieszowice", "Gogolin", "Leśnica", "Prudnik", "Głuchołazy"]
|
| },
|
| "Świętokrzyskie": {
|
| "major_cities": ["Kielce"],
|
| "large_cities": ["Ostrowiec Świętokrzyski", "Starachowice", "Skarżysko-Kamienna", "Sandomierz"],
|
| "medium_cities": ["Końskie", "Busko-Zdrój", "Jędrzejów", "Włoszczowa", "Pinczów"],
|
| "small_cities": ["Staszów", "Opatów", "Sędziszów", "Wąchock", "Ćmielów"]
|
| }
|
| }
|
|
|
|
|
| QUERY_STRATEGY = {
|
| "major_cities": 3,
|
| "large_cities": 1,
|
| "medium_cities": 1,
|
| "small_cities": 1
|
| }
|
|
|
|
|
| RURAL_BUSINESS_STRATEGY = {
|
| "major_cities": 4,
|
| "large_cities": 2,
|
| "medium_cities": 2,
|
| "small_cities": 2
|
| }
|
|
|
|
|
| RURAL_BUSINESS_TYPES = [
|
| "hurtownia nawozów", "hurtownia chemiczna", "sklep rolniczy",
|
| "maszyny rolnicze", "pasze", "nasiona", "nawozy", "pestycydy",
|
| "sprzęt rolniczy", "traktory", "kombajny", "opryskiwacze",
|
| "agrochemikalia", "środki ochrony roślin", "nawozy sztuczne"
|
| ]
|
|
|
|
|
| COST_PER_QUERY = 0.0005
|
|
|
| st.set_page_config(
|
| page_title="SerpAPI Business Finder",
|
| page_icon="🏢",
|
| layout="wide"
|
| )
|
|
|
|
|
| @st.cache_resource
|
| def get_database_manager():
|
| return DatabaseManager()
|
|
|
| @st.cache_resource
|
| def get_contact_scraper():
|
| return ContactScraper()
|
|
|
|
|
| if 'db_manager' not in st.session_state:
|
| st.session_state.db_manager = get_database_manager()
|
|
|
| if 'contact_scraper' not in st.session_state:
|
| st.session_state.contact_scraper = get_contact_scraper()
|
|
|
| if 'scraping_in_progress' not in st.session_state:
|
| st.session_state.scraping_in_progress = False
|
|
|
| if 'scraping_result' not in st.session_state:
|
| st.session_state.scraping_result = ""
|
|
|
| def is_rural_business(query):
|
| """Check if the business type benefits from rural search strategy"""
|
| query_lower = query.lower()
|
| return any(rural_type in query_lower for rural_type in RURAL_BUSINESS_TYPES)
|
|
|
| def get_query_strategy(query):
|
| """Get appropriate query strategy based on business type"""
|
| if is_rural_business(query):
|
| return RURAL_BUSINESS_STRATEGY
|
| return QUERY_STRATEGY
|
|
|
| def run_contact_scraping(dataset_name, search_type, progress_bar, status_text):
|
| """Run contact scraping in background thread"""
|
| try:
|
|
|
| db_manager = DatabaseManager()
|
| contact_scraper = ContactScraper()
|
|
|
|
|
| companies = db_manager.get_companies_for_contact_collection(dataset_name, search_type)
|
|
|
| if not companies:
|
| st.session_state.scraping_result = "Brak firm do zbierania kontaktów"
|
| return
|
|
|
|
|
| results = contact_scraper.batch_scrape_contacts(companies)
|
|
|
|
|
| updated_count = 0
|
| emails_found = 0
|
| phones_found = 0
|
|
|
| for result in results:
|
| if result['status'] == 'success':
|
| company_id = result['company_id']
|
| company_name = result.get('company_name', 'Unknown')
|
| updates = {}
|
|
|
| print(f"Processing company {company_id}: {company_name}")
|
| print(f"Found emails: {result.get('emails', [])}")
|
| print(f"Found phones: {result.get('phones', [])}")
|
|
|
| if result['emails']:
|
| updates['email'] = '; '.join(result['emails'])
|
| emails_found += len(result['emails'])
|
| print(f"Adding email: {updates['email']}")
|
|
|
| if result['phones']:
|
|
|
| company_data = next((c for c in companies if c['id'] == company_id), None)
|
| existing_phone = company_data.get('phone', '') if company_data else ''
|
| new_phones = '; '.join(result['phones'])
|
| if existing_phone:
|
| updates['phone'] = f"{existing_phone}; {new_phones}"
|
| else:
|
| updates['phone'] = new_phones
|
| phones_found += len(result['phones'])
|
| print(f"Adding phone: {updates['phone']}")
|
|
|
| if updates:
|
| print(f"Updating company {company_id} with: {updates}")
|
| success = db_manager.update_company(company_id, updates)
|
| if success:
|
| print(f"Successfully updated company {company_id}")
|
| db_manager.mark_contact_collected(
|
| company_id,
|
| bool(result['emails']),
|
| bool(result['phones'])
|
| )
|
| updated_count += 1
|
| else:
|
| print(f"Failed to update company {company_id}")
|
| else:
|
| print(f"No updates needed for company {company_id}")
|
|
|
|
|
| result_text = f"Zakończono! Zaktualizowano {updated_count} firm"
|
| if emails_found > 0:
|
| result_text += f", znaleziono {emails_found} emaili"
|
| if phones_found > 0:
|
| result_text += f", znaleziono {phones_found} telefonów"
|
|
|
| st.session_state.scraping_result = result_text
|
|
|
|
|
| cache_key = f"companies_{dataset_name}"
|
| if cache_key in st.session_state:
|
| del st.session_state[cache_key]
|
| cache_key_stats = f"dataset_stats_{dataset_name}"
|
| if cache_key_stats in st.session_state:
|
| del st.session_state[cache_key_stats]
|
|
|
| for key in list(st.session_state.keys()):
|
| if key.startswith(f"companies_needing_contacts_{dataset_name}"):
|
| del st.session_state[key]
|
|
|
|
|
| st.session_state.should_refresh = True
|
|
|
| except Exception as e:
|
| st.session_state.scraping_result = f"Błąd podczas zbierania kontaktów: {str(e)}"
|
| finally:
|
| st.session_state.scraping_in_progress = False
|
|
|
| def upload_to_huggingface():
|
| """Upload plików projektu do Hugging Face Hub"""
|
| if not HF_AVAILABLE:
|
| st.error("❌ Hugging Face Hub nie jest zainstalowany!")
|
| st.info("Zainstaluj: `pip install huggingface_hub`")
|
| return False
|
|
|
| try:
|
|
|
| if HF_TOKEN:
|
| api = HfApi(token=HF_TOKEN)
|
| try:
|
| user_info = api.whoami()
|
| st.info(f"👤 Zalogowany jako: {user_info['name']}")
|
| except Exception as e:
|
| st.error(f"❌ Nieprawidłowy token Hugging Face! Błąd: {e}")
|
| st.info("🔑 Sprawdź token w pliku hf_token.txt")
|
| return False
|
| else:
|
| st.error("❌ Nie znaleziono tokenu Hugging Face!")
|
| st.info("🔑 Utwórz plik hf_token.txt z twoim tokenem")
|
| st.info("📝 Format pliku: jedna linia z tokenem zaczynającym się od 'hf_'")
|
| return False
|
|
|
|
|
| repo_id = st.text_input(
|
| "Nazwa repozytorium (np. username/leadfinder-app):",
|
| value="ZygmuntL/Leadfinder1",
|
| help="Format: nazwa-uzytkownika/nazwa-repozytorium"
|
| )
|
|
|
| if not repo_id or "/" not in repo_id:
|
| st.error("❌ Nieprawidłowa nazwa repozytorium!")
|
| return False
|
|
|
|
|
| files_to_upload = [
|
| "database_manager.py",
|
| "contact_scraper.py",
|
| "requirements.txt"
|
| ]
|
|
|
|
|
| if st.checkbox("📤 Upload głównego pliku (app.py)", value=False):
|
| files_to_upload.append(("simple_serpapi_app.py", "app.py"))
|
|
|
| if st.button("🚀 Upload do Hugging Face"):
|
| with st.spinner("Uploadowanie plików..."):
|
| success_count = 0
|
|
|
| for file_item in files_to_upload:
|
|
|
| if isinstance(file_item, tuple):
|
| file_path, remote_path = file_item
|
| else:
|
| file_path = file_item
|
| remote_path = file_item
|
|
|
| if os.path.exists(file_path):
|
| try:
|
|
|
| try:
|
| api.repo_info(repo_id=repo_id, repo_type="model")
|
| except:
|
| st.info(f"🆕 Tworzenie nowego repozytorium: {repo_id}")
|
| api.create_repo(repo_id=repo_id, repo_type="model", exist_ok=True)
|
|
|
|
|
| api.upload_file(
|
| path_or_fileobj=file_path,
|
| path_in_repo=remote_path,
|
| repo_id=repo_id,
|
| commit_message=f"Update {remote_path} - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
| )
|
| success_count += 1
|
| st.success(f"✅ {file_path} uploaded as {remote_path}!")
|
| except Exception as e:
|
| st.error(f"❌ Błąd uploadowania {file_path}: {e}")
|
| st.info(f"🔍 Szczegóły błędu: {str(e)}")
|
| else:
|
| st.warning(f"⚠️ Plik {file_path} nie istnieje")
|
|
|
| if success_count > 0:
|
| st.success(f"🎉 Uploadowano {success_count} plików!")
|
| st.info(f"🔗 Link: https://huggingface.co/{repo_id}")
|
| else:
|
| st.error("❌ Nie udało się uploadować żadnych plików!")
|
|
|
| return True
|
|
|
| except Exception as e:
|
| st.error(f"❌ Błąd: {e}")
|
| return False
|
|
|
| def calculate_search_cost(search_strategy, selected_regions=None, selected_cities=None, query=""):
|
| """Calculate estimated cost for search strategy"""
|
| total_queries = 0
|
|
|
| if search_strategy == "Inteligentne - Cała Polska":
|
|
|
| for region_data in POLAND_REGIONS.values():
|
| for city_type, cities in region_data.items():
|
| queries_per_city = QUERY_STRATEGY[city_type]
|
| total_queries += len(cities) * queries_per_city
|
|
|
| elif search_strategy == "Inteligentne - Województwo":
|
| if selected_regions:
|
| for region in selected_regions:
|
| if region in POLAND_REGIONS:
|
| region_data = POLAND_REGIONS[region]
|
| for city_type, cities in region_data.items():
|
| queries_per_city = QUERY_STRATEGY[city_type]
|
| total_queries += len(cities) * queries_per_city
|
|
|
| elif search_strategy == "Inteligentne - Wybrane miasta":
|
| if selected_cities:
|
|
|
| for city in selected_cities:
|
| for region_data in POLAND_REGIONS.values():
|
| for city_type, cities in region_data.items():
|
| if city in cities:
|
| total_queries += QUERY_STRATEGY[city_type]
|
| break
|
|
|
| elif search_strategy == "Tradycyjne - Województwa":
|
| total_queries = len(POLAND_REGIONS)
|
|
|
| elif search_strategy == "Tradycyjne - Główne miasta":
|
| total_queries = sum(len(cities) for region_data in POLAND_REGIONS.values()
|
| for cities in region_data.values())
|
|
|
| elif search_strategy == "Pojedyncze miasto":
|
| total_queries = 1
|
|
|
| estimated_cost = total_queries * COST_PER_QUERY
|
| return total_queries, estimated_cost
|
|
|
| def search_businesses_optimized(query, search_strategy, selected_regions, selected_cities, api_key, max_results_per_query=20):
|
| """Intelligent search with adaptive strategy based on city size and business type"""
|
| all_results = []
|
| total_queries = 0
|
| total_cost = 0
|
|
|
|
|
| query_strategy = get_query_strategy(query)
|
| is_rural = is_rural_business(query)
|
|
|
|
|
| search_locations = []
|
|
|
| if search_strategy == "Inteligentne - Cała Polska":
|
|
|
| for region_name, region_data in POLAND_REGIONS.items():
|
| for city_type, cities in region_data.items():
|
| queries_per_city = query_strategy[city_type]
|
| for city in cities:
|
| for i in range(queries_per_city):
|
| if queries_per_city > 1:
|
| if is_rural:
|
|
|
| if i == 0:
|
| search_locations.append(city)
|
| else:
|
|
|
| rural_modifiers = ["okolice", "gmina", "powiat", "rejon"]
|
| modifier = rural_modifiers[(i-1) % len(rural_modifiers)]
|
| search_locations.append(f"{modifier} {city}")
|
| else:
|
|
|
| area_modifiers = ["centrum", "północ", "południe", "wschód", "zachód"]
|
| modifier = area_modifiers[i % len(area_modifiers)]
|
| search_locations.append(f"{city} {modifier}")
|
| else:
|
| search_locations.append(city)
|
|
|
| elif search_strategy == "Inteligentne - Województwo":
|
| for region in selected_regions:
|
| if region in POLAND_REGIONS:
|
| region_data = POLAND_REGIONS[region]
|
| for city_type, cities in region_data.items():
|
| queries_per_city = query_strategy[city_type]
|
| for city in cities:
|
| for i in range(queries_per_city):
|
| if queries_per_city > 1:
|
| if is_rural:
|
| if i == 0:
|
| search_locations.append(city)
|
| else:
|
| rural_modifiers = ["okolice", "gmina", "powiat", "rejon"]
|
| modifier = rural_modifiers[(i-1) % len(rural_modifiers)]
|
| search_locations.append(f"{modifier} {city}")
|
| else:
|
| area_modifiers = ["centrum", "północ", "południe", "wschód", "zachód"]
|
| modifier = area_modifiers[i % len(area_modifiers)]
|
| search_locations.append(f"{city} {modifier}")
|
| else:
|
| search_locations.append(city)
|
|
|
| elif search_strategy == "Inteligentne - Wybrane miasta":
|
| for city in selected_cities:
|
|
|
| for region_data in POLAND_REGIONS.values():
|
| for city_type, cities in region_data.items():
|
| if city in cities:
|
| queries_per_city = query_strategy[city_type]
|
| for i in range(queries_per_city):
|
| if queries_per_city > 1:
|
| if is_rural:
|
| if i == 0:
|
| search_locations.append(city)
|
| else:
|
| rural_modifiers = ["okolice", "gmina", "powiat", "rejon"]
|
| modifier = rural_modifiers[(i-1) % len(rural_modifiers)]
|
| search_locations.append(f"{modifier} {city}")
|
| else:
|
| area_modifiers = ["centrum", "północ", "południe", "wschód", "zachód"]
|
| modifier = area_modifiers[i % len(area_modifiers)]
|
| search_locations.append(f"{city} {modifier}")
|
| else:
|
| search_locations.append(city)
|
| break
|
|
|
| elif search_strategy == "Tradycyjne - Województwa":
|
| search_locations = list(POLAND_REGIONS.keys())
|
|
|
| elif search_strategy == "Tradycyjne - Główne miasta":
|
| for region_data in POLAND_REGIONS.values():
|
| for cities in region_data.values():
|
| search_locations.extend(cities)
|
|
|
| elif search_strategy == "Pojedyncze miasto":
|
| search_locations = [selected_cities[0]] if selected_cities else []
|
|
|
|
|
| progress_bar = st.progress(0)
|
| status_text = st.empty()
|
|
|
| for i, location in enumerate(search_locations):
|
| status_text.text(f"Wyszukiwanie w: {location} ({i+1}/{len(search_locations)})")
|
|
|
| try:
|
| results = search_businesses(query, location, api_key, max_results_per_query)
|
|
|
|
|
| if not results and total_queries > 0:
|
| st.warning("⚠️ **Wykryto limit zapytań - przerywam wyszukiwanie**")
|
| break
|
|
|
| all_results.extend(results)
|
| total_queries += 1
|
| total_cost += COST_PER_QUERY
|
|
|
|
|
| progress_bar.progress((i + 1) / len(search_locations))
|
|
|
|
|
| time.sleep(1.0)
|
|
|
| except Exception as e:
|
| st.error(f"Błąd wyszukiwania w {location}: {str(e)}")
|
|
|
| break
|
|
|
|
|
| unique_results = []
|
| seen = set()
|
| for result in all_results:
|
| key = (result['name'], result['address'])
|
| if key not in seen:
|
| seen.add(key)
|
| unique_results.append(result)
|
|
|
| status_text.text(f"Zakończono! Znaleziono {len(unique_results)} unikalnych firm z {total_queries} zapytań")
|
| progress_bar.empty()
|
| status_text.empty()
|
|
|
| return unique_results, total_queries, total_cost
|
|
|
| def search_businesses(query, location, api_key, num_results=20):
|
| """Search for businesses using SerpAPI"""
|
| results = []
|
|
|
|
|
| params = {
|
| 'q': f"{query} {location}",
|
| 'api_key': api_key,
|
| 'engine': 'google_maps'
|
| }
|
|
|
| try:
|
| response = requests.get('https://serpapi.com/search', params=params)
|
| data = response.json()
|
|
|
|
|
| if response.status_code == 429:
|
| st.error("🚫 **Limit zapytań SerpAPI przekroczony!**")
|
| st.warning("""
|
| **Co to oznacza:**
|
| - Przekroczyłeś miesięczny limit zapytań do SerpAPI
|
| - Sprawdź swój plan w panelu SerpAPI
|
| - Możesz zwiększyć limit lub poczekać do następnego miesiąca
|
|
|
| **Rozwiązania:**
|
| 1. Sprawdź swój plan w https://serpapi.com/dashboard
|
| 2. Rozważ upgrade do wyższego planu
|
| 3. Użyj mniejszej liczby zapytań (wybierz "Pojedyncze miasto")
|
| """)
|
| return results
|
|
|
| elif response.status_code != 200:
|
| st.error(f"❌ **Błąd API: {response.status_code}**")
|
| st.write(f"Response: {data}")
|
| return results
|
|
|
|
|
| if 'error' in data:
|
| st.error(f"🚫 **API Error: {data['error']}**")
|
|
|
|
|
| if "quota" in data['error'].lower() or "limit" in data['error'].lower():
|
| st.warning("""
|
| **Limit zapytań przekroczony!**
|
| - Sprawdź swój plan w https://serpapi.com/dashboard
|
| - Rozważ upgrade lub poczekaj do następnego miesiąca
|
| """)
|
| elif "invalid" in data['error'].lower() and "key" in data['error'].lower():
|
| st.error("**Nieprawidłowy klucz API!** Sprawdź klucz w pliku .env")
|
|
|
| return results
|
|
|
| if 'local_results' in data and data['local_results']:
|
| for business in data['local_results']:
|
| result = {
|
| 'name': business.get('title', ''),
|
| 'address': business.get('address', ''),
|
| 'phone': business.get('phone', ''),
|
| 'website': business.get('website', ''),
|
| 'rating': str(business.get('rating', '')),
|
| 'reviews': str(business.get('reviews', '')),
|
| 'category': business.get('type', ''),
|
| 'hours': business.get('hours', ''),
|
| 'price': business.get('price', ''),
|
| 'description': business.get('snippet', '')
|
| }
|
| results.append(result)
|
| else:
|
| st.warning("No local_results found in API response")
|
|
|
| st.write("Available data:", data)
|
|
|
| except Exception as e:
|
| st.error(f"Error fetching data: {str(e)}")
|
|
|
| return results[:num_results]
|
|
|
| def main():
|
| st.title("🏢 SerpAPI Business Finder - Optymalizacja Kosztów")
|
| st.markdown("Inteligentne wyszukiwanie firm z optymalizacją obszarów i kosztów")
|
|
|
|
|
| st.info("🗄️ **Baza danych SQLite jest aktywna** - wszystkie wyniki są automatycznie zapisywane")
|
|
|
|
|
| with st.expander("⚠️ **Informacje o limitach SerpAPI**"):
|
| st.markdown("""
|
| **🚫 Błąd 429 - "Too Many Requests":**
|
|
|
| **Co to oznacza:**
|
| - Przekroczyłeś miesięczny limit zapytań do SerpAPI
|
| - Twój plan ma ograniczoną liczbę zapytań miesięcznie
|
|
|
| **Jak sprawdzić swój limit:**
|
| 1. Idź na https://serpapi.com/dashboard
|
| 2. Sprawdź sekcję "Usage" lub "Billing"
|
| 3. Zobacz ile zapytań zostało Ci w tym miesiącu
|
|
|
| **Rozwiązania:**
|
| - **Natychmiastowe**: Użyj "Pojedyncze miasto" (1 zapytanie = $0.0005)
|
| - **Krótkoterminowe**: Upgrade do wyższego planu SerpAPI
|
| - **Długoterminowe**: Poczekaj do następnego miesiąca (limity resetują się)
|
|
|
| **💡 Wskazówki:**
|
| - Zawsze testuj z "Pojedyncze miasto" przed większymi wyszukiwaniami
|
| - Monitoruj swoje użycie w dashboard SerpAPI
|
| - Rozważ plan z większym limitem jeśli często używasz aplikacji
|
| """)
|
|
|
|
|
| with st.expander("💡 Inteligentne strategie wyszukiwania"):
|
| st.markdown("""
|
| **🧠 Inteligentne strategie (REKOMENDOWANE):**
|
|
|
| 🎯 **Pojedyncze miasto** - $0.0005 (1 zapytanie)
|
| - Najtańsza opcja, idealna do testów
|
|
|
| 🏙️ **Inteligentne - Wybrane miasta** - Dostosowuje zapytania do wielkości miasta
|
| - Warszawa: 3 zapytania (centrum, północ, południe) = $0.0015
|
| - Gdańsk: 3 zapytania = $0.0015
|
| - Małe miasto (5K): 1 zapytanie = $0.0005
|
| - **Przykład**: 5 miast różnej wielkości = ~$0.005
|
|
|
| 🗺️ **Inteligentne - Województwo** - Optymalizuje całe województwo
|
| - Pomorskie: ~25 zapytań = $0.0125
|
| - Mazowieckie: ~35 zapytań = $0.0175
|
| - **Zaleta**: Znajdzie firmy w małych miastach (5K+ mieszkańców)
|
|
|
| 🇵🇱 **Inteligentne - Cała Polska** - ~400 zapytań = $0.20
|
| - Pokrywa wszystkie miasta 5K+ mieszkańców
|
| - Warszawa: 3 zapytania, małe miasto: 1 zapytanie
|
| - **Rezultat**: ~8000+ firm z całej Polski
|
|
|
| **📊 Tradycyjne strategie (dla porównania):**
|
|
|
| 🗺️ **Tradycyjne - Województwa** - $0.008 (16 zapytań)
|
| - Tylko nazwy województw, może przegapić małe miasta
|
|
|
| 🏢 **Tradycyjne - Główne miasta** - $0.08 (160 zapytań)
|
| - Tylko duże miasta, pomija małe miejscowości
|
|
|
| **💡 Rekomendacje dla różnych biznesów:**
|
|
|
| **🏙️ Biznesy miejskie** (myjnia samochodowa, restauracja, sklep):
|
| - **Test**: "Pojedyncze miasto" (Warszawa) - $0.0005
|
| - **Region**: "Inteligentne - Województwo" (Pomorskie) - $0.0125
|
| - **Cała Polska**: "Inteligentne - Cała Polska" - $0.20
|
|
|
| **🌾 Biznesy wiejskie** (hurtownia nawozów, maszyny rolnicze, pasze):
|
| - **Automatycznie wykrywane** - aplikacja dostosowuje strategię
|
| - **Więcej zapytań** - miasto + okolice + gmina + powiat
|
| - **Znajdzie firmy** w małych miejscowościach i na wsi
|
| - **Przykład**: "hurtownia nawozów" w Pomorskim - ~50 zapytań = $0.025
|
|
|
| **🔍 Jak działa wyszukiwanie:**
|
| - **SerpAPI** wyszukuje w **promieniu** wokół podanej lokalizacji
|
| - **"Pomorskie"** = całe województwo + okolice
|
| - **"Gdańsk"** = miasto + okoliczne gminy
|
| - **"okolice Gdańsk"** = wsie i małe miejscowości wokół Gdańska
|
| """)
|
|
|
|
|
| with st.sidebar:
|
| st.header("⚙️ Konfiguracja")
|
|
|
|
|
| api_key = st.text_input("SerpAPI Key", value=os.getenv('SERPAPI_KEY', ''), type="password")
|
|
|
| if api_key and api_key != 'your_serpapi_key_here':
|
| st.success("✅ SerpAPI Key skonfigurowany")
|
| else:
|
| st.error("❌ SerpAPI Key nie jest skonfigurowany")
|
| st.info("Dodaj SERPAPI_KEY do pliku .env")
|
|
|
| st.markdown("---")
|
|
|
|
|
| query = st.text_input("Wyszukiwana fraza", value="myjnia samochodowa", help="np. myjnia samochodowa, hurtownia chemiczna")
|
|
|
|
|
| st.header("📊 Zarządzanie datasetami")
|
|
|
|
|
| if 'datasets_cache' not in st.session_state:
|
| st.session_state.datasets_cache = st.session_state.db_manager.get_datasets()
|
| datasets = st.session_state.datasets_cache
|
| dataset_names = [d['name'] for d in datasets]
|
|
|
|
|
| dataset_choice = st.selectbox(
|
| "Wybierz dataset",
|
| ["(nowy)"] + dataset_names,
|
| help="Wybierz istniejący dataset lub utwórz nowy"
|
| )
|
|
|
| if dataset_choice == "(nowy)":
|
| dataset_name = st.text_input("Nazwa nowego datasetu", value=f"dataset_{datetime.now().strftime('%Y%m%d_%H%M')}")
|
|
|
| if st.button("Utwórz dataset"):
|
| if dataset_name:
|
|
|
| success = st.session_state.db_manager.create_dataset(dataset_name, "", "")
|
| if success:
|
|
|
| if 'datasets_cache' in st.session_state:
|
| del st.session_state.datasets_cache
|
| st.success(f"Dataset '{dataset_name}' utworzony!")
|
| st.rerun()
|
| else:
|
| st.error("Dataset o tej nazwie już istnieje!")
|
| else:
|
| dataset_name = dataset_choice
|
|
|
| cache_key_stats = f"dataset_stats_{dataset_name}"
|
| if cache_key_stats not in st.session_state:
|
| st.session_state[cache_key_stats] = st.session_state.db_manager.get_dataset_stats(dataset_name)
|
| stats = st.session_state[cache_key_stats]
|
| col1, col2, col3, col4, col5 = st.columns(5)
|
| with col1:
|
| st.metric("Firmy", stats['total_companies'])
|
| with col2:
|
| st.metric("Z emailami", stats['with_email'])
|
| with col3:
|
| st.metric("Z telefonami", stats['with_phone'])
|
| with col4:
|
| st.metric("Ze stronami WWW", stats['with_website'])
|
| with col5:
|
| st.metric("Potrzebują kontaktów", stats.get('need_contact_collection', 0))
|
|
|
| st.markdown("---")
|
|
|
|
|
| st.header("🎯 Strategia wyszukiwania")
|
| search_strategy = st.selectbox(
|
| "Wybierz strategię",
|
| [
|
| "Pojedyncze miasto",
|
| "Inteligentne - Wybrane miasta",
|
| "Inteligentne - Województwo",
|
| "Inteligentne - Cała Polska",
|
| "Tradycyjne - Województwa",
|
| "Tradycyjne - Główne miasta"
|
| ],
|
| help="Inteligentne strategie dostosowują liczbę zapytań do wielkości miasta"
|
| )
|
|
|
| st.markdown("---")
|
|
|
|
|
| st.header("🔍 Parametry wyszukiwania")
|
| max_results_per_query = st.slider("Wyników na zapytanie", 10, 20, 20, help="Maksymalnie 20 na zapytanie (limit SerpAPI)")
|
|
|
|
|
| selected_regions = []
|
| selected_cities = []
|
|
|
| if search_strategy == "Pojedyncze miasto":
|
| all_cities = []
|
| for region_data in POLAND_REGIONS.values():
|
| for cities in region_data.values():
|
| all_cities.extend(cities)
|
| selected_cities = [st.selectbox("Wybierz miasto", all_cities)]
|
|
|
| elif search_strategy == "Inteligentne - Wybrane miasta":
|
| all_cities = []
|
| for region_data in POLAND_REGIONS.values():
|
| for cities in region_data.values():
|
| all_cities.extend(cities)
|
| selected_cities = st.multiselect("Wybierz miasta", all_cities, default=["Warszawa", "Kraków", "Gdańsk"])
|
|
|
| elif search_strategy == "Inteligentne - Województwo":
|
| selected_regions = st.multiselect("Wybierz województwa", list(POLAND_REGIONS.keys()), default=["Pomorskie"])
|
|
|
|
|
| if search_strategy in ["Inteligentne - Cała Polska", "Tradycyjne - Województwa", "Tradycyjne - Główne miasta"]:
|
| queries, cost = calculate_search_cost(search_strategy, query=query)
|
| st.info(f"💰 Szacowany koszt: {queries} zapytań = ${cost:.2f}")
|
| potential_results = queries * max_results_per_query
|
| st.info(f"📊 Potencjalne wyniki: {queries} zapytań × {max_results_per_query} = {potential_results} firm (przed usunięciem duplikatów)")
|
| elif selected_regions or selected_cities:
|
| queries, cost = calculate_search_cost(search_strategy, selected_regions, selected_cities, query)
|
| if queries > 0:
|
| st.info(f"💰 Szacowany koszt: {queries} zapytań = ${cost:.2f}")
|
| potential_results = queries * max_results_per_query
|
| st.info(f"📊 Potencjalne wyniki: {queries} zapytań × {max_results_per_query} = {potential_results} firm (przed usunięciem duplikatów)")
|
|
|
|
|
| if is_rural_business(query):
|
| st.success("🌾 Wykryto biznes wiejski - zastosowano strategię rozszerzoną (miasto + okolice)")
|
| else:
|
| st.info("🏙️ Biznes miejski - standardowa strategia wyszukiwania")
|
|
|
|
|
| if not api_key or api_key == 'your_serpapi_key_here':
|
| st.warning("Proszę skonfigurować klucz SerpAPI w panelu bocznym")
|
| return
|
|
|
|
|
| if search_strategy in ["Inteligentne - Wybrane miasta", "Pojedyncze miasto"] and not selected_cities:
|
| st.warning("Proszę wybrać miasta do wyszukiwania")
|
| return
|
| elif search_strategy == "Inteligentne - Województwo" and not selected_regions:
|
| st.warning("Proszę wybrać województwa do wyszukiwania")
|
| return
|
|
|
| if st.button("🔍 Wyszukaj firmy", type="primary"):
|
| if dataset_choice == "(nowy)":
|
| st.error("Proszę najpierw utworzyć dataset!")
|
| return
|
|
|
|
|
| total_queries_est, estimated_cost = calculate_search_cost(search_strategy, selected_regions, selected_cities, query)
|
| st.info(f"💰 **Szacowany koszt**: ${estimated_cost:.4f} ({total_queries_est} zapytań)")
|
|
|
| if estimated_cost > 0.01:
|
| st.warning("⚠️ **Wysoki koszt wyszukiwania!** Rozważ użycie 'Pojedyncze miasto' do testów.")
|
|
|
| with st.spinner("Wyszukiwanie firm..."):
|
| results, total_queries, total_cost = search_businesses_optimized(
|
| query, search_strategy, selected_regions, selected_cities, api_key, max_results_per_query
|
| )
|
|
|
| if results:
|
|
|
| for result in results:
|
| result['location'] = f"{query} {search_strategy}"
|
|
|
|
|
| added_count = st.session_state.db_manager.add_companies(results, dataset_name, query)
|
|
|
|
|
| cache_key = f"companies_{dataset_name}"
|
| if cache_key in st.session_state:
|
| del st.session_state[cache_key]
|
| cache_key_stats = f"dataset_stats_{dataset_name}"
|
| if cache_key_stats in st.session_state:
|
| del st.session_state[cache_key_stats]
|
|
|
| for key in list(st.session_state.keys()):
|
| if key.startswith(f"companies_needing_contacts_{dataset_name}"):
|
| del st.session_state[key]
|
|
|
|
|
| potential_results = total_queries * max_results_per_query
|
| duplicates_removed = potential_results - len(results)
|
| duplicate_rate = (duplicates_removed / potential_results * 100) if potential_results > 0 else 0
|
|
|
| st.success(f"Znaleziono {len(results)} unikalnych firm z {total_queries} zapytań")
|
| st.info(f"Zapisano {added_count} nowych firm do datasetu '{dataset_name}'")
|
|
|
|
|
| col1, col2, col3, col4 = st.columns(4)
|
| with col1:
|
| st.metric("Znalezione firmy", len(results))
|
| with col2:
|
| st.metric("Zapytania API", total_queries)
|
| with col3:
|
| st.metric("Koszt", f"${total_cost:.2f}")
|
| with col4:
|
| st.metric("Duplikaty usunięte", f"{duplicates_removed} ({duplicate_rate:.1f}%)")
|
|
|
|
|
| df = pd.DataFrame(results)
|
| st.dataframe(df, width='stretch')
|
|
|
|
|
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| strategy_name = search_strategy.replace(" ", "_").replace("-", "_")
|
| csv_filename = f"{query.replace(' ', '_')}_{strategy_name}_{timestamp}.csv"
|
|
|
| csv_data = df.to_csv(index=False)
|
| st.download_button(
|
| label="📥 Pobierz CSV",
|
| data=csv_data,
|
| file_name=csv_filename,
|
| mime="text/csv"
|
| )
|
|
|
|
|
| with st.expander("📋 Szczegóły danych"):
|
| st.json(results)
|
| else:
|
| st.warning("Nie znaleziono żadnych firm")
|
|
|
|
|
| st.markdown("---")
|
| st.header("📊 Zarządzanie danymi")
|
|
|
| if dataset_choice != "(nowy)":
|
|
|
|
|
| if st.button("🔄 Odśwież dane"):
|
|
|
| cache_key = f"companies_{dataset_name}"
|
| if cache_key in st.session_state:
|
| del st.session_state[cache_key]
|
|
|
| for key in list(st.session_state.keys()):
|
| if key.startswith(f"companies_needing_contacts_{dataset_name}"):
|
| del st.session_state[key]
|
| st.rerun()
|
|
|
|
|
| cache_key = f"companies_{dataset_name}"
|
| if cache_key not in st.session_state:
|
| st.session_state[cache_key] = st.session_state.db_manager.get_companies(dataset_name)
|
| companies_df = st.session_state[cache_key]
|
|
|
| if not companies_df.empty:
|
| st.subheader(f"Firmy w datasecie '{dataset_name}'")
|
|
|
|
|
| col1, col2 = st.columns(2)
|
| with col1:
|
| filter_email = st.checkbox("Tylko z emailami", value=False)
|
| with col2:
|
| filter_phone = st.checkbox("Tylko z telefonami", value=False)
|
|
|
|
|
| filtered_df = companies_df.copy()
|
| if filter_email:
|
| filtered_df = filtered_df[filtered_df['email'].notna() & (filtered_df['email'] != '')]
|
| if filter_phone:
|
| filtered_df = filtered_df[filtered_df['phone'].notna() & (filtered_df['phone'] != '')]
|
|
|
| st.dataframe(filtered_df, width='stretch')
|
|
|
|
|
| st.subheader("📧 Zbieranie kontaktów")
|
|
|
|
|
| search_type = st.selectbox(
|
| "Co zbierać:",
|
| ["both", "emails_only", "phones_only"],
|
| format_func=lambda x: {
|
| "both": "📧📞 Emails i telefony (wszystko brakujące)",
|
| "emails_only": "📧 Tylko emaile (jeśli już masz telefony)",
|
| "phones_only": "📞 Tylko telefony (jeśli już masz emaile)"
|
| }[x],
|
| help="Wybierz co chcesz zbierać ze stron WWW"
|
| )
|
|
|
|
|
| cache_key_contacts = f"companies_needing_contacts_{dataset_name}_{search_type}"
|
| if cache_key_contacts not in st.session_state:
|
| st.session_state[cache_key_contacts] = st.session_state.db_manager.get_companies_for_contact_collection(dataset_name, search_type)
|
| companies_needing_contacts = st.session_state[cache_key_contacts]
|
|
|
| if companies_needing_contacts:
|
| search_type_text = {
|
| "both": "kontaktów (emaili lub telefonów)",
|
| "emails_only": "emaili",
|
| "phones_only": "telefonów"
|
| }[search_type]
|
| st.info(f"🔍 Znaleziono {len(companies_needing_contacts)} firm, które potrzebują zbierania {search_type_text}")
|
| else:
|
| st.success("✅ Wszystkie firmy mają już kompletne dane kontaktowe!")
|
|
|
| col1, col2 = st.columns(2)
|
| with col1:
|
| button_text = {
|
| "both": "🔍 Zbierz emaile i telefony",
|
| "emails_only": "📧 Zbierz tylko emaile",
|
| "phones_only": "📞 Zbierz tylko telefony"
|
| }[search_type]
|
|
|
| if st.button(button_text, disabled=st.session_state.scraping_in_progress or not companies_needing_contacts):
|
| if not st.session_state.scraping_in_progress and companies_needing_contacts:
|
|
|
| st.session_state.scraping_in_progress = True
|
| st.session_state.scraping_result = f"Rozpoczynam zbieranie {search_type_text} dla {len(companies_needing_contacts)} firm..."
|
|
|
|
|
| thread = threading.Thread(
|
| target=run_contact_scraping,
|
| args=(dataset_name, search_type, None, None)
|
| )
|
| thread.start()
|
| st.rerun()
|
|
|
|
|
| if st.session_state.scraping_in_progress:
|
| st.info("🔄 Zbieranie kontaktów w toku...")
|
| elif hasattr(st.session_state, 'scraping_result'):
|
| st.success(st.session_state.scraping_result)
|
|
|
|
|
| if hasattr(st.session_state, 'should_refresh') and st.session_state.should_refresh:
|
| del st.session_state.should_refresh
|
| st.rerun()
|
|
|
| with col2:
|
| if st.button("🗑️ Usuń dataset", type="secondary"):
|
| if st.session_state.db_manager.delete_dataset(dataset_name):
|
|
|
| if 'datasets_cache' in st.session_state:
|
| del st.session_state.datasets_cache
|
| cache_key = f"companies_{dataset_name}"
|
| if cache_key in st.session_state:
|
| del st.session_state[cache_key]
|
| cache_key_stats = f"dataset_stats_{dataset_name}"
|
| if cache_key_stats in st.session_state:
|
| del st.session_state[cache_key_stats]
|
|
|
| for key in list(st.session_state.keys()):
|
| if key.startswith(f"companies_needing_contacts_{dataset_name}"):
|
| del st.session_state[key]
|
| st.success(f"Dataset '{dataset_name}' usunięty!")
|
| st.rerun()
|
| else:
|
| st.error("Błąd podczas usuwania datasetu")
|
|
|
|
|
| tab1, tab2, tab3, tab4 = st.tabs(["✏️ Edycja firm", "➕ Dodawanie firm", "☁️ Upload do HF", "🔄 Aktualizacja Space"])
|
|
|
| with tab1:
|
| st.subheader("✏️ Ręczne uzupełnianie danych")
|
|
|
|
|
| company_options = [(f"{row['name']} - {row['address']}", row['id'])
|
| for _, row in companies_df.iterrows()]
|
| selected_company = st.selectbox("Wybierz firmę do edycji", company_options)
|
|
|
| if selected_company:
|
| company_id = selected_company[1]
|
| company_data = companies_df[companies_df['id'] == company_id].iloc[0]
|
|
|
| col1, col2 = st.columns(2)
|
| with col1:
|
| new_email = st.text_input("Email", value=company_data.get('email', ''))
|
| new_phone = st.text_input("Telefon", value=company_data.get('phone', ''))
|
| with col2:
|
| new_website = st.text_input("Strona WWW", value=company_data.get('website', ''))
|
| new_address = st.text_input("Adres", value=company_data.get('address', ''))
|
|
|
| if st.button("💾 Zapisz zmiany"):
|
| updates = {}
|
| if new_email != company_data.get('email', ''):
|
| updates['email'] = new_email
|
| if new_phone != company_data.get('phone', ''):
|
| updates['phone'] = new_phone
|
| if new_website != company_data.get('website', ''):
|
| updates['website'] = new_website
|
| if new_address != company_data.get('address', ''):
|
| updates['address'] = new_address
|
|
|
| if updates:
|
| if st.session_state.db_manager.update_company(company_id, updates):
|
| st.success("Dane zaktualizowane!")
|
| st.rerun()
|
| else:
|
| st.error("Błąd podczas aktualizacji")
|
|
|
| with tab2:
|
| st.subheader("➕ Dodawanie nowej firmy")
|
|
|
| with st.form("add_company"):
|
| col1, col2 = st.columns(2)
|
| with col1:
|
| new_name = st.text_input("Nazwa firmy:", placeholder="np. Myjnia ABC")
|
| new_email = st.text_input("Email:", placeholder="kontakt@firma.pl")
|
| with col2:
|
| new_phone = st.text_input("Telefon:", placeholder="+48 123 456 789")
|
| new_website = st.text_input("Strona WWW:", placeholder="https://www.firma.pl")
|
|
|
| new_address = st.text_input("Adres:", placeholder="ul. Przykładowa 1, 00-000 Warszawa")
|
| new_business_type = st.text_input("Wyszukiwana fraza:", value="myjnia samochodowa")
|
|
|
| if st.form_submit_button("Dodaj firmę"):
|
| if new_name:
|
|
|
| company_data = {
|
| 'name': new_name,
|
| 'email': new_email,
|
| 'phone': new_phone,
|
| 'website': new_website,
|
| 'address': new_address,
|
| 'business_type': new_business_type,
|
| 'dataset_name': dataset_name
|
| }
|
|
|
| success = st.session_state.db_manager.add_companies([company_data])
|
| if success:
|
|
|
| cache_key = f"companies_{dataset_name}"
|
| if cache_key in st.session_state:
|
| del st.session_state[cache_key]
|
| cache_key_stats = f"dataset_stats_{dataset_name}"
|
| if cache_key_stats in st.session_state:
|
| del st.session_state[cache_key_stats]
|
|
|
| for key in list(st.session_state.keys()):
|
| if key.startswith(f"companies_needing_contacts_{dataset_name}"):
|
| del st.session_state[key]
|
| st.success("Firma dodana!")
|
| st.rerun()
|
| else:
|
| st.error("Błąd podczas dodawania firmy!")
|
| else:
|
| st.error("Nazwa firmy jest wymagana!")
|
|
|
| with tab3:
|
| st.subheader("☁️ Upload do Hugging Face Hub")
|
| st.markdown("""
|
| **📤 Automatyczne uploadowanie plików projektu:**
|
|
|
| - ✅ `simple_serpapi_app.py` - główna aplikacja
|
| - ✅ `database_manager.py` - zarządzanie bazą danych
|
| - ✅ `contact_scraper.py` - zbieranie kontaktów
|
| - ✅ `requirements.txt` - zależności
|
|
|
| **🔧 Wymagania:**
|
| 1. Zainstaluj: `pip install huggingface_hub`
|
| 2. Zaloguj się: `huggingface-cli login`
|
| 3. Utwórz repozytorium na https://huggingface.co/new
|
| """)
|
|
|
| upload_to_huggingface()
|
|
|
| with tab4:
|
| st.subheader("🔄 Jak zaktualizować Space na Hugging Face")
|
| st.markdown("""
|
| **📋 Krok po kroku aktualizacja Space:**
|
|
|
| ### 🎯 **Metoda 1: Przez aplikację (najłatwiejsza)**
|
| 1. **W tej aplikacji** → zakładka "☁️ Upload do HF"
|
| 2. **Wpisz nazwę repozytorium** (np. `ZygmuntL/leadfinder`)
|
| 3. **Kliknij "🚀 Upload do Hugging Face"**
|
| 4. **Space automatycznie się zaktualizuje!**
|
|
|
| ### 🎯 **Metoda 2: Ręcznie przez Git**
|
| 1. **Otwórz terminal** w folderze projektu
|
| 2. **Wykonaj komendy:**
|
| ```bash
|
| git add .
|
| git commit -m "Update application"
|
| git push
|
| ```
|
| 3. **Space automatycznie się zaktualizuje!**
|
|
|
| ### 🎯 **Metoda 3: Upload przez stronę**
|
| 1. **Idź na** https://huggingface.co/spaces/ZygmuntL/leadfinder
|
| 2. **Kliknij "Files"** → "Add file" → "Upload files"
|
| 3. **Przeciągnij pliki** (`app.py`, `database_manager.py`, etc.)
|
| 4. **Kliknij "Commit changes"**
|
|
|
| ### ⚡ **Automatyczne aktualizacje:**
|
| - **Każda zmiana** w kodzie → Space się aktualizuje
|
| - **Nowe funkcje** → dostępne od razu
|
| - **Poprawki błędów** → działają natychmiast
|
|
|
| ### 🔧 **Ważne pliki do aktualizacji:**
|
| - ✅ `app.py` (główna aplikacja)
|
| - ✅ `database_manager.py` (baza danych)
|
| - ✅ `contact_scraper.py` (zbieranie kontaktów)
|
| - ✅ `requirements.txt` (zależności)
|
| - ✅ `README.md` (dokumentacja)
|
| """)
|
|
|
| st.info("💡 **Wskazówka:** Używaj Metody 1 - najłatwiejsza i najszybsza!")
|
|
|
|
|
| st.subheader("📝 Edycja tabeli")
|
|
|
|
|
| st.write("**Kliknij na komórkę aby edytować, zaznacz wiersze aby przenieść:**")
|
|
|
|
|
| companies_df_with_checkbox = companies_df.copy()
|
| companies_df_with_checkbox['Zaznacz'] = False
|
|
|
|
|
| edited_df = st.data_editor(
|
| companies_df_with_checkbox[['Zaznacz', 'id', 'name', 'email', 'phone', 'website', 'address']],
|
| num_rows="dynamic",
|
| width='stretch',
|
| key=f"editor_{dataset_name}",
|
| column_config={
|
| "Zaznacz": st.column_config.CheckboxColumn("Zaznacz", help="Zaznacz firmy do przeniesienia"),
|
| "id": st.column_config.NumberColumn("ID", disabled=True),
|
| "name": st.column_config.TextColumn("Nazwa firmy"),
|
| "email": st.column_config.TextColumn("Email"),
|
| "phone": st.column_config.TextColumn("Telefon"),
|
| "website": st.column_config.TextColumn("Strona WWW"),
|
| "address": st.column_config.TextColumn("Adres")
|
| }
|
| )
|
|
|
|
|
| st.write("**Przenoszenie zaznaczonych firm:**")
|
|
|
|
|
| all_datasets = st.session_state.db_manager.get_datasets()
|
| dataset_names = [d['name'] for d in all_datasets if d['name'] != dataset_name]
|
|
|
| if dataset_names:
|
| col1, col2 = st.columns(2)
|
| with col1:
|
| target_dataset = st.selectbox("Przenieś do datasetu:", dataset_names)
|
| with col2:
|
| if st.button("📦 Przenieś zaznaczone firmy"):
|
|
|
| selected_companies = edited_df[edited_df['Zaznacz'] == True]
|
|
|
| if len(selected_companies) > 0:
|
| success_count = 0
|
| duplicate_count = 0
|
|
|
| for _, row in selected_companies.iterrows():
|
| company_id = row['id']
|
| company_data = companies_df[companies_df['id'] == company_id].iloc[0]
|
|
|
|
|
| existing_companies = st.session_state.db_manager.get_companies(target_dataset)
|
| is_duplicate = False
|
|
|
| for _, existing in existing_companies.iterrows():
|
| if (existing['name'] == company_data['name'] and
|
| existing['address'] == company_data['address']):
|
| is_duplicate = True
|
| break
|
|
|
| if is_duplicate:
|
| duplicate_count += 1
|
| st.warning(f"Firma '{company_data['name']}' już istnieje w '{target_dataset}' - pominięto")
|
| else:
|
| success = st.session_state.db_manager.update_company(
|
| company_id,
|
| {'dataset_name': target_dataset}
|
| )
|
| if success:
|
| success_count += 1
|
|
|
| if success_count > 0:
|
|
|
| cache_key = f"companies_{dataset_name}"
|
| if cache_key in st.session_state:
|
| del st.session_state[cache_key]
|
| cache_key_stats = f"dataset_stats_{dataset_name}"
|
| if cache_key_stats in st.session_state:
|
| del st.session_state[cache_key_stats]
|
|
|
| target_cache_key = f"companies_{target_dataset}"
|
| if target_cache_key in st.session_state:
|
| del st.session_state[target_cache_key]
|
| target_cache_key_stats = f"dataset_stats_{target_dataset}"
|
| if target_cache_key_stats in st.session_state:
|
| del st.session_state[target_cache_key_stats]
|
|
|
| for key in list(st.session_state.keys()):
|
| if key.startswith(f"companies_needing_contacts_{dataset_name}") or key.startswith(f"companies_needing_contacts_{target_dataset}"):
|
| del st.session_state[key]
|
| st.success(f"Przeniesiono {success_count} firm do '{target_dataset}'!")
|
| if duplicate_count > 0:
|
| st.info(f"Pominięto {duplicate_count} duplikatów")
|
| st.rerun()
|
| else:
|
| st.error("Błąd podczas przenoszenia lub wszystkie firmy to duplikaty!")
|
| else:
|
| st.warning("Zaznacz firmy checkboxami aby je przenieść")
|
| else:
|
| st.info("Brak innych datasetów do przeniesienia.")
|
|
|
|
|
| if st.button("💾 Zapisz zmiany w tabeli"):
|
| changes_made = False
|
|
|
|
|
| if len(edited_df) < len(companies_df):
|
|
|
| original_ids = set(companies_df['id'].tolist())
|
| current_ids = set(edited_df['id'].tolist())
|
| deleted_ids = original_ids - current_ids
|
|
|
| if deleted_ids:
|
| for company_id in deleted_ids:
|
|
|
| if st.session_state.db_manager.delete_company(company_id):
|
| changes_made = True
|
| st.info(f"Usunięto firmę o ID: {company_id}")
|
|
|
|
|
| for idx, row in edited_df.iterrows():
|
|
|
| original_row = companies_df[companies_df['id'] == row['id']]
|
| if not original_row.empty:
|
| original_row = original_row.iloc[0]
|
|
|
|
|
| updates = {}
|
| if row['name'] != original_row['name']:
|
| updates['name'] = row['name']
|
| if row['email'] != original_row['email']:
|
| updates['email'] = row['email']
|
| if row['phone'] != original_row['phone']:
|
| updates['phone'] = row['phone']
|
| if row['website'] != original_row['website']:
|
| updates['website'] = row['website']
|
| if row['address'] != original_row['address']:
|
| updates['address'] = row['address']
|
|
|
| if updates:
|
| company_id = original_row['id']
|
| if st.session_state.db_manager.update_company(company_id, updates):
|
| changes_made = True
|
|
|
| if changes_made:
|
|
|
| cache_key = f"companies_{dataset_name}"
|
| if cache_key in st.session_state:
|
| del st.session_state[cache_key]
|
| cache_key_stats = f"dataset_stats_{dataset_name}"
|
| if cache_key_stats in st.session_state:
|
| del st.session_state[cache_key_stats]
|
|
|
| for key in list(st.session_state.keys()):
|
| if key.startswith(f"companies_needing_contacts_{dataset_name}"):
|
| del st.session_state[key]
|
| st.success("Zmiany zapisane!")
|
| st.rerun()
|
| else:
|
| st.info("Brak zmian do zapisania")
|
|
|
|
|
| st.subheader("📥 Pobieranie danych")
|
|
|
| if not companies_df.empty:
|
| col1, col2, col3 = st.columns(3)
|
|
|
| with col1:
|
| if st.button("📊 Pobierz wszystkie dane"):
|
| csv_data = companies_df.to_csv(index=False)
|
| st.download_button(
|
| label="💾 Pobierz CSV",
|
| data=csv_data,
|
| file_name=f"{dataset_name}_all_data.csv",
|
| mime="text/csv"
|
| )
|
|
|
| with col2:
|
| if st.button("📧 Pobierz z emailami"):
|
| email_df = companies_df[companies_df['email'].notna() & (companies_df['email'] != '')]
|
| if not email_df.empty:
|
| csv_data = email_df.to_csv(index=False)
|
| st.download_button(
|
| label="💾 Pobierz CSV",
|
| data=csv_data,
|
| file_name=f"{dataset_name}_with_emails.csv",
|
| mime="text/csv"
|
| )
|
| else:
|
| st.warning("Brak firm z emailami")
|
|
|
| with col3:
|
| if st.button("📞 Pobierz z telefonami"):
|
| phone_df = companies_df[companies_df['phone'].notna() & (companies_df['phone'] != '')]
|
| if not phone_df.empty:
|
| csv_data = phone_df.to_csv(index=False)
|
| st.download_button(
|
| label="💾 Pobierz CSV",
|
| data=csv_data,
|
| file_name=f"{dataset_name}_with_phones.csv",
|
| mime="text/csv"
|
| )
|
| else:
|
| st.warning("Brak firm z telefonami")
|
| else:
|
| st.info("Dataset jest pusty. Wykonaj wyszukiwanie, aby dodać firmy.")
|
| else:
|
| st.info("💡 Wybierz dataset z listy powyżej, aby zobaczyć dane i zarządzać nimi")
|
| st.markdown("""
|
| **Dostępne funkcje po wybraniu datasetu:**
|
| - 📊 Przeglądanie firm w datasecie
|
| - 🔍 Automatyczne zbieranie emaili i telefonów ze stron WWW
|
| - ✏️ Ręczne uzupełnianie danych firm
|
| - 📥 Pobieranie danych w różnych formatach CSV
|
| - 🗑️ Usuwanie datasetów
|
| """)
|
|
|
| if __name__ == "__main__":
|
| main() |