网易企业邮箱批量修改设置
概述
使用 Selenium 4 自动化批量登录网易企业邮箱,修改客户端设置中的 SMTP 收件范围(改为"收取全部邮件")和客户端授权密码开关。本技能记录了从环境配置到脚本执行的全流程,以及开发过程中遇到的关键坑点和解决方案。
经过实战优化(284 个账号批量处理),脚本已具备:浏览器崩溃自动重启、窗口堆积防护、定期内存释放、失败原因追踪、快速处理(单账号约 5-6 秒)等能力。
前置要求(必须先完成)
使用本技能前,必须先让用户提供账号 txt 文件。 没有账号文件脚本无法运行。
操作步骤
-
向用户索要账号文件,告知格式要求:
- 每行一个账号,格式为用户名,密码
- 支持英文逗号,和中文逗号,
- 空行和以#开头的行会被跳过
- 示例格式:
t@qiyevip163.com,Qazwsx@12 user2@qiyevip163.com,Password123 -
用户可以通过以下方式提供:
- 直接在对话中发送 txt 文件
- 把文件内容粘贴到对话中(帮其保存为 txt)
- 告知文件已在本地某个路径 -
收到账号文件后,将其保存到工作目录下,默认文件名为
1.txt -
确认环境就绪后再执行脚本(见下方"环境配置"和"运行命令"部分)
注意事项
- 账号文件包含敏感信息(密码),不要将其内容输出到对话中
- 如果用户只提供了文件内容而没有文件,主动帮其保存为
1.txt - 如果用户提供的格式有误(如缺少密码),提醒用户修正后再运行
适用场景
- 批量修改多个网易企业邮箱账号的客户端设置
- 需要将 IMAP/SMTP 和 POP/SMTP 的收取范围改为"收取全部邮件"
- 需要关闭客户端授权密码
- 需要记录失败账号及失败原因
- 大批量(100+)账号处理,需要防崩溃和自动恢复能力
环境配置
Python venv 隔离环境
# 使用 managed Python 创建 venv
"C:\Users\huang\.workbuddy\binaries\python\versions\3.13.12\python.exe" -m venv "C:\Users\huang\.workbuddy\binaries\python\envs\default"
# 安装 selenium
"C:\Users\huang\.workbuddy\binaries\python\envs\default\Scripts\pip.exe" install selenium
ChromeDriver 本地配置
Selenium 4 内置的在线下载 ChromeDriver 在国内网络环境下经常失败。解决方案:
- 从 npmmirror CDN 手动下载对应版本的 ChromeDriver
- 放到脚本同目录的
chromedriver/chromedriver-win64/下 - 脚本中通过
Service(executable_path=...)指定本地路径
chromedriver_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"chromedriver", "chromedriver-win64", "chromedriver.exe",
)
if os.path.exists(chromedriver_path):
service = Service(executable_path=chromedriver_path)
driver = webdriver.Chrome(service=service, options=options)
else:
driver = webdriver.Chrome(options=options) # 回退到自动查找
账号文件格式
1.txt,每行一个账号,格式 用户名,密码(支持中文逗号):
t@qiyevip163.com,Qazwsx@12
user2@qiyevip163.com,Password123
核心技术要点
1. Selenium 4 API 迁移
旧版 find_element_by_xpath 已废弃,必须使用新 API:
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# 等待元素可点击并点击
el = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.XPATH, "//span[text()='设置']")))
el.click()
# 安全查找(找不到返回 None)
try:
return WebDriverWait(driver, timeout).until(EC.presence_of_element_located(locator))
except (TimeoutException, NoSuchElementException):
return None
2. 网易邮箱页面结构适配
网易企业邮箱页面有以下关键特征:
- 登录:需先点"密码登录"tab,勾选许可协议,再输入
#accname和#accpwd - 设置入口:点击工具栏"设置" → 找到"客户端设置"旁边的"进入设置"链接
- 新窗口:点击"进入设置"后会打开新窗口,必须
switch_to.window(handles[-1]) - iframe:设置页面通常在主文档中,不需要切换 iframe(但需
switch_to.default_content()确保在主文档) - 下拉框:页面使用原生
<select>元素,但被样式化包裹
3. 原生 select 操作触发 change 事件(关键)
直接点击样式化下拉框的选项无法触发网易页面的保存逻辑。必须操作底层原生 <select> 元素并派发 change 事件:
select_script = """
var section = arguments[0];
var select = section.querySelector('select');
if (!select) {
var parent = section.parentElement;
for (var i=0; i<5 && parent; i++) {
select = parent.querySelector('select');
if (select) break;
parent = parent.parentElement;
}
}
if (select) {
for (var i=0; i<select.options.length; i++) {
if (select.options[i].text.indexOf('全部') !== -1) {
select.selectedIndex = i;
var evt = document.createEvent('HTMLEvents');
evt.initEvent('change', true, false);
select.dispatchEvent(evt);
return 'select:' + select.options[i].text;
}
}
}
return null;
"""
result = driver.execute_script(select_script, section)
4. 原生 alert 自动确认(关键)
修改 select 值并触发 change 事件后,网易页面会弹出原生 JavaScript alert("邮件客户端收取时间设置成功")。必须等待 alert 出现再 accept:
def _accept_modal(driver, timeout=3):
# 1. 等待原生 alert 出现(不能用 try/except 立即检测,alert 可能还没弹出)
try:
WebDriverWait(driver, timeout).until(EC.alert_is_present())
alert = driver.switch_to.alert
text = alert.text
alert.accept()
return text
except Exception:
pass
# 2. 兜底:尝试自定义 HTML 弹窗的"确定"按钮
try:
ok_btn = WebDriverWait(driver, 2).until(
EC.element_to_be_clickable((By.XPATH, "//button[contains(text(),'确定')]"))
)
driver.execute_script("arguments[0].click();", ok_btn)
return "自定义弹窗-确定"
except Exception:
pass
return None
常见坑:之前用 try/except 立即检测 alert,因为 alert 还没弹出来就跳过了,导致修改实际没生效。必须用 WebDriverWait(driver, timeout).until(EC.alert_is_present()) 阻塞等待。
优化点:超时从 10s 降到 3s,正常有弹窗时 0.5s 内就能检测到,无弹窗时少等 7s。
5. 失败原因追踪与日志记录
核心函数返回 tuple[bool, str],第二个元素为失败原因:
def login(driver, account) -> tuple[bool, str]:
...
return False, "登录超时,可能密码错误或遇到验证码"
def process_account(driver, account) -> tuple[bool, str]:
try:
ok, reason = login(driver, account)
if not ok:
return False, reason
...
except WebDriverException as e:
return False, f"浏览器异常: {type(e).__name__}: {str(e)[:100]}"
运行结束后,失败账号及原因写入 failed_accounts.log:
@dataclass
class RunStats:
failed_details: list = field(default_factory=list) # [(账号, 原因), ...]
def write_failure_log(self, file_path):
if not self.failed_details:
return
lines = [f"# 失败日志 — 生成时间 {timestamp}", f"# 共 {self.failed} 个账号失败", ""]
for i, (username, reason) in enumerate(self.failed_details, 1):
lines.append(f"{i}. 账号: {username}")
lines.append(f" 原因: {reason}")
lines.append("")
Path(file_path).write_text("\n".join(lines), encoding="utf-8")
6. 浏览器窗口管理与防崩溃(关键优化)
问题:每个账号点"进入设置"会开新窗口,旧窗口从不关闭,处理几十个账号后浏览器内存爆炸崩溃(284 个账号跑到第 89 个就崩了)。
解决方案:
a. 每个账号处理完后关闭多余窗口
def _close_extra_windows(driver):
"""关闭除主窗口外的所有窗口,防止窗口堆积导致内存溢出"""
handles = driver.window_handles
if len(handles) <= 1:
return
main_handle = handles[0]
for h in handles[1:]:
try:
driver.switch_to.window(h)
driver.close()
except Exception:
pass
driver.switch_to.window(main_handle)
b. 开新窗口后立即关旧窗口
# open_settings() 中:切换到新窗口后,关闭旧窗口释放内存
if len(handles) > 1:
old_handle = handles[0]
new_handle = handles[-1]
driver.switch_to.window(new_handle)
try:
driver.switch_to.window(old_handle)
driver.close()
driver.switch_to.window(new_handle)
except Exception:
pass
c. 定期重建浏览器(每 30 个账号)
防止长时间运行导致内存泄漏累积:
BROWSER_REFRESH_INTERVAL = 30
if i > 1 and (i - 1) % BROWSER_REFRESH_INTERVAL == 0:
_safe_quit_driver(driver)
driver = create_driver(headless=args.headless)
restart_count = 0 # 重置计数器
d. 浏览器崩溃自动重启
def _is_driver_alive(driver) -> bool:
try:
_ = driver.current_url
return True
except (InvalidSessionIdException, WebDriverException):
return False
# 主循环中:处理前检查浏览器是否存活
if not _is_driver_alive(driver):
if restart_count < MAX_BROWSER_RESTARTS: # 上限 10 次
_safe_quit_driver(driver)
driver = create_driver(headless=args.headless)
restart_count += 1
else:
# 剩余账号全部标记失败
for remaining in accounts[i - 1:]:
stats.record_failure(remaining.username, "浏览器崩溃且重启次数已达上限")
break
e. Chrome 稳定性参数
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--disable-gpu")
options.add_argument("--disable-extensions")
options.add_argument("--disable-plugins")
options.add_argument("--disable-images") # 不加载图片,省带宽和内存
options.add_argument("--disable-notifications")
options.add_argument("--mute-audio")
options.add_argument("--blink-settings=imagesEnabled=false")
options.add_argument("--memory-pressure-off")
options.add_argument("--max-old-space-size=256")
driver.set_page_load_timeout(20) # 页面加载超时 20s
7. 批量处理速度优化
| 优化项 | 优化前 | 优化后 | 节省 |
|---|---|---|---|
_accept_modal 超时 |
10s | 3s | ~7s/次 |
_select_dropdown_option 内部 sleep |
3.5s | 0.6s | ~3s |
open_settings sleep |
4s | 1.5s | ~2.5s |
enter_mailbox sleep |
2s | 0.5s | ~1.5s |
login 固定 sleep |
2.5s | 0s(用 WebDriverWait) | ~2.5s |
| 截图 | 每步都拍 | 仅 DEBUG 模式 | ~1s/步 |
implicitly_wait |
3s | 2s | 全局少等 |
| 单账号总耗时 | ~12-15s | ~5-6s | 提速 50%+ |
截图优化:正常流程跳过截图,仅在 SELENIUM_DEBUG 环境变量存在时才拍:
def _screenshot(driver, account, tag):
if not os.environ.get("SELENIUM_DEBUG"):
return
# ... 仅 DEBUG 模式下截图
完整流程
0. 【前置】向用户索要账号 txt 文件,保存到工作目录为 1.txt
1. load_accounts() — 从 1.txt 读取账号列表
2. create_driver() — 创建 Chrome 实例(含稳定性参数 + 本地 chromedriver)
3. 对每个账号:
a. 每 30 个账号定期重建浏览器(防内存泄漏)
b. _is_driver_alive() — 检查浏览器是否存活,崩了就重启
c. login() — 登录网易企业邮箱
d. enter_mailbox() — 进入邮箱主界面
e. open_settings() — 打开设置 → 客户端设置(新窗口,关旧窗口)
f. modify_settings():
- _select_dropdown_option("IMAP / SMTP") → 改为"收取全部邮件" + 确认弹窗
- _select_dropdown_option("POP / SMTP") → 改为"收取全部邮件" + 确认弹窗
- 检查并关闭客户端授权密码
g. _close_extra_windows() — 关闭多余窗口
h. driver.delete_all_cookies() — 清理状态
4. driver.quit()
5. 输出统计摘要 + 写入 failed_accounts.log
运行命令
# 基本运行(使用默认 1.txt)
"C:\Users\huang\.workbuddy\binaries\python\envs\default\Scripts\python.exe" "py-网易邮箱批量修改设置脚本.py"
# 指定账号文件
"C:\Users\huang\.workbuddy\binaries\python\envs\default\Scripts\python.exe" "py-网易邮箱批量修改设置脚本.py" --file accounts.txt
# 无头模式
"C:\Users\huang\.workbuddy\binaries\python\envs\default\Scripts\python.exe" "py-网易邮箱批量修改设置脚本.py" --headless
# 重跑失败账号(从 failed_accounts.log 提取后生成 retry.txt)
"C:\Users\huang\.workbuddy\binaries\python\envs\default\Scripts\python.exe" "py-网易邮箱批量修改设置脚本.py" --file retry.txt
失败账号重跑流程
大批量运行后,从 failed_accounts.log 提取失败账号生成重跑文件:
import re
# 读取失败账号
with open('failed_accounts.log', encoding='utf-8') as f:
failed_text = f.read()
failed_emails = re.findall(r'账号: (.+)', failed_text)
# 读取原始账号文件,构建邮箱→密码映射
with open('1.txt', encoding='utf-8') as f:
lines = f.readlines()
account_map = {}
for line in lines:
line = line.strip().replace(',', ',')
parts = line.split(',')
if len(parts) >= 2:
account_map[parts[0].strip()] = parts[1].strip()
# 生成重跑文件
with open('retry.txt', 'w', encoding='utf-8') as f:
for email in failed_emails:
if email.strip() in account_map:
f.write(f"{email.strip()},{account_map[email.strip()]}\n")
然后用 --file retry.txt 重跑。
踩坑记录
| 问题 | 原因 | 解决方案 |
|---|---|---|
| Selenium 内置 chromedriver 下载失败 | 国内网络无法访问 googlechromelabs | 从 npmmirror CDN 手动下载,指定本地路径 |
find_element_by_xpath 报错 |
Selenium 4 废弃旧 API | 改用 find_element(By.XPATH, ...) |
| 登录后找不到设置按钮 | 页面结构变化 | 改用文本匹配定位 + JavaScript 点击 |
| 点击"进入设置"后找不到元素 | 新窗口未切换 | switch_to.window(handles[-1]) |
| 下拉框点击展开后选项找不到 | chromedriver 对复杂 XPath 崩溃 | 改用 document.querySelectorAll |
| JavaScript 点击文本节点不触发选择 | 未操作底层 select | 直接修改 select.selectedIndex + 派发 change 事件 |
| 修改后弹窗未确认 | try/except 检测 alert 太快 |
WebDriverWait().until(EC.alert_is_present()) 阻塞等待 |
| 大批量运行浏览器崩溃 | 窗口堆积导致内存溢出 | 每个账号处理完关闭多余窗口 + 定期重建浏览器 |
| 崩溃后后续账号全部失败 | session 失效后未重建浏览器 | _is_driver_alive() 检测 + 自动重启(上限 10 次) |
| 单账号处理太慢 | 大量固定 sleep + 超长 alert 等待 | sleep 从 10s 降到 3s,用 WebDriverWait 替代固定 sleep |
资源文件
scripts/
netease_mail_batch.py— 完整的可运行脚本(优化版),可直接复制使用或作为基础进行修改
references/
page_structure.md— 网易企业邮箱页面结构参考文档,记录各关键元素的定位方式