# Neue Version mit Wikipedia-Validierung, GPT-Schutz und Antwortlogging inkl. Retry import os import time import csv import re import pandas as pd import gspread import openai import wikipedia from bs4 import BeautifulSoup import requests from oauth2client.service_account import ServiceAccountCredentials from datetime import datetime from openai.error import OpenAIError # === KONFIGURATION === EXCEL = "Bestandsfirmen.xlsx" SHEET_URL = "https://docs.google.com/spreadsheets/d/1u_gHr9JUfmV1-iviRzbSe3575QEp7KLhK5jFV_gJcgo" CREDENTIALS = "service_account.json" LANG = "de" LOG_CSV = "gpt_antworten_log.csv" DURCHLÄUFE = int(input("Wieviele Zeilen sollen überprüft werden? ")) # === OpenAI API-KEY LADEN === with open("api_key.txt", "r") as f: openai.api_key = f.read().strip() # === GOOGLE SHEET VERBINDUNG === scope = ["https://www.googleapis.com/auth/spreadsheets"] creds = ServiceAccountCredentials.from_json_keyfile_name(CREDENTIALS, scope) sheet = gspread.authorize(creds).open_by_url(SHEET_URL).sheet1 sheet_values = sheet.get_all_values() # === STARTINDEX SUCHEN (Spalte N = Index 13) === filled_n = [row[13] if len(row) > 13 else '' for row in sheet_values[1:]] start = next((i + 1 for i, v in enumerate(filled_n, start=1) if not str(v).strip()), len(filled_n) + 1) print(f"Starte bei Zeile {start+1}") wikipedia.set_lang(LANG) # === SYSTEM PROMPT === branches = [ "Hersteller / Produzenten > Maschinenbau", "Hersteller / Produzenten > Automobil", "Hersteller / Produzenten > Anlagenbau", "Hersteller / Produzenten > Medizintechnik", "Hersteller / Produzenten > Chemie & Pharma", "Hersteller / Produzenten > Elektrotechnik", "Hersteller / Produzenten > Lebensmittelproduktion", "Hersteller / Produzenten > IT / Telekommunikation", "Hersteller / Produzenten > Bürotechnik", "Hersteller / Produzenten > Automaten (Vending, Slot)", "Hersteller / Produzenten > Gebäudetechnik Heizung, Lüftung, Klima", "Hersteller / Produzenten > Gebäudetechnik Allgemein", "Hersteller / Produzenten > Schädlingsbekämpfung", "Hersteller / Produzenten > Fertigung", "Hersteller / Produzenten > Braune & Weiße Ware", "Versorger > Stadtwerk", "Versorger > Verteilnetzbetreiber", "Versorger > Telekommunikation", "Dienstleister > Messdienstleister", "Dienstleister > Facility Management", "Dienstleister > Healthcare/Pflegedienste", "Dienstleister > Servicedienstleister / Reparatur ohne Produktion", "Handel & Logistik > Auslieferdienste", "Handel & Logistik > Energie (Brennstoffe)", "Handel & Logistik > Großhandel", "Handel & Logistik > Einzelhandel", "Handel & Logistik > Logistik Sonstige", "Sonstige > Unternehmensberatung (old)", "Sonstige > Sonstige", "Sonstige > Agrar, Pellets (old)", "Sonstige > Sonstiger Service (old)", "Sonstige > IT Beratung", "Sonstige > Engineering", "Baubranche > Baustoffhandel", "Baubranche > Baustoffindustrie", "Baubranche > Logistiker Baustoffe", "Baubranche > Bauunternehmen", "Gutachter / Versicherungen > Versicherungsgutachten", "Gutachter / Versicherungen > Technische Gutachter", "Gutachter / Versicherungen > Medizinische Gutachten" ] system_prompt = { "role": "system", "content": ( "Du bist ein Experte für Brancheneinstufung und FSM-Potenzialbewertung. " "Bitte beziehe dich ausschließlich auf das konkret genannte Unternehmen. Ähnlich klingende Firmennamen dürfen nicht verwendet werden.\n" "FSM steht für Field Service Management – Software zur Planung und Unterstützung mobiler Techniker.\n" "Ziel ist es, Unternehmen mit >50 Technikern im Außeneinsatz zu identifizieren.\n\n" "Struktur: Firmenname; Website; Ort; Aktuelle Einstufung; Beschreibung der Branche Extern\n\n" "Gib deine Antwort im CSV-Format (1 Zeile, 8 Spalten, durch Semikolon getrennt):\n" "Wikipedia-Branche;LinkedIn-Branche;Umsatz (Mio €);Empfohlene Neueinstufung;Begründung;FSM-Relevanz (Ja/Nein/k.A. mit Begründung);Techniker-Einschätzung;Techniker-Begründung\n\n" "Falls ein Wikipedia-Link angegeben ist, vertraue ausschließlich den Angaben aus diesem Artikel für Branche und Umsatz.\n" "Falls kein Wikipedia-Link existiert, gib für 'Wikipedia-Branche' und 'Umsatz (Mio €)' bitte 'k.A.' aus.\n\n" "Ziel-Branchenschema:\n" + "\n".join(branches) ) } WHITELIST_KATEGORIEN = ["Unternehmen", "Hersteller", "Produktion", "Industrie", "Maschinenbau", "Technik", "Dienstleistungsunternehmen"] def get_wikipedia_data(name, website_hint=""): begriffe = [name.strip(), " ".join(name.split()[:2])] if website_hint: parts = website_hint.replace("https://", "").replace("http://", "").split(".") if len(parts) > 1: begriffe.append(parts[0]) for suchbegriff in begriffe: results = wikipedia.search(suchbegriff, results=3) for title in results: try: page = wikipedia.page(title) if any(x in page.title.lower() for x in ["krankenkasse", "versicherung"]): continue url = page.url html = requests.get(url).text if website_hint: domain_fragment = website_hint.lower().split(".")[0] if domain_fragment not in html.lower(): continue if name.lower().split()[0] not in page.title.lower(): continue soup = BeautifulSoup(html, 'html.parser') infobox = soup.find("table", class_=["infobox", "infobox vcard"]) if not infobox: tables = soup.find_all("table") for table in tables: if any("Branche" in (th.text if th else '') for th in table.find_all("th")): infobox = table break branche = umsatz = "" if infobox: for row in infobox.find_all("tr"): th, td = row.find("th"), row.find("td") if not th or not td: continue if th and "branche" in th.text.lower().strip(): branche = td.text.strip() if "Umsatz" in th.text: umsatz_raw = td.text.strip() if "Mio" in umsatz_raw: match = re.search(r"(\d+[,.]?\d*)", umsatz_raw) if match: umsatz = match.group(1).replace(",", ".") if not branche: cats = page.categories if any(any(w.lower() in c.lower() for w in WHITELIST_KATEGORIEN) for c in cats): branche = cats[0] else: return "", "k.A.", "k.A." return url, branche or "k.A.", umsatz or "k.A." except: continue return "", "k.A.", "k.A." def classify_company(row, wikipedia_url=""): user_prompt = { "role": "user", "content": f"{row[0]};{row[1]};{row[2]};{row[4]};{row[5]}\nWikipedia-Link: {wikipedia_url}" } for attempt in range(3): try: response = openai.chat.completions.create( model="gpt-3.5-turbo", messages=[system_prompt, user_prompt], temperature=0 ) full_text = response.choices[0].message.content.strip() break except OpenAIError as e: print(f"⚠️ GPT-Fehler bei Versuch {attempt+1}: {e}") time.sleep(10) else: print("❌ GPT 3x fehlgeschlagen – setze alles auf 'k.A.'") full_text = "k.A.;k.A.;k.A.;k.A.;k.A.;k.A.;k.A.;k.A." lines = full_text.splitlines() csv_line = next((l for l in lines if ";" in l and not l.lower().startswith("wikipedia-branche")), "") parts = [v.strip().strip('"') for v in csv_line.split(";")] if csv_line else ["k.A."] * 8 if len(parts) != 8: parts = ["k.A."] * 8 with open(LOG_CSV, "a", newline="", encoding="utf-8") as log: writer = csv.writer(log, delimiter=";") writer.writerow([datetime.now().strftime("%Y-%m-%d %H:%M:%S"), row[0], *parts, full_text]) return parts # === VERARBEITUNG === for i in range(start, min(start + DURCHLÄUFE, len(sheet_values))): row = sheet_values[i] print(f"[{time.strftime('%H:%M:%S')}] Verarbeite Zeile {i+1}: {row[0]}") url, wiki_branche, umsatz = get_wikipedia_data(row[0], row[1]) wiki, linkedin, umsatz_chat, new_cat, reason, fsm, techniker, techniker_reason = classify_company(row, wikipedia_url=url) wiki_final = wiki_branche if url else "k.A." umsatz_final = umsatz if url else "k.A." values = [ wiki_final, linkedin, umsatz_final, new_cat, reason, fsm, url, datetime.now().strftime("%Y-%m-%d %H:%M:%S"), techniker, techniker_reason ] sheet.update(range_name=f"G{i+1}:P{i+1}", values=[values]) time.sleep(5) print("✅ Durchläufe abgeschlossen")