From 0cd29e3584fcf0c89725e9b0758f2d505398a90e Mon Sep 17 00:00:00 2001 From: break27 Date: Tue, 8 Jul 2025 15:39:45 +0800 Subject: [PATCH] update: pivot to pandas from csv --- requirements.txt | Bin 622 -> 898 bytes 邮件批量发送脚本.py | 154 +++++++++++++++++++++++++------------------- 2 files changed, 86 insertions(+), 68 deletions(-) diff --git a/requirements.txt b/requirements.txt index 1196d1c0c41ffd66d7890008374125b476134f6f..e658d27ae9d6ab4e5e41051819cf79744110cfe0 100644 GIT binary patch delta 249 zcmaFI(!@R?MKP73gdv`xf+3e7hart26G)~q*aD#ugC2tc2v3YRx6ETG1u7_DsDw)y z1EmZZcp11D@`2*140&ML3ZQn7W{9jFgXzSH#^Q-U$rOe}hGJCB6Zg8SgG?-8$N-v= z$Dj*VR01@s6etFA2+SFl6W2-#moQX81q^_0GX+|2H1WSSeN&R7lrR-iA$ delta 28 kcmZo-f5$Q*Wn!1n#9w-o-54Dv-(yspEXQR diff --git a/邮件批量发送脚本.py b/邮件批量发送脚本.py index 9fb9bd1..23f2f92 100644 --- a/邮件批量发送脚本.py +++ b/邮件批量发送脚本.py @@ -1,6 +1,7 @@ import argparse +import pandas import time -import csv +import itertools from selenium import webdriver from selenium.common.exceptions import StaleElementReferenceException, TimeoutException @@ -15,10 +16,10 @@ parser = argparse.ArgumentParser(description="邮件批量发送脚本") parser.add_argument('input', nargs='?') parser.add_argument('--column-address', type=str, nargs='?', default='邮箱') parser.add_argument('--column-code', type=str, nargs='?', default='客户编号') +parser.add_argument('--column-mark', type=str, nargs='?', default='已发送') parser.add_argument('-u', '--url', type=str, nargs='?', default='https://id.ionos.fr/identifier') parser.add_argument('-a', '--address', type=str, required=True) parser.add_argument('-p', '--password', type=str, required=True) -parser.add_argument('-e', '--encoding', type=str, nargs='?', default='utf-8') parser.add_argument('-t', '--timeout', type=int, nargs='?', default=60) parser.add_argument('-i', '--interval', type=int, nargs='?', default=10) parser.add_argument('-m', '--max-occurrence', type=int, nargs='?', default=5) @@ -35,20 +36,23 @@ def main(): if not args.input: root = Tk() root.withdraw() - args.input = filedialog.askopenfilename(defaultextension='csv') + args.input = filedialog.askopenfilename(defaultextension='xlsx') print(f'[信息] 正在读取数据:{args.input}') try: - with open(args.input, 'r', encoding=args.encoding) as file: - rows = csv.DictReader(file) - data = list(rows) + workbook = pandas.read_excel(args.input) + data = workbook.where(pandas.notnull(workbook), None).to_dict(orient='list') + recipients = data.get(args.column_address, []) + codes = data.get(args.column_code, []) except Exception as e: print(f'[!!!!] 读取数据表失败:{e}') return 1 - rate = 60 / (args.interval + 3) - limit = len(data) + limit = len(recipients) + marks = data.setdefault(args.column_mark, [None] * limit) + print(f'[信息] 已读取联系人信息共 {limit} 条') + if limit == 0: return 0 print('[信息] 正在启动 Chrome 自动化实例') try: @@ -144,13 +148,14 @@ def main(): print(f'[!!!!] 读取邮件时发生了错误:{e}') return 5 + rate = 60 / (args.interval + 3) print(f'[信息] 当前发送速率 {round(rate, 2)} 封/分钟') print(f'[信息] 预计使用时间 {timedelta(minutes=limit / rate)}') if rate > 8.33: print('[警告] 当前发送速率已超出限制 8.33 封/分钟') key = input('[????] 是否确定发送?确定 (Y) / 取消 (N): ') - if key == 'y' or key == 'Y': + if key in ['Y', 'y']: print('[信息] 已确定发送') else: print('[信息] 已取消发送') @@ -159,76 +164,89 @@ def main(): global date date = datetime.now() - current = None - occurrence = 0 + index = 0 + active = True + occurrences = {} - for row in data: - recipient = row.get(args.column_address) - code = row.get(args.column_code) + while active and index < limit: + recipient = recipients[index] + code = codes[index] + mark = marks[index] + occurrence = occurrences.setdefault(code, [0]) + index += 1 - if code == current: - occurrence += 1 - else: - current = code - occurrence = 1 + if mark is not None: + print(f'[信息] 已跳过项目 {recipient}') + continue - if args.max_occurrence > 0 and occurrence > args.max_occurrence: + if args.max_occurrence > 0 and occurrence[0] > args.max_occurrence: print(f'[警告] 收件人 {recipient} 所属组织已重复出现 {occurrence} 次') global warnings warnings += 1 continue + while active: + try: + print(f'[信息] 正在发送:{recipient}') + click("button[aria-label='Edit copy']") + + # 等待页面加载 + wait = WebDriverWait(driver, timeout=args.timeout) + wait.until_not(lambda x: x.find_element(By.CSS_SELECTOR, ".io-ox-busy")) + + # 点击活动窗口 + click("div.io-ox-mail-compose-window div.floating-header") + + # 填入收件人 + wrapper = locate("div.io-ox-mail-compose-window div[data-extension-id='to'] > div.mail-input") + to = locate("input.token-input.tt-input[tabindex='0']", parent=wrapper) + click(wrapper) + to.send_keys(recipient) + + # 发送邮件 + click("div.io-ox-mail-compose-window button[data-action='send']") + + # 检测页面警告 + try: + wait = WebDriverWait(driver, timeout=args.interval) + alert = wait.until(lambda x: x.find_element(By.CSS_SELECTOR, "div.io-ox-alert.io-ox-alert-error")) + except: + marks[index] = '✔' + occurrence[0] += 1 + global sent + sent += 1 + break + + message = alert.text.replace('\n', ' ') + print(f'[警告] 来自网页:{message}') + + global errors + errors += 1 + + # 关闭警告 + click("div.io-ox-alert.io-ox-alert-error button[data-action='close']") + # 关闭过期邮件 + click("div.io-ox-mail-compose-window button[data-action='close']") + # 删除过期邮件 + click("div.modal-footer button[data-action='delete']") + except Exception as e: + print(f'[警告] 发生错误:{e}') + key = input('[????] 重试 (R) / 跳过 (S) / 取消 (C): ') + match key: + case 'R', 'r': continue + case 'S', 's': break + case _: active = False + + key = input('[????] 是否需要保存到文件?确定 (Y) / 取消 (N): ') + if key in ['Y', 'y']: + print(f'[信息] 正在写入文件:{args.input}') try: - print(f'[信息] 正在发送:{recipient}') - click("button[aria-label='Edit copy']") - - # 等待页面加载 - wait = WebDriverWait(driver, timeout=args.timeout) - wait.until_not(lambda x: x.find_element(By.CSS_SELECTOR, ".io-ox-busy")) - - # 点击活动窗口 - click("div.io-ox-mail-compose-window div.floating-header") - - # 填入收件人 - wrapper = locate("div.io-ox-mail-compose-window div[data-extension-id='to'] > div.mail-input") - to = locate("input.token-input.tt-input[tabindex='0']", parent=wrapper) - click(wrapper) - to.send_keys(recipient) - - # 发送邮件 - click("div.io-ox-mail-compose-window button[data-action='send']") - - global sent - sent += 1 + pandas.DataFrame.from_dict(data).to_excel(args.input, index=False, sheet_name='Sheet1') except Exception as e: - print(f'[警告] 发生错误:{e}') - key = input('[????] 是否继续?确定 (Y) / 取消 (N): ') + print(f'[警告] 写入文件时发生了错误:{e}') + return 6 - if key == 'y' or key == 'Y': continue - else: return 6 - - # 检测页面警告 - try: - wait = WebDriverWait(driver, timeout=args.interval) - alert = wait.until(lambda x: x.find_element(By.CSS_SELECTOR, "div.io-ox-alert.io-ox-alert-error")) - except: - continue - - message = alert.text.replace('\n', ' ') - print(f'[警告] 来自网页:{message}') - - global errors - errors += 1 - - # 发送失败不计入重复项 - if occurrence > 1: occurrence -= 1 - - # 关闭警告 - click("div.io-ox-alert.io-ox-alert-error button[data-action='close']") - # 关闭过期邮件 - click("div.io-ox-mail-compose-window button[data-action='close']") - # 删除过期邮件 - click("div.modal-footer button[data-action='delete']") + return 0 try: status = main() except KeyboardInterrupt: status = 145