diff --git a/brancheneinstufung.py b/brancheneinstufung.py index 39c22edd..dadbac19 100644 --- a/brancheneinstufung.py +++ b/brancheneinstufung.py @@ -1,16 +1,15 @@ #!/usr/bin/env python3 """ -Version: v1.5.1 +Version: v1.5.2 Datum: {aktuelles Datum} Git-Überschrift (max. 100 Zeichen): -1.5.1: Integrierter hybrider Geschlechtsdetektor & aktualisierte Kontakte-Spalten +1.5.2: Ergänzung heuristischer E-Mail-Generierung und Spaltenanpassung im Contacts-Blatt Git-Änderungsbeschreibung: -- Umstellung der Geschlechtsbestimmung: Zuerst gender-guesser, Fallback zu Genderize API -- Geschlecht wird jetzt in Spalte D gespeichert (alle folgenden Felder rutschen um eine Spalte nach rechts) -- Aktualisierte Header und Kontaktzeilen im Contacts-Blatt, inklusive API-Key aus "genderize_API_Key.txt" -- Anpassung der Contact Research-Funktion zur Verarbeitung der geänderten Spalten +- Neue Funktion get_email_address zur Erzeugung der E-Mail-Adresse im Format vorname.nachname@domain.tld +- Anpassung von process_contact_research: E-Mail-Adresse in Spalte I eingetragen, LinkedIn-Link und Timestamp entsprechend verschoben +- Integration der E-Mail-Generierung in den bestehenden Kontaktverarbeitungs-Workflow, bestehende Funktionen weitgehend unverändert """ @@ -37,7 +36,7 @@ except ImportError: # ==================== KONFIGURATION ==================== class Config: - VERSION = "v1.5.1" + VERSION = "v1.5.2" LANG = "de" CREDENTIALS_FILE = "service_account.json" SHEET_URL = "https://docs.google.com/spreadsheets/d/1u_gHr9JUfmV1-iviRzbSe3575QEp7KLhK5jFV_gJcgo" @@ -128,6 +127,25 @@ def get_gender(firstname): else: return result +def get_email_address(firstname, lastname, website): + """ + Generiert eine E-Mail-Adresse im Format vorname.nachname@domain.tld. + Dabei wird der Domainname aus der Website extrahiert. + """ + # Falls die Website keinen Protokoll-Präfix hat, hinzufügen, um das Parsen zu ermöglichen + url = website if website.startswith("http") else "http://" + website + parsed = urlparse(url) + domain = parsed.netloc + if domain.startswith("www."): + domain = domain[4:] + # Entferne Nicht-Alphanumerische Zeichen aus Vor- und Nachnamen und wandle in Kleinbuchstaben um + f = re.sub(r'\W+', '', firstname.lower()) + l = re.sub(r'\W+', '', lastname.lower()) + if f and l: + return f"{f}.{l}@{domain}" + else: + return "" + def is_valid_company_article(wiki_categories): """ Prüft, ob in den Wikipedia-Kategorien ein Hinweis auf einen Unternehmensartikel enthalten ist. @@ -1707,9 +1725,9 @@ def count_linkedin_contacts(company_name, website, position_query, crm_kurzform) def process_contact_research(): """ Sucht mithilfe der SerpAPI Kontakte für bestimmte Positionen für jedes Unternehmen. - Es werden zunächst die CRM-Daten (insbesondere CRM Kurzform in Spalte C) sowie Firma und Website aus dem Hauptblatt genommen. - Die gefundenen Kontakte, welche den Filter (CRM Kurzform muss im Titel enthalten sein) erfüllen, werden in das Kontakte-Blatt eingetragen. - + Es werden zunächst die CRM-Daten (insbesondere CRM Kurzform in Spalte C) sowie Firma und Website aus dem Hauptblatt gelesen. + Die gefundenen Kontakte, welche den Filter (CRM Kurzform muss im Titel enthalten sein) erfüllen, werden im Kontakte-Blatt eingetragen. + Zusätzlich werden die Trefferzahlen (als Summen pro Position) in das Hauptblatt in den Spalten AI, AJ, AK, AL geschrieben und ein Timestamp in Spalte AM gesetzt. @@ -1717,13 +1735,14 @@ def process_contact_research(): A: Firmenname B: CRM Kurzform C: Website - D: Geschlecht (ermittelt aus dem Vornamen) + D: Geschlecht E: Vorname F: Nachname G: Position H: Suchbegriffskategorie - I: LinkedIn-Link - J: Timestamp + I: E-Mail-Adresse + J: LinkedIn-Link + K: Timestamp Detaillierte Debug-Ausgaben sorgen für Transparenz bei der Ausführung. """ @@ -1736,15 +1755,14 @@ def process_contact_research(): data = main_sheet.get_all_values() # Ermittle die letzte Zeile in Spalte AM (Spalte 39), in der ein Timestamp eingetragen wurde - col_am = main_sheet.col_values(39) # Spalte AM = 39. Spalte + col_am = main_sheet.col_values(39) # Spalte AM hat den Index 39 (A=1, ..., AM=39) last_filled_row = 1 # Header-Zeile for idx, cell in enumerate(col_am): if cell.strip() != "": - last_filled_row = idx + 1 # 0-basierter Index -> Zeilennummer - start_row = last_filled_row + 1 # Verarbeitung ab der nächsten Zeile + last_filled_row = idx + 1 + start_row = last_filled_row + 1 debug_print(f"Letzter Timestamp in Spalte AM wurde in Zeile {last_filled_row} gefunden. Starte Verarbeitung ab Zeile {start_row}.") - # Falls start_row größer ist als die Anzahl der vorhandenen Zeilen, gibt es nichts zu verarbeiten. if start_row > len(data): debug_print("Keine neuen Zeilen zu verarbeiten, da Timestamp in Spalte AM bereits bis zum Ende vorhanden ist.") return @@ -1753,15 +1771,15 @@ def process_contact_research(): try: contacts_sheet = sh.worksheet("Contacts") except gspread.exceptions.WorksheetNotFound: - contacts_sheet = sh.add_worksheet(title="Contacts", rows="1000", cols="10") + contacts_sheet = sh.add_worksheet(title="Contacts", rows="1000", cols="12") header = ["Firmenname", "CRM Kurzform", "Website", "Geschlecht", "Vorname", "Nachname", "Position", - "Suchbegriffskategorie", "LinkedIn-Link", "Timestamp"] - contacts_sheet.update(values=[header], range_name="A1:J1") + "Suchbegriffskategorie", "E-Mail-Adresse", "LinkedIn-Link", "Timestamp"] + contacts_sheet.update(values=[header], range_name="A1:K1") debug_print("Neues Blatt 'Contacts' erstellt und Header eingetragen.") # Verarbeite jede Zeile ab der ermittelten Startzeile for i in range(start_row, len(data) + 1): - row = data[i - 1] # data ist 0-basiert + row = data[i - 1] company_name = row[1] if len(row) > 1 else "" crm_kurzform = row[2] if len(row) > 2 else "" website = row[3] if len(row) > 3 else "" @@ -1771,38 +1789,31 @@ def process_contact_research(): positions = ["Serviceleiter", "IT-Leiter", "Geschäftsführer", "Disponent"] contact_counts = {} - # Für jeden Positionsbegriff: Kontakte zählen und exemplarisch einen Kontakt suchen for pos in positions: count = count_linkedin_contacts(crm_kurzform, website, pos, crm_kurzform) contact_counts[pos] = count contact = search_linkedin_contact(crm_kurzform, website, pos, crm_kurzform) if contact: - # Ermittle das Geschlecht anhand des Vornamens firstname = contact.get("Vorname", "") + lastname = contact.get("Nachname", "") gender_value = get_gender(firstname) if firstname else "unknown" - # Erstelle den Kontaktzeile-Eintrag: - # Spalte A: Firmenname - # Spalte B: CRM Kurzform - # Spalte C: Website - # Spalte D: Geschlecht - # Spalte E: Vorname - # Spalte F: Nachname - # Spalte G: Position - # Spalte H: Suchbegriffskategorie - # Spalte I: LinkedIn-Link - # Spalte J: Timestamp + email = get_email_address(firstname, lastname, website) timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + # Neue Reihenfolge: + # A: Firmenname, B: CRM Kurzform, C: Website, D: Geschlecht, E: Vorname, F: Nachname, + # G: Position, H: Suchbegriffskategorie, I: E-Mail-Adresse, J: LinkedIn-Link, K: Timestamp contact_row = [ company_name, crm_kurzform, website, - gender_value, # Geschlecht (Spalte D) - contact.get("Vorname", ""), - contact.get("Nachname", ""), + gender_value, + firstname, + lastname, contact.get("Position", ""), - pos, # Suchbegriffskategorie (Spalte H) + pos, + email, contact.get("LinkedInURL", ""), - timestamp # Timestamp (Spalte J) + timestamp ] try: contacts_sheet.append_row(contact_row) @@ -1812,7 +1823,7 @@ def process_contact_research(): else: debug_print(f"Zeile {i}: Kein passender Kontakt für Position '{pos}' gefunden.") - # Aktualisierung des Hauptblatts + # Aktualisiere Hauptblatt-Zeile mit Trefferzahlen und Timestamp try: main_sheet.update(values=[[str(contact_counts.get("Serviceleiter", 0))]], range_name=f"AI{i}") main_sheet.update(values=[[str(contact_counts.get("IT-Leiter", 0))]], range_name=f"AJ{i}") @@ -1826,7 +1837,6 @@ def process_contact_research(): time.sleep(Config.RETRY_DELAY) debug_print("Contact Research abgeschlossen.") - # ----------------- DataProcessor-Klasse inklusive neuer SERP-API Website Lookup-Methode ----------------- class DataProcessor: def __init__(self):