Selenium实现自动登录

起因

因为办公环境的原因,平时有用到云桌面,登录的时候,除了账户和密码,还需要输入令牌,账户和密码每次是相同的,令牌随着时间每次都会变,每次使用云桌面都要手动输入账户、密码后,打开手机上的app查看令牌,虽然优化了一下流程,在Mac上下载了一个可以查看令牌的软件,比每次打开手机查看方便多了。但是感觉每次操作还是很费劲,理想中的操作是可以打开网址,自动填充账号、密码、令牌,然后登录

云桌面

结论

使用Selenium可以完成想要的效果

AppleScript的优势是Mac原生系统支持,不需要下载第三方库

相比AppleScript,Selenium优势为:

  1. 更可靠的元素定位和操作
  2. 内置等待机制,无需手动设置固定延迟
  3. 更好的错误处理和调试能力

思考

从有是否能实现自动登录云桌面,到动手操作实现过程没有花太多的时间,基本上都是在给AI提需求,然后反馈问题,再确认

AI的确比普通的搜索引擎效率高很多,对于知识的普及的确强了很多,有点平权的味道,不需要你对很多知识了解的太深,但是基本的编程知识还是需要的,有人说这是产品经理的时代,我们拭目以待吧

方案一,使用AppleScript来实现自动化

  • 没法确认什么时候网页加载好了,使用时需要手动延迟,需要等页面展示好才能定位聚焦到输入框
  • 也没法很好的和其他软件协同,比如获取令牌后拷贝到网页中

当然以上可能对大佬们都不是问题,但是对新手还是有点困难的,有点门槛

-- 设置要打开的网址
set targetUrl to "https://xxxxx.com.cn/#/"
-- 设置账号和密码
set username to "xxxx"
set userPassword to "xxxxx"

tell application "Safari"
	open location targetUrl
	activate
	delay 6 -- 等待页面加载
	
	-- 利用id定位并聚焦账号输入框(关键修改)
	do JavaScript "document.getElementById('username').focus();" in document 1
	delay 1
end tell

tell application "System Events"
	tell process "Safari"
		keystroke username -- 输入账号
		keystroke tab -- 切换到密码框(如果页面结构允许)
		delay 1
		keystroke userPassword -- 输入密码
		keystroke tab
		
	end tell
end tell

tell application "OTPKEY.app"
	activate -- 激活应用,使其显示在前台
	delay 2 -- 等待应用启动
end tell

方案二,使用Selenium来实现自动化

  • 使用OpenCV解析二维码,获取到密钥,当然也可以直接从二维码生成TOTP令牌
  • 通过第一步拿到的密钥,可以每次需要的时候,直接生成TOTP令牌
  • 使用selenium操作网页,执行输入信息、登录流程
import pyotp
import time
import urllib.parse
import cv2  # OpenCV库,包含QRCodeDetector

def decode_qrcode(image_path):
    #使用OpenCV解析二维码#
    # 读取图片
    img = cv2.imread(image_path)
    if img is None:
        raise FileNotFoundError(f"无法读取图片: {image_path}")

    # 创建QR码检测器
    detector = cv2.QRCodeDetector()

    # 检测并解码
    data, _, _ = detector.detectAndDecode(img)
    if not data:
        raise ValueError("无法识别二维码,请检查图片是否清晰或为有效的QR码")

    print(f"二维码内容: {data}")
    return data


def extract_secret_from_qr(qr_content):
    #从二维码内容中提取secret密钥#
    parsed = urllib.parse.urlparse(qr_content)
    params = urllib.parse.parse_qs(parsed.query)

    if 'secret' not in params:
        raise ValueError("二维码中未找到有效的secret密钥")

    return params['secret'][0]


def generate_totp_from_qrcode(image_path):
    #从二维码生成TOTP令牌#
    qr_content = decode_qrcode(image_path)
    secret_key = extract_secret_from_qr(qr_content)

    totp = pyotp.TOTP(secret_key)
    current_token = totp.now()
    remaining_seconds = 30 - (int(time.time()) % 30)

    return current_token, remaining_seconds, secret_key


