updte: recipient name filling in

This commit is contained in:
2025-07-22 10:02:11 +08:00
parent 3fb56ec808
commit 833dcc493c

View File

@@ -1,9 +1,12 @@
import unicodedata
import argparse import argparse
import pandas import pandas
import time import time
from selenium import webdriver from selenium import webdriver
from selenium.common.exceptions import StaleElementReferenceException, TimeoutException from selenium.common.exceptions import StaleElementReferenceException, TimeoutException
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support.wait import WebDriverWait
@@ -15,6 +18,7 @@ from datetime import datetime, timedelta
parser = argparse.ArgumentParser(description="邮件批量发送脚本") 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-name', 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-sent', type=str, nargs='?', default='已发送') parser.add_argument('--column-sent', 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')
@@ -48,6 +52,7 @@ def main():
return 1 return 1
limit = len(recipients) limit = len(recipients)
names = data.setdefault(args.column_name, [None] * limit)
codes = data.setdefault(args.column_code, [None] * limit) codes = data.setdefault(args.column_code, [None] * limit)
sents = data.setdefault(args.column_sent, [None] * limit) sents = data.setdefault(args.column_sent, [None] * limit)
@@ -64,7 +69,7 @@ def main():
return 2 return 2
def locate(selector, condition=EC.presence_of_element_located, parent=driver) -> WebElement: def locate(selector, condition=EC.presence_of_element_located, parent=driver) -> WebElement:
while True: for attempt in range(args.retry):
try: try:
wait = WebDriverWait(parent, timeout=args.timeout) wait = WebDriverWait(parent, timeout=args.timeout)
return wait.until(condition((By.CSS_SELECTOR, selector))) return wait.until(condition((By.CSS_SELECTOR, selector)))
@@ -74,9 +79,10 @@ def main():
except TimeoutException: except TimeoutException:
# 超时错误 # 超时错误
raise Exception('操作超时') raise Exception('操作超时')
raise Exception(f'无法定位元素: {selector}')
def click(selector, condition=EC.element_to_be_clickable): def click(selector, condition=EC.element_to_be_clickable, parent=driver):
element = locate(selector, condition) if isinstance(selector, str) else selector element = locate(selector, condition, parent) if isinstance(selector, str) else selector
counter = lambda: int(element.get_attribute('taximeter') or 0) counter = lambda: int(element.get_attribute('taximeter') or 0)
error = False error = False
@@ -99,6 +105,26 @@ def main():
except TimeoutException: continue except TimeoutException: continue
except: break except: break
def keyin(element: WebElement, value):
try: element.send_keys(value)
except: driver.execute_script(f"arguments[0].value = arguments[1];", element, value)
def ready(driver, predicate):
try:
wait = WebDriverWait(driver, timeout=args.timeout)
wait.until(predicate, '操作超时')
except:
return True
wait = WebDriverWait(driver, timeout=args.timeout)
wait.until_not(predicate, '操作超时')
return True
def contains_non_latin_alphabet(string: str):
for char in string:
if unicodedata.category(char).startswith('L') and not unicodedata.name(char, '').startswith('LATIN'):
return True
return False
try: try:
driver.get(args.url) driver.get(args.url)
except TimeoutException: except TimeoutException:
@@ -169,21 +195,24 @@ def main():
occurrences = {} occurrences = {}
while active and index < limit: while active and index < limit:
recipient = recipients[index] current = index
code = codes[index] index += 1
recipient = recipients[current]
name = names[current]
code = codes[current]
mark = sents[current]
if not code: occurrence = [0] if not code: occurrence = [0]
else: occurrence = occurrences.setdefault(code, [0]) else: occurrence = occurrences.setdefault(code, [0])
current = index if mark is not None and str(mark).strip():
index += 1
if sents[current] is not None:
print(f'[信息] 已跳过项目 {recipient}') print(f'[信息] 已跳过项目 {recipient}')
occurrence[0] += 1
continue continue
if args.max_occurrence > 0 and occurrence[0] > 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
@@ -194,15 +223,23 @@ def main():
click("button[aria-label='Edit copy']") click("button[aria-label='Edit copy']")
# 等待页面加载 # 等待页面加载
wait = WebDriverWait(driver, timeout=args.timeout) ready(driver, lambda x: x.find_element(By.CSS_SELECTOR, ".io-ox-busy"))
wait.until_not(lambda x: x.find_element(By.CSS_SELECTOR, ".io-ox-busy"))
# 填入收件人
wrapper = locate("div.io-ox-mail-compose-window div[data-extension-id='to'] > div.mail-input") 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) to = locate("input.token-input.tt-input[tabindex='0']", parent=wrapper)
click(wrapper)
to.send_keys(recipient)
if name is not None and (string := str(name).strip()) and not contains_non_latin_alphabet(string):
# 填入客户称呼
firstname = string.split()[0].title()
action = ActionChains(driver).send_keys(Keys.END)
for attempt in range(args.retry): action.perform()
ActionChains(driver, 5000).key_down(Keys.SHIFT).send_keys(Keys.LEFT).key_up(Keys.SHIFT).key_down(Keys.CONTROL).send_keys('x').key_up(Keys.CONTROL).perform()
ActionChains(driver, 5000).send_keys(Keys.SPACE).send_keys(firstname).key_down(Keys.CONTROL).send_keys('v').key_up(Keys.CONTROL).perform()
# 填入收件人
click(wrapper)
keyin(to, recipient)
# 发送邮件 # 发送邮件
click("div.io-ox-mail-compose-window button[data-action='send']") click("div.io-ox-mail-compose-window button[data-action='send']")
@@ -225,22 +262,24 @@ def main():
# 关闭警告 # 关闭警告
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']") while mails := driver.find_elements(By.CSS_SELECTOR, "div.io-ox-mail-compose-window"):
# 删除过期邮件 # 关闭过期邮件
click("div.modal-footer button[data-action='delete']") click("button[data-action='close']", parent=mails[0])
# 删除过期邮件
click("div.modal-footer button[data-action='delete']")
break break
except KeyboardInterrupt: except KeyboardInterrupt:
print('[信息] 程序中断') print('[信息] 程序中断')
except Exception as e: except Exception as e:
print(f'[警告] 发生错误:{e}') print(f'[警告] 发生错误:{e}')
key = input('[????] 重试 (R) / 跳过 (S) / 取消 (C): ') key = input('[????] 重试 (r) / 跳过 (s) / 取消 (C): ')
if key in ['R', 'r']: continue if key in ['R', 'r']: continue
elif key in ['S', 's']: break elif key in ['S', 's']: break
else: active = False else: active = False
if input('[????] 是否保存到文件?确定 (Y) / 取消 (N): ') in ['Y', 'y']: if input('[????] 是否保存到文件?确定 (y) / 取消 (N): ') in ['Y', 'y']:
print(f'[信息] 正在写入文件:{args.input}') print(f'[信息] 正在写入文件:{args.input}')
try: try:
pandas.DataFrame.from_dict(data).to_excel(args.input, index=False, sheet_name='Sheet1') pandas.DataFrame.from_dict(data).to_excel(args.input, index=False, sheet_name='Sheet1')