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:
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user