if __name__ == "__main__":
    QR_CODE_IMAGE_PATH = "./cloud_desktop/s.jpeg"  # 例如:"~/Downloads/2fa_qr.png"

    try:
        token, remaining, secret = generate_totp_from_qrcode(QR_CODE_IMAGE_PATH)
        print(f"\n解析到的共享密钥: {secret}")
        print(f"当前动态令牌: {token}")
        print(f"令牌剩余有效时间: {remaining}")
    except Exception as e:
        print(f"发生错误: {str(e)}")

from selenium import webdriver
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.webdriver.common.action_chains import ActionChains
import pyotp
import time


# 配置参数 - 请根据实际情况修改
LOGIN_URL = "https://xxxxx.com.cn/#/"  # 登录页面URL
USERNAME = "xxxx"  # 账号
PASSWORD = "xxxx"  # 密码
QR_CODE_PATH = "./cloud_desktop/s.jpeg"  # 二维码图片路径(首次运行需要,之后可注释)
SAVED_SECRET = "xxxxx"  # 保存解析后的密钥,如:"JBSWY3DPEHPK3PXP"(首次运行后填写)

def generate_totp_token(secret):
    """生成当前有效的TOTP令牌"""
    totp = pyotp.TOTP(secret)
    return totp.now()


# 自定义条件:检查元素是否不包含某个属性
class attribute_not_contains(object):
    def __init__(self, locator, attribute, value):
        self.locator = locator
        self.attribute = attribute
        self.value = value

    def __call__(self, driver):
        element = driver.find_element(*self.locator)
        return element.get_attribute(self.attribute) != self.value


def login():
    # 初始化浏览器
    driver = webdriver.Safari()
    driver.maximize_window()
    driver.get(LOGIN_URL)

    try:
        # 1. 生成TOTP令牌
        totp_token = generate_totp_token(SAVED_SECRET)
        print(f"生成当前令牌: {totp_token}")

        # 2. 输入账号
        username_field = WebDriverWait(driver, 15).until(
            EC.element_to_be_clickable((By.ID, "username"))
        )
        ActionChains(driver).move_to_element(username_field).perform()
        driver.execute_script("arguments[0].focus();", username_field)
        username_field.clear()
        username_field.send_keys(USERNAME)
        time.sleep(1)

        # 3. 输入密码
        password_field = WebDriverWait(driver, 10).until(
            EC.element_to_be_clickable((By.ID, "password"))  # 替换为实际密码框ID
        )
        ActionChains(driver).move_to_element(password_field).perform()
        driver.execute_script("arguments[0].focus();", password_field)
        password_field.clear()
        password_field.send_keys(PASSWORD)
        time.sleep(1)

        # 4. 输入令牌
        token_field = WebDriverWait(driver, 10).until(
            EC.element_to_be_clickable((By.CSS_SELECTOR, "input.el-input__inner[type='password'][placeholder='令牌']"))
        )
        ActionChains(driver).move_to_element(token_field).perform()
        driver.execute_script("arguments[0].focus();", token_field)
        token_field.clear()
        token_field.send_keys(totp_token)
        time.sleep(1)  # 等待表单验证

        # 5. 等待登录按钮启用并点击(使用自定义条件)
        login_button_locator = (By.ID, "login")
        login_button = WebDriverWait(driver, 15).until(
            attribute_not_contains(login_button_locator, "disabled", "disabled")
        )

        # 再次确认按钮可点击
        login_button = WebDriverWait(driver, 10).until(
            EC.element_to_be_clickable(login_button_locator)
        )

        # 执行点击
        driver.execute_script("arguments[0].click();", login_button)

        print("登录操作已完成")
        time.sleep(5)  # 等待登录后页面加载

    except Exception as e:
        print(f"发生错误: {str(e)}")

    finally:
        input("按Enter键关闭浏览器...")
        driver.quit()


if __name__ == "__main__":
    login()



引用源

Written on September 3, 2025