update: pivot to pandas from csv

This commit is contained in:
2025-07-08 15:39:45 +08:00
parent 224932cec9
commit 0cd29e3584
2 changed files with 86 additions and 68 deletions

Binary file not shown.

View File

@@ -1,6 +1,7 @@
import argparse import argparse
import pandas
import time import time
import csv import itertools
from selenium import webdriver from selenium import webdriver
from selenium.common.exceptions import StaleElementReferenceException, TimeoutException from selenium.common.exceptions import StaleElementReferenceException, TimeoutException
@@ -15,10 +16,10 @@ parser = argparse.ArgumentParser(description="邮件批量发送脚本")
parser.add_argument('input', nargs='?') parser.add_argument('input', nargs='?')
parser.add_argument('--column-address', type=str, nargs='?', default='邮箱') parser.add_argument('--column-address', type=str, nargs='?', default='邮箱')
parser.add_argument('--column-code', 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('-u', '--url', type=str, nargs='?', default='https://id.ionos.fr/identifier')
parser.add_argument('-a', '--address', type=str, required=True) parser.add_argument('-a', '--address', type=str, required=True)
parser.add_argument('-p', '--password', 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('-t', '--timeout', type=int, nargs='?', default=60)
parser.add_argument('-i', '--interval', type=int, nargs='?', default=10) parser.add_argument('-i', '--interval', type=int, nargs='?', default=10)
parser.add_argument('-m', '--max-occurrence', type=int, nargs='?', default=5) parser.add_argument('-m', '--max-occurrence', type=int, nargs='?', default=5)
@@ -35,20 +36,23 @@ def main():
if not args.input: if not args.input:
root = Tk() root = Tk()
root.withdraw() root.withdraw()
args.input = filedialog.askopenfilename(defaultextension='csv') args.input = filedialog.askopenfilename(defaultextension='xlsx')
print(f'[信息] 正在读取数据:{args.input}') print(f'[信息] 正在读取数据:{args.input}')
try: try:
with open(args.input, 'r', encoding=args.encoding) as file: workbook = pandas.read_excel(args.input)
rows = csv.DictReader(file) data = workbook.where(pandas.notnull(workbook), None).to_dict(orient='list')
data = list(rows) recipients = data.get(args.column_address, [])
codes = data.get(args.column_code, [])
except Exception as e: except Exception as e:
print(f'[!!!!] 读取数据表失败:{e}') print(f'[!!!!] 读取数据表失败:{e}')
return 1 return 1
rate = 60 / (args.interval + 3) limit = len(recipients)
limit = len(data) marks = data.setdefault(args.column_mark, [None] * limit)
print(f'[信息] 已读取联系人信息共 {limit}') print(f'[信息] 已读取联系人信息共 {limit}')
if limit == 0: return 0
print('[信息] 正在启动 Chrome 自动化实例') print('[信息] 正在启动 Chrome 自动化实例')
try: try:
@@ -144,13 +148,14 @@ def main():
print(f'[!!!!] 读取邮件时发生了错误:{e}') print(f'[!!!!] 读取邮件时发生了错误:{e}')
return 5 return 5
rate = 60 / (args.interval + 3)
print(f'[信息] 当前发送速率 {round(rate, 2)} 封/分钟') print(f'[信息] 当前发送速率 {round(rate, 2)} 封/分钟')
print(f'[信息] 预计使用时间 {timedelta(minutes=limit / rate)}') print(f'[信息] 预计使用时间 {timedelta(minutes=limit / rate)}')
if rate > 8.33: print('[警告] 当前发送速率已超出限制 8.33 封/分钟') if rate > 8.33: print('[警告] 当前发送速率已超出限制 8.33 封/分钟')
key = input('[????] 是否确定发送?确定 (Y) / 取消 (N): ') key = input('[????] 是否确定发送?确定 (Y) / 取消 (N): ')
if key == 'y' or key == 'Y': if key in ['Y', 'y']:
print('[信息] 已确定发送') print('[信息] 已确定发送')
else: else:
print('[信息] 已取消发送') print('[信息] 已取消发送')
@@ -159,25 +164,28 @@ def main():
global date global date
date = datetime.now() date = datetime.now()
current = None index = 0
occurrence = 0 active = True
occurrences = {}
for row in data: while active and index < limit:
recipient = row.get(args.column_address) recipient = recipients[index]
code = row.get(args.column_code) code = codes[index]
mark = marks[index]
occurrence = occurrences.setdefault(code, [0])
index += 1
if code == current: if mark is not None:
occurrence += 1 print(f'[信息] 已跳过项目 {recipient}')
else: continue
current = code
occurrence = 1
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}') print(f'[警告] 收件人 {recipient} 所属组织已重复出现 {occurrence}')
global warnings global warnings
warnings += 1 warnings += 1
continue continue
while active:
try: try:
print(f'[信息] 正在发送:{recipient}') print(f'[信息] 正在发送:{recipient}')
click("button[aria-label='Edit copy']") click("button[aria-label='Edit copy']")
@@ -198,21 +206,16 @@ def main():
# 发送邮件 # 发送邮件
click("div.io-ox-mail-compose-window button[data-action='send']") click("div.io-ox-mail-compose-window button[data-action='send']")
global sent
sent += 1
except Exception as e:
print(f'[警告] 发生错误:{e}')
key = input('[????] 是否继续?确定 (Y) / 取消 (N): ')
if key == 'y' or key == 'Y': continue
else: return 6
# 检测页面警告 # 检测页面警告
try: try:
wait = WebDriverWait(driver, timeout=args.interval) 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")) alert = wait.until(lambda x: x.find_element(By.CSS_SELECTOR, "div.io-ox-alert.io-ox-alert-error"))
except: except:
continue marks[index] = ''
occurrence[0] += 1
global sent
sent += 1
break
message = alert.text.replace('\n', ' ') message = alert.text.replace('\n', ' ')
print(f'[警告] 来自网页:{message}') print(f'[警告] 来自网页:{message}')
@@ -220,15 +223,30 @@ def main():
global errors global errors
errors += 1 errors += 1
# 发送失败不计入重复项
if occurrence > 1: occurrence -= 1
# 关闭警告 # 关闭警告
click("div.io-ox-alert.io-ox-alert-error button[data-action='close']") 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.io-ox-mail-compose-window button[data-action='close']")
# 删除过期邮件 # 删除过期邮件
click("div.modal-footer button[data-action='delete']") 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:
pandas.DataFrame.from_dict(data).to_excel(args.input, index=False, sheet_name='Sheet1')
except Exception as e:
print(f'[警告] 写入文件时发生了错误:{e}')
return 6
return 0
try: status = main() try: status = main()
except KeyboardInterrupt: status = 145 except KeyboardInterrupt: status = 145