updte: recipient name filling in
This commit is contained in:
83
邮件批量发送脚本.py
83
邮件批量发送脚本.py
@@ -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')
|
||||||
|
|||||||
Reference in New Issue
Block a user