ZygmuntL's picture
Add clean app.py without tokens
53a79bf verified
Raw
History Blame Contribute Delete
72.3 kB
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
# Import dla Hugging Face Hub (opcjonalne)
try:
from huggingface_hub import HfApi
HF_AVAILABLE = True
except ImportError:
HF_AVAILABLE = False
# Hugging Face Token - czytaj z pliku
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
# Load environment variables
from dotenv import load_dotenv
load_dotenv()
# Poland regions with detailed city structure for intelligent searching
POLAND_REGIONS = {
"Mazowieckie": {
"major_cities": ["Warszawa"], # 1.8M+ - multiple queries needed
"large_cities": ["Radom", "Płock", "Siedlce", "Ostrołęka"], # 50K+ - single query
"medium_cities": ["Pruszków", "Legionowo", "Otwock", "Piaseczno", "Wołomin"], # 20K+ - single query
"small_cities": ["Grodzisk Mazowiecki", "Marki", "Nowy Dwór Mazowiecki", "Wyszków", "Kobyłka"] # 5K+ - single query
},
"Pomorskie": {
"major_cities": ["Gdańsk", "Gdynia"], # 500K+ - multiple queries
"large_cities": ["Słupsk", "Tczew", "Wejherowo", "Rumia"], # 50K+ - single query
"medium_cities": ["Starogard Gdański", "Chojnice", "Kwidzyn", "Malbork", "Lębork"], # 20K+ - single query
"small_cities": ["Reda", "Puck", "Kartuzy", "Bytów", "Człuchów"] # 5K+ - single query
},
"Śląskie": {
"major_cities": ["Katowice", "Częstochowa"], # 300K+ - multiple queries
"large_cities": ["Sosnowiec", "Gliwice", "Zabrze", "Bytom", "Ruda Śląska", "Rybnik"], # 50K+ - single query
"medium_cities": ["Tychy", "Dąbrowa Górnicza", "Chorzów", "Jaworzno", "Jastrzębie-Zdrój"], # 20K+ - single query
"small_cities": ["Mikołów", "Piekary Śląskie", "Racibórz", "Wodzisław Śląski", "Będzin"] # 5K+ - single query
},
"Wielkopolskie": {
"major_cities": ["Poznań"], # 500K+ - multiple queries
"large_cities": ["Kalisz", "Konin", "Piła", "Ostrów Wielkopolski"], # 50K+ - single query
"medium_cities": ["Gniezno", "Leszno", "Śrem", "Września", "Krotoszyn"], # 20K+ - single query
"small_cities": ["Rawicz", "Jarocin", "Wągrowiec", "Środa Wielkopolska", "Gostyń"] # 5K+ - single query
},
"Małopolskie": {
"major_cities": ["Kraków"], # 800K+ - multiple queries
"large_cities": ["Tarnów", "Nowy Sącz", "Oświęcim", "Chrzanów"], # 50K+ - single query
"medium_cities": ["Wieliczka", "Bochnia", "Zakopane", "Skawina", "Brzesko"], # 20K+ - single query
"small_cities": ["Andrychów", "Kęty", "Trzebinia", "Olkusz", "Myślenice"] # 5K+ - single query
},
"Dolnośląskie": {
"major_cities": ["Wrocław"], # 600K+ - multiple queries
"large_cities": ["Wałbrzych", "Legnica", "Jelenia Góra", "Lubin"], # 50K+ - single query
"medium_cities": ["Głogów", "Świdnica", "Bolesławiec", "Oława", "Dzierżoniów"], # 20K+ - single query
"small_cities": ["Zgorzelec", "Oleśnica", "Trzebnica", "Bielawa", "Kłodzko"] # 5K+ - single query
},
"Łódzkie": {
"major_cities": ["Łódź"], # 700K+ - multiple queries
"large_cities": ["Piotrków Trybunalski", "Pabianice", "Tomaszów Mazowiecki", "Bełchatów"], # 50K+ - single query
"medium_cities": ["Zgierz", "Skierniewice", "Kutno", "Sieradz", "Łowicz"], # 20K+ - single query
"small_cities": ["Radomsko", "Wieluń", "Opoczno", "Rawa Mazowiecka", "Brzeziny"] # 5K+ - single query
},
"Lubelskie": {
"major_cities": ["Lublin"], # 350K+ - multiple queries
"large_cities": ["Chełm", "Zamość", "Puławy", "Świdnik"], # 50K+ - single query
"medium_cities": ["Kraśnik", "Lubartów", "Łuków", "Biłgoraj", "Dęblin"], # 20K+ - single query
"small_cities": ["Tomaszów Lubelski", "Hrubieszów", "Janów Lubelski", "Krasnystaw", "Łęczna"] # 5K+ - single query
},
"Podlaskie": {
"major_cities": ["Białystok"], # 300K+ - multiple queries
"large_cities": ["Suwałki", "Łomża"], # 50K+ - single query
"medium_cities": ["Augustów", "Hajnówka", "Sokółka", "Grajewo", "Kolno"], # 20K+ - single query
"small_cities": ["Zambrów", "Wysokie Mazowieckie", "Bielsk Podlaski", "Sejny", "Ciechanowiec"] # 5K+ - single query
},
"Podkarpackie": {
"major_cities": ["Rzeszów"], # 200K+ - multiple queries
"large_cities": ["Przemyśl", "Stalowa Wola", "Mielec", "Tarnobrzeg"], # 50K+ - single query
"medium_cities": ["Jarosław", "Krosno", "Dębica", "Nisko", "Sanok"], # 20K+ - single query
"small_cities": ["Przeworsk", "Ropczyce", "Łańcut", "Leżajsk", "Kolbuszowa"] # 5K+ - single query
},
"Kujawsko-Pomorskie": {
"major_cities": ["Bydgoszcz", "Toruń"], # 350K+ - multiple queries
"large_cities": ["Włocławek", "Grudziądz", "Inowrocław"], # 50K+ - single query
"medium_cities": ["Brodnica", "Świecie", "Chełmno", "Nakło nad Notecią", "Rypin"], # 20K+ - single query
"small_cities": ["Golub-Dobrzyń", "Lipno", "Radziejów", "Żnin", "Mogilno"] # 5K+ - single query
},
"Warmińsko-Mazurskie": {
"major_cities": ["Olsztyn"], # 200K+ - multiple queries
"large_cities": ["Elbląg", "Ełk", "Ostróda", "Iława"], # 50K+ - single query
"medium_cities": ["Giżycko", "Bartoszyce", "Kętrzyn", "Szczytno", "Mrągowo"], # 20K+ - single query
"small_cities": ["Pisz", "Braniewo", "Lidzbark Warmiński", "Działdowo", "Nowe Miasto Lubawskie"] # 5K+ - single query
},
"Zachodniopomorskie": {
"major_cities": ["Szczecin"], # 400K+ - multiple queries
"large_cities": ["Koszalin", "Stargard", "Kołobrzeg", "Świnoujście"], # 50K+ - single query
"medium_cities": ["Szczecinek", "Police", "Wałcz", "Białogard", "Goleniów"], # 20K+ - single query
"small_cities": ["Choszczno", "Drawsko Pomorskie", "Myślibórz", "Chojna", "Pyrzyce"] # 5K+ - single query
},
"Lubuskie": {
"major_cities": ["Zielona Góra", "Gorzów Wielkopolski"], # 100K+ - single query each
"large_cities": ["Żary", "Żagań", "Sulechów"], # 50K+ - single query
"medium_cities": ["Nowa Sól", "Kostrzyn nad Odrą", "Świebodzin", "Międzyrzecz", "Słubice"], # 20K+ - single query
"small_cities": ["Wschowa", "Lubsko", "Krosno Odrzańskie", "Gubin", "Cybinka"] # 5K+ - single query
},
"Opolskie": {
"major_cities": ["Opole"], # 120K+ - single query
"large_cities": ["Kędzierzyn-Koźle", "Nysa", "Brzeg", "Kluczbork"], # 50K+ - single query
"medium_cities": ["Strzelce Opolskie", "Krapkowice", "Głubczyce", "Głuchołazy", "Paczków"], # 20K+ - single query
"small_cities": ["Zdzieszowice", "Gogolin", "Leśnica", "Prudnik", "Głuchołazy"] # 5K+ - single query
},
"Świętokrzyskie": {
"major_cities": ["Kielce"], # 200K+ - multiple queries
"large_cities": ["Ostrowiec Świętokrzyski", "Starachowice", "Skarżysko-Kamienna", "Sandomierz"], # 50K+ - single query
"medium_cities": ["Końskie", "Busko-Zdrój", "Jędrzejów", "Włoszczowa", "Pinczów"], # 20K+ - single query
"small_cities": ["Staszów", "Opatów", "Sędziszów", "Wąchock", "Ćmielów"] # 5K+ - single query
}
}
# Query strategy based on city size and business type
QUERY_STRATEGY = {
"major_cities": 3, # 3 queries for major cities (different areas/districts)
"large_cities": 1, # 1 query for large cities
"medium_cities": 1, # 1 query for medium cities
"small_cities": 1 # 1 query for small cities
}
# Rural business strategy - includes surrounding areas
RURAL_BUSINESS_STRATEGY = {
"major_cities": 4, # 4 queries: city + surrounding areas
"large_cities": 2, # 2 queries: city + surrounding areas
"medium_cities": 2, # 2 queries: city + surrounding areas
"small_cities": 2 # 2 queries: city + surrounding areas
}
# Business types that benefit from rural search
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 (SerpAPI pricing)
COST_PER_QUERY = 0.0005 # $0.0005 per query
st.set_page_config(
page_title="SerpAPI Business Finder",
page_icon="🏢",
layout="wide"
)
# Initialize database and scraper
@st.cache_resource
def get_database_manager():
return DatabaseManager()
@st.cache_resource
def get_contact_scraper():
return ContactScraper()
# Initialize session state
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:
# Initialize database manager and scraper in thread
db_manager = DatabaseManager()
contact_scraper = ContactScraper()
# Get companies that need contact collection
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
# Scrape contacts
results = contact_scraper.batch_scrape_contacts(companies)
# Update database with results
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']:
# Get existing phone from the company data
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}")
# Store result in session state for display
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
# Clear cache to show updated data
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]
# Clear contact collection cache
for key in list(st.session_state.keys()):
if key.startswith(f"companies_needing_contacts_{dataset_name}"):
del st.session_state[key]
# Trigger page refresh to show updated data
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:
# Użyj tokenu z pliku
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
# Konfiguracja repozytorium
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
# Pliki do uploadowania
files_to_upload = [
"database_manager.py",
"contact_scraper.py",
"requirements.txt"
]
# Opcjonalnie - upload głównego pliku jako app.py
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:
# Obsługa tuple (lokalna_nazwa, zdalna_nazwa) lub string
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:
# Sprawdź czy repozytorium istnieje, jeśli nie - utwórz
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)
# Upload pliku
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":
# Calculate based on city sizes
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:
# Find city type for each selected city
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
# Get appropriate query strategy based on business type
query_strategy = get_query_strategy(query)
is_rural = is_rural_business(query)
# Determine search locations based on strategy
search_locations = []
if search_strategy == "Inteligentne - Cała Polska":
# Search all cities with appropriate query count per city type
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:
# For rural businesses, search city + surrounding areas
if i == 0:
search_locations.append(city)
else:
# Add surrounding area modifiers for rural search
rural_modifiers = ["okolice", "gmina", "powiat", "rejon"]
modifier = rural_modifiers[(i-1) % len(rural_modifiers)]
search_locations.append(f"{modifier} {city}")
else:
# For urban businesses, search different city areas
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:
# Find city type and apply appropriate query count
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
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)
# Check if we hit rate limit
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
# Update progress
progress_bar.progress((i + 1) / len(search_locations))
# Longer delay to avoid rate limiting
time.sleep(1.0)
except Exception as e:
st.error(f"Błąd wyszukiwania w {location}: {str(e)}")
# If we get an error, stop searching to avoid more API calls
break
# Remove duplicates based on name and address
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 = []
# SerpAPI returns up to 20 results per request for Google Maps
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()
# Handle different response status codes
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
# Debug info (only show if there's an issue)
if 'error' in data:
st.error(f"🚫 **API Error: {data['error']}**")
# Provide specific guidance for common errors
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")
# Show what we got instead
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")
# Database status
st.info("🗄️ **Baza danych SQLite jest aktywna** - wszystkie wyniki są automatycznie zapisywane")
# API limits warning
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
""")
# Tips section
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
""")
# Sidebar
with st.sidebar:
st.header("⚙️ Konfiguracja")
# API Key input
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("---")
# Initialize query variable
query = st.text_input("Wyszukiwana fraza", value="myjnia samochodowa", help="np. myjnia samochodowa, hurtownia chemiczna")
# Dataset management
st.header("📊 Zarządzanie datasetami")
# Get existing datasets (with caching)
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 selection
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:
# Use empty string for business_type when creating dataset
success = st.session_state.db_manager.create_dataset(dataset_name, "", "")
if success:
# Clear datasets cache when creating new dataset
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
# Show dataset stats (with caching)
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("---")
# Search strategy
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("---")
# Search parameters
st.header("🔍 Parametry wyszukiwania")
max_results_per_query = st.slider("Wyników na zapytanie", 10, 20, 20, help="Maksymalnie 20 na zapytanie (limit SerpAPI)")
# Location selection based on strategy
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"])
# Show cost for selected strategy
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)")
# Show business type detection
if is_rural_business(query):
st.success("🌾 Wykryto biznes wiejski - zastosowano strategię rozszerzoną (miasto + okolice)")
else:
st.info("🏙️ Biznes miejski - standardowa strategia wyszukiwania")
# Main content
if not api_key or api_key == 'your_serpapi_key_here':
st.warning("Proszę skonfigurować klucz SerpAPI w panelu bocznym")
return
# Validation
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
# Show estimated cost and warn about limits
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: # More than 1 cent
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:
# Add location info to results
for result in results:
result['location'] = f"{query} {search_strategy}"
# Save to database
added_count = st.session_state.db_manager.add_companies(results, dataset_name, query)
# Clear cache when adding new companies
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]
# Clear contact collection cache
for key in list(st.session_state.keys()):
if key.startswith(f"companies_needing_contacts_{dataset_name}"):
del st.session_state[key]
# Calculate duplicates removed
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}'")
# Cost summary
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}%)")
# Display results
df = pd.DataFrame(results)
st.dataframe(df, width='stretch')
# Download buttons
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"
)
# Display raw data
with st.expander("📋 Szczegóły danych"):
st.json(results)
else:
st.warning("Nie znaleziono żadnych firm")
# Data management section - always visible
st.markdown("---")
st.header("📊 Zarządzanie danymi")
if dataset_choice != "(nowy)":
# Show current dataset data
if st.button("🔄 Odśwież dane"):
# Clear cache when refreshing
cache_key = f"companies_{dataset_name}"
if cache_key in st.session_state:
del st.session_state[cache_key]
# Clear contact collection cache
for key in list(st.session_state.keys()):
if key.startswith(f"companies_needing_contacts_{dataset_name}"):
del st.session_state[key]
st.rerun()
# Get companies from selected dataset (with caching)
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}'")
# Filter options
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)
# Apply filters
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')
# Contact collection section
st.subheader("📧 Zbieranie kontaktów")
# Contact collection options
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"
)
# Show how many companies need contact collection (with caching)
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:
# Start scraping in background
st.session_state.scraping_in_progress = True
st.session_state.scraping_result = f"Rozpoczynam zbieranie {search_type_text} dla {len(companies_needing_contacts)} firm..."
# Run in thread to avoid blocking
thread = threading.Thread(
target=run_contact_scraping,
args=(dataset_name, search_type, None, None)
)
thread.start()
st.rerun()
# Show scraping status
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)
# Check if we should refresh the page
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):
# Clear all cache when deleting dataset
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]
# Clear contact collection cache
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")
# Data management tabs
tab1, tab2, tab3, tab4 = st.tabs(["✏️ Edycja firm", "➕ Dodawanie firm", "☁️ Upload do HF", "🔄 Aktualizacja Space"])
with tab1:
st.subheader("✏️ Ręczne uzupełnianie danych")
# Select company to edit
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:
# Create company data
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:
# Clear cache when adding new company
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]
# Clear contact collection cache
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!")
# Table editing
st.subheader("📝 Edycja tabeli")
# Editable table
st.write("**Kliknij na komórkę aby edytować, zaznacz wiersze aby przenieść:**")
# Add checkbox column for selection
companies_df_with_checkbox = companies_df.copy()
companies_df_with_checkbox['Zaznacz'] = False
# Create editable dataframe with selection
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")
}
)
# Dataset transfer section
st.write("**Przenoszenie zaznaczonych firm:**")
# Get all datasets
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"):
# Get selected rows from checkboxes
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]
# Check for duplicates in target dataset
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:
# Clear cache when moving companies
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]
# Clear target dataset cache
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]
# Clear contact collection cache for both datasets
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.")
# Save changes button
if st.button("💾 Zapisz zmiany w tabeli"):
changes_made = False
# Check if rows were deleted
if len(edited_df) < len(companies_df):
# Some rows were deleted
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:
# Delete company from database
if st.session_state.db_manager.delete_company(company_id):
changes_made = True
st.info(f"Usunięto firmę o ID: {company_id}")
# Check for edited rows
for idx, row in edited_df.iterrows():
# Find corresponding original row by ID
original_row = companies_df[companies_df['id'] == row['id']]
if not original_row.empty:
original_row = original_row.iloc[0]
# Check for changes (ignore checkbox column)
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:
# Clear cache when saving changes
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]
# Clear contact collection cache
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")
# Download options
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()