1.5.2: Ergänzung heuristischer E-Mail-Generierung und Spaltenanpassung im Contacts-Blatt

- 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
This commit is contained in:
2025-04-10 06:11:28 +00:00
parent 2377149d76
commit d557ecce88

View File

@@ -1,16 +1,15 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Version: v1.5.1 Version: v1.5.2
Datum: {aktuelles Datum} Datum: {aktuelles Datum}
Git-Überschrift (max. 100 Zeichen): 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: Git-Änderungsbeschreibung:
- Umstellung der Geschlechtsbestimmung: Zuerst gender-guesser, Fallback zu Genderize API - Neue Funktion get_email_address zur Erzeugung der E-Mail-Adresse im Format vorname.nachname@domain.tld
- Geschlecht wird jetzt in Spalte D gespeichert (alle folgenden Felder rutschen um eine Spalte nach rechts) - Anpassung von process_contact_research: E-Mail-Adresse in Spalte I eingetragen, LinkedIn-Link und Timestamp entsprechend verschoben
- Aktualisierte Header und Kontaktzeilen im Contacts-Blatt, inklusive API-Key aus "genderize_API_Key.txt" - Integration der E-Mail-Generierung in den bestehenden Kontaktverarbeitungs-Workflow, bestehende Funktionen weitgehend unverändert
- Anpassung der Contact Research-Funktion zur Verarbeitung der geänderten Spalten
""" """
@@ -37,7 +36,7 @@ except ImportError:
# ==================== KONFIGURATION ==================== # ==================== KONFIGURATION ====================
class Config: class Config:
VERSION = "v1.5.1" VERSION = "v1.5.2"
LANG = "de" LANG = "de"
CREDENTIALS_FILE = "service_account.json" CREDENTIALS_FILE = "service_account.json"
SHEET_URL = "https://docs.google.com/spreadsheets/d/1u_gHr9JUfmV1-iviRzbSe3575QEp7KLhK5jFV_gJcgo" SHEET_URL = "https://docs.google.com/spreadsheets/d/1u_gHr9JUfmV1-iviRzbSe3575QEp7KLhK5jFV_gJcgo"
@@ -128,6 +127,25 @@ def get_gender(firstname):
else: else:
return result 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): def is_valid_company_article(wiki_categories):
""" """
Prüft, ob in den Wikipedia-Kategorien ein Hinweis auf einen Unternehmensartikel enthalten ist. 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(): def process_contact_research():
""" """
Sucht mithilfe der SerpAPI Kontakte für bestimmte Positionen für jedes Unternehmen. 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. 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 in das Kontakte-Blatt eingetragen. 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 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. und ein Timestamp in Spalte AM gesetzt.
@@ -1717,13 +1735,14 @@ def process_contact_research():
A: Firmenname A: Firmenname
B: CRM Kurzform B: CRM Kurzform
C: Website C: Website
D: Geschlecht (ermittelt aus dem Vornamen) D: Geschlecht
E: Vorname E: Vorname
F: Nachname F: Nachname
G: Position G: Position
H: Suchbegriffskategorie H: Suchbegriffskategorie
I: LinkedIn-Link I: E-Mail-Adresse
J: Timestamp J: LinkedIn-Link
K: Timestamp
Detaillierte Debug-Ausgaben sorgen für Transparenz bei der Ausführung. 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() data = main_sheet.get_all_values()
# Ermittle die letzte Zeile in Spalte AM (Spalte 39), in der ein Timestamp eingetragen wurde # 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 last_filled_row = 1 # Header-Zeile
for idx, cell in enumerate(col_am): for idx, cell in enumerate(col_am):
if cell.strip() != "": if cell.strip() != "":
last_filled_row = idx + 1 # 0-basierter Index -> Zeilennummer last_filled_row = idx + 1
start_row = last_filled_row + 1 # Verarbeitung ab der nächsten Zeile 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}.") 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): if start_row > len(data):
debug_print("Keine neuen Zeilen zu verarbeiten, da Timestamp in Spalte AM bereits bis zum Ende vorhanden ist.") debug_print("Keine neuen Zeilen zu verarbeiten, da Timestamp in Spalte AM bereits bis zum Ende vorhanden ist.")
return return
@@ -1753,15 +1771,15 @@ def process_contact_research():
try: try:
contacts_sheet = sh.worksheet("Contacts") contacts_sheet = sh.worksheet("Contacts")
except gspread.exceptions.WorksheetNotFound: 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", header = ["Firmenname", "CRM Kurzform", "Website", "Geschlecht", "Vorname", "Nachname", "Position",
"Suchbegriffskategorie", "LinkedIn-Link", "Timestamp"] "Suchbegriffskategorie", "E-Mail-Adresse", "LinkedIn-Link", "Timestamp"]
contacts_sheet.update(values=[header], range_name="A1:J1") contacts_sheet.update(values=[header], range_name="A1:K1")
debug_print("Neues Blatt 'Contacts' erstellt und Header eingetragen.") debug_print("Neues Blatt 'Contacts' erstellt und Header eingetragen.")
# Verarbeite jede Zeile ab der ermittelten Startzeile # Verarbeite jede Zeile ab der ermittelten Startzeile
for i in range(start_row, len(data) + 1): 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 "" company_name = row[1] if len(row) > 1 else ""
crm_kurzform = row[2] if len(row) > 2 else "" crm_kurzform = row[2] if len(row) > 2 else ""
website = row[3] if len(row) > 3 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"] positions = ["Serviceleiter", "IT-Leiter", "Geschäftsführer", "Disponent"]
contact_counts = {} contact_counts = {}
# Für jeden Positionsbegriff: Kontakte zählen und exemplarisch einen Kontakt suchen
for pos in positions: for pos in positions:
count = count_linkedin_contacts(crm_kurzform, website, pos, crm_kurzform) count = count_linkedin_contacts(crm_kurzform, website, pos, crm_kurzform)
contact_counts[pos] = count contact_counts[pos] = count
contact = search_linkedin_contact(crm_kurzform, website, pos, crm_kurzform) contact = search_linkedin_contact(crm_kurzform, website, pos, crm_kurzform)
if contact: if contact:
# Ermittle das Geschlecht anhand des Vornamens
firstname = contact.get("Vorname", "") firstname = contact.get("Vorname", "")
lastname = contact.get("Nachname", "")
gender_value = get_gender(firstname) if firstname else "unknown" gender_value = get_gender(firstname) if firstname else "unknown"
# Erstelle den Kontaktzeile-Eintrag: email = get_email_address(firstname, lastname, website)
# 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
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") 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 = [ contact_row = [
company_name, company_name,
crm_kurzform, crm_kurzform,
website, website,
gender_value, # Geschlecht (Spalte D) gender_value,
contact.get("Vorname", ""), firstname,
contact.get("Nachname", ""), lastname,
contact.get("Position", ""), contact.get("Position", ""),
pos, # Suchbegriffskategorie (Spalte H) pos,
email,
contact.get("LinkedInURL", ""), contact.get("LinkedInURL", ""),
timestamp # Timestamp (Spalte J) timestamp
] ]
try: try:
contacts_sheet.append_row(contact_row) contacts_sheet.append_row(contact_row)
@@ -1812,7 +1823,7 @@ def process_contact_research():
else: else:
debug_print(f"Zeile {i}: Kein passender Kontakt für Position '{pos}' gefunden.") debug_print(f"Zeile {i}: Kein passender Kontakt für Position '{pos}' gefunden.")
# Aktualisierung des Hauptblatts # Aktualisiere Hauptblatt-Zeile mit Trefferzahlen und Timestamp
try: try:
main_sheet.update(values=[[str(contact_counts.get("Serviceleiter", 0))]], range_name=f"AI{i}") 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}") 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) time.sleep(Config.RETRY_DELAY)
debug_print("Contact Research abgeschlossen.") debug_print("Contact Research abgeschlossen.")
# ----------------- DataProcessor-Klasse inklusive neuer SERP-API Website Lookup-Methode ----------------- # ----------------- DataProcessor-Klasse inklusive neuer SERP-API Website Lookup-Methode -----------------
class DataProcessor: class DataProcessor:
def __init__(self): def __init__(self):