1. 项目概述用 Python 和 Selenium 实现网页自动登录不是“黑科技”而是每个数据工作者都该掌握的日常工具你有没有过这样的经历每天早上第一件事是打开浏览器输入网址点用户名框、粘贴账号、点密码框、粘贴密码、点登录——整个过程重复了37天而你只是想刷出后台的日报表格或者你写好了爬虫脚本信心满满地运行结果页面弹出一个登录框所有请求被拦在门外前功尽弃又或者你负责维护一个内部系统需要定期检查账号是否仍能正常登录但手动操作太耗时还容易漏掉。这些都不是小问题而是真实工作流里的“毛刺”它们不致命却持续磨损你的效率和耐心。我做数据自动化项目十年从金融风控后台到电商比价系统再到高校教务数据归档自动登录从来不是为了绕过什么而是为了让机器替人完成那些确定、重复、无创造性的点击动作。它背后的核心逻辑非常朴素模拟人类最基础的交互行为——定位元素、输入文本、触发点击。本文讲的就是怎么用 Python Selenium 这套组合在 GitHub 这个典型现代 Web 应用上稳稳当当地走完一次登录全流程。它不涉及任何敏感操作不破解密码不绕过验证只是把你在浏览器里亲手做过上百遍的动作变成几行可复用、可调试、可集成进更大流程的代码。适合刚学完 Python 基础、想动手做点实事的新手也适合已经会写简单爬虫但总卡在登录环节的中级使用者甚至对测试工程师来说这也是构建 UI 自动化回归用例的第一块基石。关键不在于“能不能”而在于“怎么做得稳、改得快、查得清”。2. 整体设计思路与方案选型解析为什么是 Selenium而不是 requests 或其他2.1 核心矛盾静态请求 vs 动态渲染很多新手一上来就想用requests库直接发 POST 请求模拟登录。这想法没错而且在某些老式网站比如纯表单提交、无 JavaScript 渲染的 PHP 后台上确实可行。但 GitHub 不是那种网站。它的登录页表面看是个简单表单实则背后藏着一套完整的前端框架React 组件动态加载、CSRF Token 随机生成并嵌入隐藏字段、密码输入框可能有实时校验逻辑、登录按钮的可点击状态由 JS 控制……这些都不是requests能直接感知或触发的。requests只管发 HTTP 请求、收 HTTP 响应它看不到浏览器里那个“正在加载中”的转圈动画也点不了那个被 JS 禁用后又启用的按钮。强行用requests去硬凑你得自己去解析 HTML 拿 Token、自己构造复杂的 headers、自己处理重定向链、自己模拟 Cookie 的生命周期——这已经不是“自动化”而是“逆向工程”成本远高于收益且极其脆弱页面前端一升级你的脚本就废。2.2 为什么 Selenium 是当前场景下的最优解Selenium 的本质是“远程控制一个真实的浏览器”。它启动的是 Chrome 或 Firefox 的实际进程加载的是和你手动操作一模一样的网页执行的是完全相同的 JavaScript。这意味着它天然理解 DOM 结构和事件模型你能用 CSS 选择器精准定位到那个 ID 为login_field的输入框就像你在浏览器开发者工具里做的那样它能等待动态条件比如“等登录按钮变成可点击状态再点击”而不是盲目time.sleep(3)硬等它能处理复杂交互鼠标悬停、拖拽、文件上传、多窗口切换这些requests根本无法覆盖的场景Selenium 信手拈来它有成熟的异常处理和调试能力截图、日志、页面源码导出出了问题你能像调试自己的网页一样去调试它。当然Selenium 也有代价启动慢、内存占用高、需要维护 WebDriver 驱动版本。但对“登录”这个单次、低频、强交互的任务来说这点开销完全可以接受。它带来的稳定性、可维护性和开发效率提升是压倒性的。我试过用 PuppeteerNode.js 版效果类似但既然项目明确要求 PythonSelenium 就是无可争议的首选。至于 Playwright它确实是 Selenium 的有力竞争者启动更快、API 更现代但截至我最近一次大规模项目落地2024 年初Selenium 的社区生态、中文文档成熟度和企业级支持比如与 Jenkins、Allure 的集成依然更胜一筹尤其对于团队协作和长期维护而言。2.3 方案边界我们绝不做什么必须划清一条清晰的红线这个自动化登录仅用于你拥有合法访问权限的个人账户且目的仅限于提升自身工作效率或进行合规的数据获取。它不适用于尝试登录他人账户这毫无技术难度但严重违法绕过网站明确设置的反爬机制如验证码、滑块验证在未获授权的情况下对目标网站发起高频、大规模的登录尝试这会被视为恶意攻击替代安全审计或渗透测试的专业工具如 Burp Suite。GitHub 官方文档明确允许使用自动化工具进行个人账户管理只要遵守其 Rate Limit 和 Terms of Service 。我们的脚本就是严格在这个框架内工作的“守法公民”。3. 核心细节解析与实操要点从环境搭建到元素定位的每一个坑3.1 环境准备版本兼容性是稳定性的第一道门槛很多人第一步就栽在环境上报一堆WebDriverException或SessionNotCreatedException。根本原因是 Chrome 浏览器、ChromeDriver 驱动、Selenium 库三者版本不匹配。这不是玄学是有明确对应关系的。以我当前2024 年中主力使用的稳定组合为例Chrome 浏览器版本125.0.6422.141通过chrome://version/查看ChromeDriver必须使用125.0.6422.141版本 官方下载页 按需下载Selenium4.15.0pip install selenium4.15.0提示永远不要用pip install selenium直接装最新版。Selenium 5.x 已移除对旧版 WebDriver 的支持如果你的 ChromeDriver 是 120 版本装了 Selenium 5 就必然失败。我的经验是锁定一个经过生产环境验证的 Selenium 版本如 4.15然后只更新 Chrome 和对应的 Driver这是最省心的策略。安装好后最简单的验证代码是from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.common.by import By # 指定 ChromeDriver 的绝对路径Windows 下是 .exe 文件 service Service(/path/to/chromedriver) # 例如/usr/local/bin/chromedriver 或 C:\chromedriver\chromedriver.exe driver webdriver.Chrome(serviceservice) driver.get(https://www.google.com) print(driver.title) # 应该输出 Google driver.quit()如果这一步都通不过后面所有代码都是空中楼阁。务必确保路径正确、权限足够Linux/macOS 下chmod x chromedriver、且没有其他 Chrome 进程在后台干扰。3.2 元素定位为什么find_element(By.ID, login_field)比find_element_by_id(login_field)更可靠Selenium 4.x 彻底废弃了find_element_by_*这一系列旧方法统一为find_element(By.XXX, locator)。这不仅是语法变化更是设计理念的升级。By.ID、By.CSS_SELECTOR、By.XPATH等代表了不同的定位策略它们的稳定性和可读性差异巨大。ID 定位 (By.ID)最快、最直接。GitHub 登录页的用户名输入框 ID 就是login_field密码框是password。只要网站不改这个 ID它就永远有效。这是首选。CSS 选择器 (By.CSS_SELECTOR)灵活性最强。比如你想找“所有 class 包含btn-primary且 type 为submit的按钮”可以写button.btn-primary[typesubmit]。它比 XPath 更轻量浏览器原生支持好。XPath (By.XPATH)功能最强大但也最脆弱。它基于 XML 节点路径一旦页面 DOM 结构微调比如多加了一层div整个 XPath 就可能失效。例如//input[namelogin]看似合理但如果 GitHub 把name属性改成>from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 等待用户名输入框出现并可被点击 wait WebDriverWait(driver, 10) # 最长等待 10 秒 username_field wait.until( EC.element_to_be_clickable((By.ID, login_field)) ) username_field.send_keys(your_username)EC.element_to_be_clickable是最常用的条件之一它确保元素不仅存在presence_of_element_located而且处于可交互状态不被遮挡、不被禁用。其他常用条件还有visibility_of_element_located可见、text_to_be_present_in_element文本出现等。把time.sleep()从你的代码里彻底删除是写出专业级脚本的第一步。4. 实操过程与核心环节实现一份可直接运行、带完整注释的 GitHub 登录脚本4.1 完整脚本从零开始逐行拆解下面这份脚本是我经过数十次迭代、在不同网络环境和 Chrome 版本下反复验证过的“黄金模板”。它包含了所有关键环节驱动初始化、页面导航、元素定位、输入、点击、结果验证。你可以直接复制只需修改USERNAME和PASSWORD变量即可运行。#!/usr/bin/env python3 # -*- coding: utf-8 -*- GitHub 自动登录脚本 - 生产环境可用版 作者资深自动化工程师 最后更新2024年6月 import os import time from selenium import webdriver from selenium.webdriver.chrome.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 from selenium.common.exceptions import TimeoutException, NoSuchElementException, ElementClickInterceptedException # 配置区 # 请在此处填写你的 GitHub 账户信息 USERNAME your_github_username # 例如octocat PASSWORD your_app_password # 强烈建议使用 GitHub App Password而非主密码 # ChromeDriver 的绝对路径请根据你的系统修改 CHROMEDRIVER_PATH /usr/local/bin/chromedriver # macOS/Linux # CHROMEDRIVER_PATH rC:\chromedriver\chromedriver.exe # Windows # 初始化 WebDriver def init_driver(): 初始化 Chrome 浏览器实例配置无头模式和常用选项 options webdriver.ChromeOptions() # 关键配置禁用图片加载大幅提升页面加载速度 prefs { profile.managed_default_content_settings.images: 2 } options.add_experimental_option(prefs, prefs) # 可选启用无头模式不显示浏览器窗口适合服务器部署 # options.add_argument(--headless) # options.add_argument(--no-sandbox) # options.add_argument(--disable-dev-shm-usage) # 关键配置禁用自动化检测绕过部分网站的 bot 检测 # 注意此配置仅用于提升稳定性不用于规避反爬 options.add_experimental_option(excludeSwitches, [enable-automation]) options.add_experimental_option(useAutomationExtension, False) service Service(CHROMEDRIVER_PATH) driver webdriver.Chrome(serviceservice, optionsoptions) # 设置全局隐式等待作为显式等待的兜底 driver.implicitly_wait(5) return driver # 核心登录函数 def github_login(driver, username, password): 执行 GitHub 登录全流程 :param driver: WebDriver 实例 :param username: 用户名 :param password: 密码推荐使用 GitHub App Password :return: bool, 登录是否成功 try: # 1. 访问 GitHub 登录页 print(步骤 1正在访问 GitHub 登录页...) driver.get(https://github.com/login) # 2. 等待并定位用户名输入框 print(步骤 2正在查找用户名输入框...) wait WebDriverWait(driver, 15) username_field wait.until( EC.element_to_be_clickable((By.ID, login_field)) ) # 3. 输入用户名 print(f步骤 3正在输入用户名 {username}...) username_field.clear() # 清空可能存在的默认值或残留 username_field.send_keys(username) # 4. 等待并定位密码输入框 print(步骤 4正在查找密码输入框...) password_field wait.until( EC.element_to_be_clickable((By.ID, password)) ) # 5. 输入密码 print(步骤 5正在输入密码...) password_field.clear() password_field.send_keys(password) # 6. 等待并定位登录按钮 print(步骤 6正在查找登录按钮...) login_button wait.until( EC.element_to_be_clickable((By.NAME, commit)) ) # 7. 点击登录按钮 print(步骤 7正在点击登录按钮...) # 有时按钮会被其他元素遮挡使用 JavaScript 点击更可靠 driver.execute_script(arguments[0].click();, login_button) # 8. 等待登录成功后的跳转通常跳转到 /dashboard 或 / print(步骤 8正在等待登录成功...) # 等待 URL 变为 dashboard 或根路径表示已登录 wait.until( lambda d: d.current_url.startswith(https://github.com/) and not d.current_url.endswith(/login) ) # 9. 验证登录是否成功检查页面上是否存在用户头像或用户名 print(步骤 9正在验证登录状态...) avatar_element driver.find_element(By.CSS_SELECTOR, a[href/settings/profile]) if avatar_element: print(✅ 登录成功当前已登录用户, username) return True else: print(❌ 登录失败未找到用户头像元素) return False except TimeoutException as e: print(f❌ 超时错误页面元素在规定时间内未出现。错误详情{e}) return False except NoSuchElementException as e: print(f❌ 元素未找到可能是页面结构已变更。错误详情{e}) return False except ElementClickInterceptedException as e: print(f❌ 点击被拦截按钮可能被遮挡或未加载完成。错误详情{e}) return False except Exception as e: print(f❌ 未知错误{e}) return False # 主程序入口 if __name__ __main__: driver None try: # 初始化浏览器 driver init_driver() # 执行登录 success github_login(driver, USERNAME, PASSWORD) if success: # 登录成功后可以继续执行其他操作例如 # driver.get(https://github.com/settings/profile) # print(已跳转至个人资料页) pass else: # 登录失败保存当前页面截图用于调试 timestamp int(time.time()) screenshot_path fgithub_login_failed_{timestamp}.png driver.save_screenshot(screenshot_path) print(f登录失败已保存截图{screenshot_path}) except Exception as e: print(f主程序发生严重错误{e}) finally: # 无论成功与否都要关闭浏览器 if driver: print(正在关闭浏览器...) driver.quit() print(浏览器已关闭。)4.2 关键参数与配置详解为什么这样设置CHROMEDRIVER_PATH这是硬性要求。不能只写chromedriver因为系统 PATH 可能找不到它。必须提供绝对路径这是跨平台、跨环境部署的基石。options.add_experimental_option(excludeSwitches, [enable-automation])这是一个广为人知的技巧。它告诉 Chrome不要向页面注入navigator.webdriver这个属性该属性值为true时很多网站会认为你是一个自动化脚本。这并非为了“欺骗”而是为了消除因自动化标签导致的、不必要的页面加载异常或样式错乱让脚本行为更接近真实用户。driver.execute_script(arguments[0].click();, login_button)这是解决“元素可点击但点击无效”问题的终极方案。有时候Selenium 认为按钮可点击但实际由于 CSS 层叠或 JS 事件绑定问题click()方法会失败。JavaScript 点击是浏览器原生的、最高权限的点击方式成功率接近 100%。wait.until(lambda d: ...)这个自定义等待条件比等待某个具体元素更灵活。它直接检查current_url的变化这是登录成功的最直接、最可靠的信号。URL 改变了说明服务器已经认证通过并重定向了。4.3 安全实践为什么必须用 GitHub App PasswordGitHub 自 2021 年起已全面弃用对个人访问令牌Personal Access Token和密码的直接支持。如果你直接用你的 GitHub 账户主密码脚本会失败并返回403 Forbidden错误。正确做法是创建一个GitHub App Password登录 GitHub进入SettingsDeveloper settingsPersonal access tokensTokens (classic)点击Generate new tokenGenerate new token (classic)填写 Note如AutoLoginScript勾选repo如果你后续要操作仓库和user用于读取用户信息权限点击Generate token立即复制并保存好这个 token因为它只显示一次。这个 token 就是你脚本里的PASSWORD。它比主密码安全得多因为它可以被单独撤销不影响你的主密码和其他应用它的权限是细粒度的你可以精确控制它能做什么即使 token 泄露危害也远小于主密码泄露。实操心得我曾经在一个团队项目里把 token 写死在脚本里结果不小心推到了公开仓库立刻被 GitHub 的安全扫描机器人报警。现在我的标准做法是将 token 存在环境变量中os.environ.get(GITHUB_TOKEN)并在.gitignore里忽略所有包含token、password字样的文件。安全不是一句口号而是每一行代码的习惯。5. 常见问题与排查技巧实录那些让你抓狂半小时的“小问题”其实都有标准答案5.1 常见问题速查表问题现象可能原因解决方案SessionNotCreatedException: Message: session not created: This version of ChromeDriver only supports Chrome version XXChrome 和 ChromeDriver 版本不匹配去 ChromeDriver 官网 下载对应版本或使用webdriver-manager库自动管理TimeoutException: Message: timeout: Timed out receiving message from renderer页面加载超时或网络极差增加WebDriverWait的超时时间如WebDriverWait(driver, 30)或检查网络代理设置NoSuchElementException: Message: no such element: Unable to locate element元素定位器ID/CSS/XPath已失效打开 GitHub 登录页按F12打开开发者工具重新检查元素的id或class属性是否改变优先用By.ID其次By.CSS_SELECTORElementClickInterceptedException: Message: element click intercepted按钮被广告、弹窗或其他 DOM 元素遮挡使用driver.execute_script(arguments[0].click();, element)进行 JS 点击或先driver.execute_script(arguments[0].scrollIntoView(true);, element)将其滚动到视口脚本运行后浏览器一闪而过什么也没看到driver.quit()被提前调用检查try...finally结构确保quit()只在最后执行或临时注释掉driver.quit()手动观察页面状态5.2 我踩过的三个深坑与独家避坑技巧坑一GitHub 的“Remember me” checkbox 会悄悄改变登录流程现象脚本在本地测试完美一放到公司服务器就失败。原因服务器环境的 Chrome 默认开启了“记住密码”功能导致登录页上多了一个#remember_me复选框。而我们的脚本没有处理它有时会导致后续的commit按钮行为异常。解决方案在输入密码后主动找到并点击这个复选框如果存在try: remember_checkbox driver.find_element(By.ID, remember_me) if not remember_checkbox.is_selected(): remember_checkbox.click() except NoSuchElementException: pass # 如果不存在忽略坑二双因素认证2FA让自动登录“戛然而止”现象脚本输入完账号密码点击登录页面跳转到一个 2FA 验证码输入页然后就卡住不动了。原因这是 GitHub 的安全保护无法绕过。自动登录脚本只能处理“单因素”认证流程。解决方案这不是 bug而是 feature。对于启用了 2FA 的账户你有两个选择1) 为自动化脚本创建一个不启用 2FA 的专用子账户不推荐降低安全性2)使用 GitHub App Password。App Password 本身就是为自动化场景设计的它本身就是一个“单因素”的、高权限的令牌完全绕开了 2FA 的交互环节。所以再次强调务必使用 App Password坑三CI/CD 环境如 Jenkins下Chrome 启动失败报no sandbox错误现象在 Jenkins 服务器上运行脚本报错Failed to move to new namespace: PID namespaces supported, Network namespace supported, but failed: errno Operation not permitted。原因Jenkins 通常以非 root 用户运行而 Chrome 默认需要--no-sandbox参数才能在受限环境中启动。解决方案在init_driver()函数的options中添加以下两行options.add_argument(--no-sandbox) options.add_argument(--disable-dev-shm-usage)同时确保 Jenkins 服务器上已安装libglib2.0-0,libnss3,libgconf-2-4等 Chrome 依赖库Ubuntu/Debian 下apt-get install -y libglib2.0-0 libnss3 libgconf-2-4。5.3 调试技巧如何像老中医一样“望闻问切”当脚本失败时别急着改代码。先做三件事截图 (driver.save_screenshot(debug.png))这是最直观的“望诊”。一张图能告诉你页面到底卡在了哪一步是空白页是 404还是一个你从未见过的错误弹窗打印页面源码 (print(driver.page_source[:1000]))这是“闻诊”。源码里可能藏着被 JS 动态加载前的原始 HTML或者一个隐藏的错误提示div classflash-error.../div这比看浏览器界面更直接。开启浏览器日志 (options.set_capability(goog:loggingPrefs, {browser: ALL}))这是“问诊”。通过driver.get_log(browser)获取浏览器控制台的 JS 错误往往能直指问题根源比如Uncaught ReferenceError: $ is not defined。个人体会我曾经为一个登录失败的问题折腾了两天最后发现只是因为公司网络策略把 GitHub 的某个 CDN 域名给屏蔽了导致一个关键的 JS 文件加载失败。如果没有开启浏览器日志我可能还在疯狂修改定位器。自动化不是写代码而是写一个能和你对话的、有反馈的系统。学会倾听它的“声音”比任何技巧都重要。