Helllo! I am currently trying to take a bunch of differnet-formatted excel files with information (Names, Jobs, Phone #s, Emails, etc) and turn them all into one large master sheet with a uniform formatting. I tried using python to automate this/solve this issue but I am having a lot of trouble getting my code to recognize specific data entries as a specific data type (mistaking a country for a name or a phone # for an email, etc) I am not that experienced at coding practically like this, but I was wondering if anyones done this before or has tips on how to create a tool that could do this. Thank You! (P.S. I've attached what ChatGPT and Me (mostly chat lol) have come up with) #!/usr/bin/env python3 import os import re import sys import datetime import pandas as pd # ─── USAGE & INPUT PATH ──────────────────────────────────────────────────── if len(sys.argv) != 2: print("Usage: python extract_contacts_master.py <folder-of-excels>") sys.exit(1) INPUT_DIR = sys.argv[1] if not os.path.isdir(INPUT_DIR): print(f"❌ Folder not found: {INPUT_DIR}") sys.exit(1) # ─── OUTPUT CONFIG ───────────────────────────────────────────────────────── OUTPUT_DIR = os.path.expanduser("~/Downloads") TS = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") OUT_CSV = os.path.join(OUTPUT_DIR, f"contacts_master_{TS}.csv") # ─── PATTERNS ────────────────────────────────────────────────────────────── NAME_RE = re.compile(r"^[A-Z][a-z]*(?:\s*[A-Z][a-z]*)+$") # Names with capitalized words PHONE_RE = re.compile(r"\b(?:\+?1[\s.-]?)?(?:\(\d{3}\)|\d{3})[\s.-]?\d{3}[\s.-]?\d{4}(?:\s*(?:ext|x|ext\.)\s*\d+)?(?:\.\d)?\b") # Expanded for various formats: (123) 456-7890, 123-456-7890, +1 123.456.7890, 1234567890, with ext EMAIL_RE = re.compile(r"[\w\.-]+@[\w\.-]+\.\w+") ORG_KEYWORD_RE = re.compile(r"\b(?:inc|llc|ltd|corp|company|partners|group|management|capital|llp|bank|llc|fund|investments|advisors|group|co|lp|partners|associates|firm)\b", re.I) # Expanded for common org words TITLE_SET = {"partner", "counsel", "associate", "patent counsel", "special counsel", "senior counsel", "of counsel", "managing partner", "office managing shareholder", "managing director", "senior associate", "senior vice president", "vice president", "executive director", "chief operating officer", "chief financial officer", "chief compliance officer", "director", "senior director", "principal", "senior principal", "portfolio manager", "analyst", "senior analyst"} # Expanded title set CITY_PATTERN = re.compile(r"^(?:[A-Z][a-z]+(?:\s[A-Z][a-z]+)?(?:\sMetropolitan\sArea)?|Greater\s[A-Z][a-z]+\sArea|San\sFrancisco\sBay\sArea|Los\sAngeles\sMetropolitan\sArea|New\sYork\sCity\sMetropolitan\sArea|United\sStates|USA)$") # Pattern for cities/locations DNC_TERMS = {"dnc", "do not call"} # ─── HELPERS ────────────────────────────────────────────────────────────── def choose_engine(fname): return "openpyxl" if fname.lower().endswith(".xlsx") else "xlrd" def find_header(df): # Scan first 50 rows for a “name-like” cell for i, row in df.head(50).iterrows(): for v in row.dropna().astype(str): if NAME_RE.match(v.strip()): return i return None def score_col(col, regex): vs = col.dropna().astype(str) return sum(bool(regex.search(v)) for v in vs) / len(vs) if len(vs) else 0 def score_org(col): vs = col.dropna().astype(str) return sum(bool(ORG_KEYWORD_RE.search(v)) for v in vs) / len(vs) if len(vs) else 0 def score_title(col): vs = col.dropna().astype(str).str.lower() return sum(any(t in v for t in TITLE_SET) for v in vs) / len(vs) if len(vs) else 0 def score_city(col): vs = col.dropna().astype(str) return sum(bool(CITY_PATTERN.match(v)) for v in vs) / len(vs) if len(vs) else 0 def pick_columns(df): cols = df.columns if len(cols) == 0: return None, None, None, None, None # Avoid columns that look like cities non_city_cols = [c for c in cols if score_city(df[c]) < 0.5] name_col = max(non_city_cols, key=lambda c: score_col(df[c], NAME_RE)) if non_city_cols else None remaining = [c for c in non_city_cols if c != name_col] if name_col else non_city_cols if not remaining: return name_col, None, None, None, None phone_col = max(remaining, key=lambda c: score_col(df[c], PHONE_RE)) if remaining else None remaining = [c for c in remaining if c != phone_col] if phone_col else remaining email_col = max(remaining, key=lambda c: score_col(df[c], EMAIL_RE)) if remaining else None remaining = [c for c in remaining if c != email_col] if email_col else remaining title_col = max(remaining, key=lambda c: score_title(df[c])) if remaining else None remaining = [c for c in remaining if c != title_col] if title_col else remaining org_col = max(remaining, key=lambda c: score_org(df[c])) if remaining else None return name_col, phone_col, email_col, org_col, title_col def split_full_name(name): if not name: return "", "" # Remove suffixes and nicknames name = re.sub(r',\s*(Jr\.|Sr\.|II|III|IV)\b', '', name, flags=re.I) name = re.sub(r'\s*\([^)]+\)', '', name).strip() # Skip if it looks like a title, location, or organization if name.lower() in TITLE_SET or CITY_PATTERN.match(name) or ORG_KEYWORD_RE.search(name.lower()): return "", "" parts = name.split() if len(parts) < 2: return name, "" return " ".join(parts[:-1]), parts[-1] def clean_phone(phone): if not phone: return "" phone = str(phone).strip().rstrip('.0') # Normalize format: add dashes if 10 digits digits = re.sub(r'\D', '', phone) if len(digits) == 10: return f"{digits[:3]}-{digits[3:6]}-{digits[6:]}" elif len(digits) == 11 and digits.startswith('1'): return f"1-{digits[1:4]}-{digits[4:7]}-{digits[7:]}" return phone # Keep original if not matching def extract_from_sheet(df, fname, sheet): name_col, phone_col, email_col, org_col, title_col = pick_columns(df) if name_col is None: print(f"Skipping sheet {sheet} in {fname}: No columns or no name column found") return [] contacts = [] for _, row in df.iterrows(): # Skip DNC combined = " ".join(str(v).lower() for v in row if not pd.isna(v)) if any(term in combined for term in DNC_TERMS): continue name = str(row[name_col]).strip() if name_col is not None and pd.notna(row[name_col]) else "" if not name: continue first, last = split_full_name(name) if not first and not last: continue phone = clean_phone(row[phone_col] if phone_col is not None and pd.notna(row[phone_col]) else "") email = str(row[email_col]).strip() if email_col is not None and pd.notna(row[email_col]) else "" org = str(row[org_col]).strip() if org_col is not None and pd.notna(row[org_col]) else "" title = str(row[title_col]).strip() if title_col is not None and pd.notna(row[title_col]) else "" # Infer org from email if missing and not an email provider (e.g., skip gmail, yahoo) if not org and email and EMAIL_RE.match(email): domain = email.split('@')[-1].split('.')[0].lower() if domain not in {'gmail', 'yahoo', 'hotmail', 'outlook', 'icloud', 'me', 'aol'}: org = domain.capitalize() # Skip if no meaningful data if not (phone or email or org or title): continue contacts.append({ "First Name": first, "Last Name": last, "Phone #": phone, "Organization": org, "Title": title, "Country": "", # Blank as per request "Email": email }) return contacts # ─── MAIN ───────────────────────────────────────────────────────────────── all_contacts = [] files = sorted(f for f in os.listdir(INPUT_DIR) if f.lower().endswith((".xlsx", ".xls"))) print(f" Found {len(files)} files in: {INPUT_DIR}\n") for fn in files: path = os.path.join(INPUT_DIR, fn) engine = choose_engine(fn) try: xls = pd.ExcelFile(path, engine=engine) except Exception as e: print(f"⚠️ Skipping {fn}: cannot open ({e})") continue for sheet in xls.sheet_names: if "dnc" in sheet.lower(): print(f"Skipping DNC sheet: {sheet} in {fn}") continue try: raw_df = pd.read_excel(path, sheet_name=sheet, header=None, engine=engine).dropna(how="all") hdr = find_header(raw_df) if hdr is not None: df = pd.read_excel(path, sheet_name=sheet, header=hdr, engine=engine).dropna(how="all") else: df = raw_df # Fallback to no header except Exception as e: print(f"⚠️ Skipping sheet {sheet} in {fn}: {e}") continue print(f"✅ Parsing {fn} → sheet “{sheet}”") contacts = extract_from_sheet(df, fn, sheet) all_contacts.extend(contacts) if not all_contacts: print("\n❌ No contacts extracted.") sys.exit(1) df_out = pd.DataFrame(all_contacts).drop_duplicates() df_out.to_csv(OUT_CSV, index=False) print(f"\n✅ Done! Extracted {len(df_out)} contacts → {OUT_CSV}") Continue reading...