Selenium自动化登录12306实战:Python+Edge完整解决方案
1. 项目概述与核心价值
最近在技术社区里,看到不少朋友在讨论用Python做自动化操作,尤其是针对一些需要登录的网站。其中,12306这个“国民级”购票网站,因其复杂的验证码和登录流程,常常被当作一个颇具挑战性的实战目标。今天,我就来分享一个基于Selenium和Edge浏览器的自动化登录12306的完整方案。这不是一个简单的脚本堆砌,而是融合了我在实际项目中处理动态页面、反爬策略和浏览器驱动管理的心得。整个过程,从环境搭建到成功登录,核心代码部分确实能在5分钟左右跑通,但更重要的是理解每一步背后的逻辑和可能遇到的“坑”。
这个实战项目适合谁呢?如果你是刚接触Selenium,想找一个有代表性的综合案例来练手;或者你正在为某个需要模拟登录的爬虫或自动化测试任务发愁,那么这篇内容会非常对路。我们将使用Python语言,搭配微软的Edge浏览器,因为它与Windows系统集成度更高,驱动获取相对稳定。整个流程会覆盖:环境准备(Python、Selenium库、Edge驱动)、页面元素定位策略、验证码处理思路(重点讨论)、登录逻辑实现,以及如何让脚本运行得更稳健。我会把完整的、可运行的代码附在最后,你可以直接复制到本地进行尝试和修改。
2. 环境准备与核心工具解析
工欲善其事,必先利其器。在开始写代码之前,我们需要把“战场”布置好。这里的选择背后都有具体的考量,我会一一解释为什么这么做。
2.1 Python与Selenium库安装
首先,确保你的电脑上安装了Python。我推荐使用Python 3.7及以上版本,因为其对新的语言特性和库的支持更好。安装过程很简单,从官网下载安装包,记得勾选“Add Python to PATH”选项,这样可以在命令行中直接使用 python 和 pip 命令。
接下来是核心——Selenium库。Selenium本质上是一个用于Web应用程序测试的工具,但它强大的浏览器操控能力使其成为了自动化操作和爬虫的利器。它通过一套WebDriver协议与真实的浏览器进行通信,从而实现点击、输入、获取数据等操作。在命令行中,使用pip安装它:
pip install selenium
这里有个注意事项:网络环境可能会导致pip安装缓慢或失败。你可以考虑使用国内的镜像源来加速,例如清华源或阿里云源。命令如下:
pip install selenium -i https://pypi.tuna.tsinghua.edu.cn/simple
安装完成后,可以在Python交互环境中输入 import selenium 来验证是否成功。
2.2 Edge浏览器与WebDriver驱动匹配
我们选择Edge浏览器而非更常见的Chrome,主要出于两点考虑:一是Edge基于Chromium内核,兼容性和性能都很好,且是Windows系统原生浏览器,稳定性有保障;二是其WebDriver由微软官方维护,下载和匹配相对清晰,避免了Chrome浏览器与驱动版本严格对应带来的麻烦。
驱动的下载与放置是关键一步,也是新手最容易出错的地方。
-
查看你的Edge浏览器版本 :打开Edge浏览器,点击右上角三个点(…)->“帮助和反馈”->“关于Microsoft Edge”。记下版本号,例如“版本 121.0.2277.128”。
-
下载对应版本的MS Edge WebDriver :访问微软官方的Edge WebDriver下载页面。你需要下载与你的Edge浏览器 主版本号 完全一致的驱动。例如,Edge是121版,就下载
edgedriver_121开头的文件。下载时选择与你的操作系统(Windows 64位)匹配的压缩包。 -
驱动的放置 :下载的是一个名为
msedgedriver.exe的可执行文件。你有三种处理方式:- 方式一(推荐,便于管理) :将其放置在一个你专门存放工具的目录下,例如
D:\WebDrivers\。然后,将这个目录的路径(D:\WebDrivers)添加到系统的环境变量Path中。这样,Python的Selenium就能在任何位置找到它。 - 方式二(项目专用) :直接放在你的Python项目文件夹根目录下。在代码中指定驱动路径时,使用相对路径
./msedgedriver.exe即可。 - 方式三(临时指定) :在代码中通过
executable_path参数指定绝对路径。
- 方式一(推荐,便于管理) :将其放置在一个你专门存放工具的目录下,例如
注意 :驱动与浏览器版本不匹配是导致
SessionNotCreatedException错误的常见原因。如果遇到此类错误,首先检查版本号。另外,确保下载的驱动没有被Windows Defender或其他安全软件误删,必要时需添加信任。
2.3 辅助工具与思路准备
除了核心的Selenium,我们还需要一点额外的准备来应对12306的登录页面。
- 开发者工具(F12) :这是我们的“眼睛”。我们将频繁使用它来查看页面HTML结构,定位用户名、密码输入框以及登录按钮的元素信息(如ID、Class Name、XPath等)。熟练使用开发者工具的“检查”功能,是做好Web自动化的基础。
- 关于验证码的思考 :12306登录时的验证码是动态生成的,通常为点击图中特定物品的类型。完全自动化解码涉及图像识别,复杂度高且可能触及合规边界。在本实战中,我们的策略是: 脚本运行后,会暂停并等待用户手动识别并点击验证码,然后再继续执行后续的自动输入和登录 。这是一种“半自动化”的务实方案,既绕开了复杂的技术难题,也保证了操作的合法性。脚本会负责打开浏览器、跳转到登录页、填充账号密码,然后等待你手动完成验证码操作。
3. 核心代码实现与分步详解
环境就绪后,我们进入核心的代码编写环节。我会将完整脚本拆解成几个逻辑模块,并详细解释每一行代码的作用和背后的考量。
3.1 脚本初始化与浏览器启动
首先,我们导入必要的模块,并设置浏览器启动选项。
from selenium import webdriver
from selenium.webdriver.edge.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
# 1. 设置Edge浏览器选项
edge_options = webdriver.EdgeOptions()
# 避免一些自动化特征被检测,可添加如下参数(非万能,但有益)
edge_options.add_argument('--disable-blink-features=AutomationControlled')
edge_options.add_experimental_option('excludeSwitches', ['enable-automation'])
edge_options.add_experimental_option('useAutomationExtension', False)
# 2. 指定WebDriver路径(如果已加入PATH,则不需要此行)
# driver_path = r'D:\WebDrivers\msedgedriver.exe'
# service = Service(executable_path=driver_path)
# 3. 启动浏览器
# 如果驱动已在PATH中,使用以下方式:
driver = webdriver.Edge(options=edge_options)
# 如果指定了路径,使用以下方式:
# driver = webdriver.Edge(service=service, options=edge_options)
# 最大化窗口,确保元素可见
driver.maximize_window()
代码解析 :
webdriver:核心模块。Service:用于管理WebDriver服务,如果你将驱动放在了自定义路径,需要通过它来指定。By:提供定位元素的方法(如By.ID,By.CLASS_NAME)。WebDriverWait和EC:用于实现“显式等待”,这是编写健壮Selenium脚本的关键,后面会详细讲。edge_options:这里我们添加了几个参数来尝试降低被网站检测为自动化的风险。--disable-blink-features=AutomationControlled是较新的一个设置,有助于隐藏WebDriver特征。excludeSwitches和useAutomationExtension是传统的规避检测方法。请注意,这些方法并非绝对有效,网站的反爬策略也在升级。- 启动浏览器时,优先推荐将驱动加入系统PATH,这样代码更简洁,也便于跨项目共享。
3.2 页面访问与元素定位策略
接下来,我们让浏览器打开12306的登录页面,并找到我们需要操作的元素。
# 4. 访问12306登录页面
login_url = 'https://kyfw.12306.cn/otn/resources/login.html'
driver.get(login_url)
print("已打开12306登录页面,请稍候...")
time.sleep(2) # 简单等待页面加载,后续会被更优的显式等待替代
# 5. 切换到“账号登录”选项卡(12306默认可能是扫码登录)
# 首先尝试定位并点击“账号登录”标签
try:
account_login_tab = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.LINK_TEXT, "账号登录"))
)
account_login_tab.click()
print("已切换到账号登录模式。")
except Exception as e:
print(f"未找到或无法点击‘账号登录’选项卡,可能页面已变更。错误:{e}")
# 这里可以选择继续或退出,我们假设页面已经是账号登录形式
pass
# 6. 定位用户名、密码输入框和登录按钮
# 使用显式等待,确保元素加载完成再操作,这是避免`NoSuchElementException`的最佳实践
wait = WebDriverWait(driver, 10) # 创建等待对象,最多等10秒
try:
# 定位用户名输入框 - 通过ID定位,通常最稳定
username_input = wait.until(
EC.presence_of_element_located((By.ID, "J-userName"))
)
# 定位密码输入框
password_input = driver.find_element(By.ID, "J-password")
# 定位登录按钮
login_button = driver.find_element(By.ID, "J-login")
print("成功定位登录表单元素。")
except Exception as e:
print(f"定位登录表单元素失败!请检查页面结构或网络。错误:{e}")
driver.quit()
exit()
核心技巧与避坑指南 :
- 显式等待 vs 隐式等待 vs
time.sleep:time.sleep(2)是“强制等待”,简单粗暴但效率低下,不推荐在正式脚本中大量使用。- 隐式等待
driver.implicitly_wait(10)设置一次,对后续所有find_element操作生效,但不够灵活。 - 显式等待
WebDriverWait是最佳实践 。它针对某个特定条件(如元素可点击、元素出现)进行等待,条件满足立即继续,最多等待指定时间。这能最大程度保证脚本的稳定性和执行速度。我们这里用EC.presence_of_element_located等待用户名输入框出现。
- 元素定位器选择优先级 :
- ID > Name > Class Name > CSS Selector > XPath 。ID通常是唯一且最稳定的。12306登录框的ID(如
J-userName)比较固定,因此我们首选ID定位。 - 使用开发者工具检查时,要确认元素的ID是唯一的,并且不是动态生成的(即每次刷新页面ID不变)。
- ID > Name > Class Name > CSS Selector > XPath 。ID通常是唯一且最稳定的。12306登录框的ID(如
- 异常处理 :使用
try...except包裹可能失败的操作(如元素定位),并给出友好的错误提示,而不是让脚本直接崩溃。这有助于调试。
3.3 填写凭证与处理验证码
这是整个流程中最需要人工干预的部分。
# 7. 填写用户名和密码(请替换为你的测试账号)
# 警告:切勿将真实账号密码直接硬编码在脚本中!此处仅为演示。
test_username = "your_username_here"
test_password = "your_password_here"
username_input.clear() # 清空可能存在的默认值
username_input.send_keys(test_username)
print(f"已输入用户名:{test_username}")
password_input.clear()
password_input.send_keys(test_password)
print("已输入密码。")
# 8. 验证码处理 - 手动干预点
print("-" * 50)
print("【重要提示】脚本已自动填充账号密码。")
print("请现在浏览器窗口中,手动完成验证码识别并点击。")
print("完成验证码操作后,请回到此控制台窗口,按回车键继续...")
print("-" * 50)
# 脚本暂停,等待用户手动操作验证码并按回车
input("等待手动验证码完成,按回车键继续执行自动登录...")
# 9. 点击登录按钮
try:
# 再次等待登录按钮可点击,因为填写验证码后页面状态可能变化
login_button = wait.until(
EC.element_to_be_clickable((By.ID, "J-login"))
)
login_button.click()
print("已点击登录按钮。")
except Exception as e:
print(f"点击登录按钮失败!错误:{e}")
实操心得 :
- 安全警告 :像上面这样把账号密码明文写在代码里是极不安全的,尤其是如果你打算共享或上传代码。在实际项目中,应该通过环境变量、配置文件(如
.ini,.yaml)或密钥管理服务来存储敏感信息。例如,可以使用os.environ.get('USERNAME')从系统环境变量读取。 - 验证码策略 :
input()函数会让Python脚本暂停,等待用户在控制台输入。这为我们创造了手动操作浏览器的时间窗口。这是一种简单有效的“人机协作”模式。完成验证码点击后,脚本才会继续执行登录。 - 状态刷新 :在手动操作页面后(如点击验证码),页面的DOM状态可能发生微小变化。因此,在点击最终的登录按钮前,我们再次使用显式等待来确保按钮处于可点击状态,这比直接用之前定位到的旧元素更可靠。
3.4 登录后处理与脚本收尾
点击登录后,我们需要处理登录成功或失败的情况,并优雅地关闭浏览器。
# 10. 等待登录结果并处理
print("正在等待登录结果...")
time.sleep(3) # 等待页面跳转或提示出现
# 检查登录是否成功的简单方法:查看当前URL或页面是否存在特定元素
current_url = driver.current_url
print(f"当前页面URL: {current_url}")
# 示例:检查是否跳转到登录成功后的首页(my12306)
if "otn/view/index.html" in current_url:
print("✅ 登录成功!已跳转到12306个人中心。")
# 这里可以继续执行登录后的操作,例如查询余票
# ...
elif "login/init" in current_url:
print("❌ 登录失败,可能仍在登录页面。请检查验证码或账号密码是否正确。")
else:
print(f"⚠️ 登录状态未知。当前页面标题:{driver.title}")
# 可以尝试捕捉页面上的错误提示信息
try:
error_msg = driver.find_element(By.CLASS_NAME, "login-error").text
print(f"页面错误提示:{error_msg}")
except:
print("未找到明确的错误提示信息。")
# 11. 保持浏览器打开一段时间供查看,然后关闭
print("登录流程演示结束。浏览器将在10秒后自动关闭...")
time.sleep(10)
driver.quit()
print("浏览器已关闭,脚本执行完毕。")
状态判断逻辑 :判断自动化操作是否成功,不能只依赖是否抛异常。一个健壮的脚本应该检查“结果状态”。这里我们通过检查跳转后的URL是否包含成功页面的特征字符串来初步判断。更严谨的做法还可以结合检查页面标题、特定欢迎语元素等。对于失败情况,尝试抓取页面上显式的错误提示信息(如 class 为 login-error 的div),能帮助我们快速定位问题。
4. 完整代码整合与执行
将上述所有步骤整合,就得到了我们完整的自动化登录脚本。你可以将下面的代码保存为一个 .py 文件(例如 login_12306.py ),并替换其中的用户名和密码占位符。
"""
Selenium自动化登录12306实战脚本
环境要求:Python 3.7+, Selenium 4+, Edge浏览器及对应WebDriver
"""
from selenium import webdriver
from selenium.webdriver.edge.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
def login_12306(username, password):
"""执行12306登录流程"""
# 初始化浏览器选项
edge_options = webdriver.EdgeOptions()
edge_options.add_argument('--disable-blink-features=AutomationControlled')
edge_options.add_experimental_option('excludeSwitches', ['enable-automation'])
edge_options.add_experimental_option('useAutomationExtension', False)
# 启动浏览器(确保msedgedriver.exe在PATH中)
driver = webdriver.Edge(options=edge_options)
driver.maximize_window()
wait = WebDriverWait(driver, 10)
try:
# 1. 访问登录页
driver.get('https://kyfw.12306.cn/otn/resources/login.html')
print("页面加载中...")
time.sleep(2)
# 2. 切换到账号登录(容错处理)
try:
account_tab = wait.until(EC.element_to_be_clickable((By.LINK_TEXT, "账号登录")))
account_tab.click()
print("已切换至账号登录。")
except:
print("未找到账号登录选项卡,继续执行。")
# 3. 定位表单元素
user_input = wait.until(EC.presence_of_element_located((By.ID, "J-userName")))
pass_input = driver.find_element(By.ID, "J-password")
login_btn = driver.find_element(By.ID, "J-login")
# 4. 填写账号密码
user_input.clear()
user_input.send_keys(username)
pass_input.clear()
pass_input.send_keys(password)
print("账号密码已自动填充。")
# 5. 等待用户手动验证码
print("\n" + "="*60)
print("请手动在浏览器中完成验证码操作!")
print("完成后,请回到此窗口按回车键继续...")
print("="*60 + "\n")
input("按回车键继续...")
# 6. 点击登录
login_btn = wait.until(EC.element_to_be_clickable((By.ID, "J-login")))
login_btn.click()
print("已提交登录。")
# 7. 验证登录结果
time.sleep(3)
if "otn/view/index.html" in driver.current_url:
print("✅ 登录成功!")
return True, driver
else:
print("❌ 登录失败,请检查原因。")
# 尝试捕获错误信息
try:
error = driver.find_element(By.CLASS_NAME, "login-error").text
print(f"错误信息: {error}")
except:
pass
return False, driver
except Exception as e:
print(f"脚本执行过程中发生异常: {e}")
return False, driver
if __name__ == "__main__":
# ==== 在这里替换你的测试账号和密码 ====
MY_USERNAME = "替换为你的用户名"
MY_PASSWORD = "替换为你的密码"
# =====================================
if MY_USERNAME == "替换为你的用户名":
print("请先在代码中设置正确的用户名和密码!")
exit()
success, browser = login_12306(MY_USERNAME, MY_PASSWORD)
if success:
print("登录成功,可在此处继续后续操作(如查询余票)。")
# 示例:停留20秒后退出
time.sleep(20)
else:
print("登录流程未成功。")
browser.quit()
print("浏览器已关闭。")
执行流程 :
- 确保Python、Selenium、Edge驱动已正确安装配置。
- 将上述代码中的
MY_USERNAME和MY_PASSWORD替换为你的12306测试账号(强烈建议使用非重要账号)。 - 在命令行中运行脚本:
python login_12306.py。 - 脚本会自动打开Edge浏览器,跳转到12306登录页,填充账号密码。
- 脚本会在控制台暂停,提示你手动操作验证码。
- 你切换到浏览器窗口,完成验证码图片的识别和点击。
- 完成后,切换回控制台窗口,按回车键。
- 脚本将继续执行,点击登录按钮,并反馈登录结果。
5. 常见问题排查与进阶优化
即使按照步骤操作,你也可能会遇到一些问题。这里我整理了几个最常见的“坑”及其解决方案。
5.1 驱动相关问题
- 问题:
WebDriverException: Message: 'msedgedriver' executable needs to be in PATH.- 原因 :系统找不到
msedgedriver.exe。 - 解决 :确认已将
msedgedriver.exe所在目录添加到系统环境变量Path中,或者按照3.1节中的“方式二”,将驱动文件直接放到项目目录下,并在代码中指定路径。
- 原因 :系统找不到
- 问题:
SessionNotCreatedException: Message: session not created: This version of MSEdgeDriver only supports MSEdge version XX- 原因 :Edge浏览器版本与WebDriver驱动版本不匹配。
- 解决 :严格按照2.2节的步骤,检查浏览器版本并下载对应的驱动。更新浏览器或回退驱动版本。
5.2 元素定位失败
- 问题:
NoSuchElementException: Message: no such element: Unable to locate element- 原因1 :页面尚未加载完成,脚本就尝试去定位元素。
- 解决 :用
WebDriverWait配合EC(如presence_of_element_located,element_to_be_clickable)替代time.sleep和直接的find_element。
- 解决 :用
- 原因2 :页面结构发生变化,元素的ID或选择器已失效。
- 解决 :使用开发者工具重新检查元素属性。可以尝试使用更稳定的定位方式,如通过
name属性或相对路径更复杂的XPath。例如,如果ID是动态的,可以尝试用By.XPATH, "//input[@placeholder='请输入用户名']"来定位。
- 解决 :使用开发者工具重新检查元素属性。可以尝试使用更稳定的定位方式,如通过
- 原因3 :元素在
iframe框架内。- 解决 :12306登录页目前没有使用iframe,但如果遇到其他网站有此情况,需要先用
driver.switch_to.frame(frame_reference)切换到对应的iframe内,才能定位其中的元素。
- 解决 :12306登录页目前没有使用iframe,但如果遇到其他网站有此情况,需要先用
- 原因1 :页面尚未加载完成,脚本就尝试去定位元素。
5.3 反爬虫检测与规避
- 现象 :脚本运行时,网页提示“操作过快”或直接跳转到验证页面,甚至账号被临时限制。
- 分析与解决 :
- 降低操作频率 :在关键操作(如点击登录)前后添加随机的、人性化的等待时间,可以使用
time.sleep(random.uniform(1, 3))。 - 优化浏览器指纹 :我们代码中已经添加了
edge_options的一些参数来隐藏自动化特征。更高级的做法可以尝试禁用WebDriver的cdp协议命令、设置常见的User-Agent等。但请注意,这是一场“攻防战”,没有一劳永逸的方法。 - 核心原则 :任何公开的自动化操作都应遵守网站的
robots.txt协议,并尊重服务器的负载。对于12306这类公共服务,切忌高频、并发地运行脚本,这不仅是技术问题,更是法律和道德问题。本脚本仅用于个人学习和技术验证。
- 降低操作频率 :在关键操作(如点击登录)前后添加随机的、人性化的等待时间,可以使用
- 分析与解决 :
5.4 验证码自动化进阶思考
手动点验证码终究不够“自动化”。如果你有进一步的学习需求,可以探索以下方向(请注意合规性):
- 图像识别库 :研究使用
pytesseract(OCR)或ddddocr等库进行简单的字符验证码识别。但对于12306的物体识别点选验证码,难度极大。 - 第三方打码平台 :市面上有一些提供人工打码或高识别率API的服务平台。其原理是你的程序将验证码图片发送到平台,平台返回点击坐标。这需要付费,且涉及接口调用。
- 深度学习 :使用TensorFlow或PyTorch训练一个专门识别12306验证码的模型。这需要大量的标注数据、计算资源和专业知识。
重要提醒 :无论采用何种技术,对12306等网站进行自动化操作必须控制在极低的频率,且仅用于个人合法的学习或购票需求。大规模、商业化的自动化抢票或爬取行为,不仅会对服务器造成压力,也可能违反相关服务条款,甚至法律法规。
这个实战项目麻雀虽小,五脏俱全。它串联起了Selenium环境搭建、浏览器控制、元素定位、等待策略、异常处理和人机交互等多个核心知识点。希望这份详细的拆解和完整的代码,能帮你打通Web自动化的“任督二脉”,并应用到更多实际的场景中去。
更多推荐



所有评论(0)