1. 项目概述与核心需求解析
最近在几个技术社群里,看到不少朋友在讨论一个挺实际的需求:如何让电脑在无人值守的情况下,自动完成一些网页端的“挂机”任务,比如在线学习平台需要累积学习时长,或者某些需要保持在线状态的网页应用。手动盯着屏幕显然不现实,而市面上的一些“鼠标连点器”或“自动刷新”工具又往往功能单一,或者存在安全风险。于是,一个结合了Python编程能力和浏览器自动化技术的方案就浮出水面了——使用Python的Selenium库,驱动Microsoft Edge浏览器,实现自动化操作,并解决长时间运行可能遇到的屏幕自动熄灭问题。
这个项目的核心,就是利用Selenium这个强大的Web自动化测试工具,模拟真实用户对Edge浏览器的操作。它不仅仅是简单的页面刷新,而是可以模拟点击、输入、滚动、等待等一系列复杂交互,让挂机行为更接近真人操作,从而降低被目标网站检测为“机器人”的风险。同时,为了解决电脑因节能设置而自动锁屏或休眠导致程序中断的问题,我们还需要编写一段“防熄屏”代码,让系统保持活跃状态。
适合谁来参考这篇内容呢?如果你是一名有一定Python基础,希望将自动化技术应用到实际场景中的开发者或爱好者;或者你是一名运维、测试人员,想了解如何通过脚本模拟用户行为;甚至你只是对“解放双手”的自动化脚本感兴趣,想学习一个完整的实战案例,那么这篇从环境搭建到代码实现,再到避坑技巧的详细指南,应该能给你提供一条清晰的路径。整个过程我们将围绕Edge浏览器展开,因为它作为Windows系统的“亲儿子”,在兼容性和性能上通常有不错的表现。
2. 环境准备与工具选型背后的逻辑
工欲善其事,必先利其器。在开始写代码之前,我们需要搭建一个稳定、可复现的自动化环境。这里的工具选型每一步都有其考量,盲目安装很容易掉进版本兼容的坑里。
2.1 Python环境:为什么推荐3.8+?
首先自然是Python。我强烈建议使用Python 3.8或以上的版本。原因有几个:一是Selenium 4.x版本对Python 3.7及以下版本的支持已逐步减弱,新特性可能无法使用;二是Python 3.8在异步IO、字典顺序等方面有重要改进,能让我们的脚本运行得更稳定。安装时务必勾选“Add Python to PATH”,这是老生常谈但新手必踩的坑。安装完成后,在命令行输入python --version确认版本。
注意:如果你的电脑上同时存在Python 2和Python 3,可能需要使用
python3和pip3命令。在Windows上,可以通过py -3.9这样的命令来指定版本运行。
2.2 Selenium库:安装与版本锁定策略
接下来是核心工具Selenium。通过pip安装非常简单:pip install selenium。但这里有个关键技巧:强烈建议锁定一个稳定的版本。直接pip install selenium会安装最新版,而最新版有时会引入不兼容的改动。对于生产或长期运行的脚本,我习惯使用pip install selenium==4.10.0这样的命令来安装一个我验证过稳定的版本(例如4.10.0)。你可以通过pip show selenium查看已安装的版本信息。
Selenium的本质是一个通过WebDriver协议与浏览器通信的客户端库。它本身不包含浏览器,也不负责驱动浏览器,它只是发送指令(如“打开页面”、“点击元素”)的“指挥官”。
2.3 Edge浏览器与WebDriver:版本同步的生死线
这是整个环境搭建中最容易出错的一环。Selenium需要通过一个名为msedgedriver的驱动程序来操控Edge浏览器。驱动程序的版本必须与Edge浏览器的版本严格匹配,否则程序将无法启动。
查看Edge浏览器版本:打开Edge浏览器,点击右上角三个点 -> 帮助和反馈 -> 关于Microsoft Edge。记下版本号(例如,115.0.1901.183)。
下载对应版本的WebDriver:
- 官方下载地址是:
https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/ - 在网站上找到与你的Edge浏览器主版本号一致的驱动进行下载。例如,Edge版本是115.1901.183,就下载适用于“115”版本的驱动。
- 根据你的系统(Win32/64, macOS, Linux)选择正确的文件。
- 官方下载地址是:
配置WebDriver路径:下载后是一个名为
msedgedriver.exe的可执行文件。你有三种方式让Python找到它:- 方法一(推荐,便于管理):将
msedgedriver.exe放在你的Python项目目录下。 - 方法二(系统级配置):将其放在一个固定路径(如
C:\WebDriver\),并将该路径添加到系统的PATH环境变量中。 - 方法三(代码中指定):在Python代码里直接指定驱动文件的绝对路径。这是最明确、最不容易出错的方式,我们后续代码会采用这种方式。
- 方法一(推荐,便于管理):将
实操心得:我习惯为每个自动化项目单独建一个文件夹,里面存放项目代码、日志以及对应的
msedgedriver.exe。这样项目隔离性好,迁移和版本管理也方便。千万不要把不同版本的驱动混放在系统目录,那将是灾难的开始。
2.4 集成开发环境(IDE)选型
写Python代码,一个好用的IDE能事半功倍。对于这个项目:
- VS Code:轻量、插件丰富。需要安装Python扩展和Pylance。配置虚拟环境稍微需要一点步骤,但灵活性极高。
- PyCharm:功能强大的专业IDE,对Python项目管理和虚拟环境支持开箱即用,适合中大型项目。
- Jupyter Notebook:适合做探索性分析和代码片段测试,但不适合作为最终自动化脚本的运行环境。
我个人更倾向于使用VS Code,因为它对Markdown(写笔记)、JSON配置以及终端操作的支持都很流畅,适合这种“脚本+文档”类型的项目。
3. 核心代码实现与自动化逻辑拆解
环境准备好后,我们进入核心部分:编写自动化脚本。我们的目标是创建一个健壮的、可配置的自动挂机程序。我会将代码分成几个功能模块来讲解,并解释每一行关键代码背后的意图。
3.1 浏览器启动与基础配置
启动浏览器不是简单的一行代码,合理的配置可以避免很多奇怪的问题,比如被网站检测到自动化工具。
from selenium import webdriver from selenium.webdriver.edge.service import Service from selenium.webdriver.edge.options import Options import time import os import pyautogui # 用于防熄屏操作 # 1. 配置Edge浏览器选项 edge_options = Options() # 关键配置1:启用“实验性选项”来尝试隐藏自动化特征 # 此选项在Selenium 4中推荐使用,可以避免一些简单的检测 edge_options.add_experimental_option("excludeSwitches", ["enable-automation"]) edge_options.add_experimental_option('useAutomationExtension', False) # 关键配置2:防止WebDriver特征被暴露 # 一些网站会通过navigator.webdriver属性来检测自动化,这个配置可以将其设置为undefined edge_options.add_argument('--disable-blink-features=AutomationControlled') # 其他实用配置 edge_options.add_argument('--no-sandbox') # 在Linux或某些Docker环境下可能需要 # edge_options.add_argument('--headless') # 无头模式,不显示浏览器界面。挂机时通常关闭,以便观察。 edge_options.add_argument('--disable-gpu') # 禁用GPU硬件加速,有时可增加稳定性 edge_options.add_argument('--window-size=1920,1080') # 设置初始窗口大小 # 2. 指定WebDriver路径并创建服务 # 将 'your_path_to/msedgedriver.exe' 替换为你的实际路径 driver_path = os.path.join(os.getcwd(), 'msedgedriver.exe') service = Service(executable_path=driver_path) # 3. 启动浏览器 driver = webdriver.Edge(service=service, options=edge_options) # 4. 执行CDP命令,进一步覆盖自动化痕迹(Selenium 4语法) driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', { 'source': ''' Object.defineProperty(navigator, 'webdriver', { get: () => undefined }); ''' })代码解读与注意事项:
Options()对象用于设置浏览器启动时的各种参数。excludeSwitches和useAutomationExtension是经典的隐藏Chrome/Edge顶部“正受到自动测试软件控制”提示的配置。--disable-blink-features=AutomationControlled是更进一步的隐藏手段。execute_cdp_cmd是直接通过Chrome DevTools Protocol执行命令,在页面加载前注入JavaScript,将navigator.webdriver属性覆盖掉,这是目前应对检测比较有效的方法之一。- 重要:这些隐藏手段并非万能,对于反爬机制严密的网站可能依然无效。自动化访问应始终遵守目标网站的
robots.txt和服务条款。
3.2 模拟用户行为:访问、等待与交互
挂机不仅仅是打开页面,还需要模拟人类的一些随机行为,避免行为模式过于规律。
# 访问目标网址 target_url = "https://example.com/learning-platform" # 请替换为你的实际网址 driver.get(target_url) print(f"已访问: {target_url}") # 强制等待,确保页面基本元素加载(初学者适用,但非最佳) time.sleep(5) # --- 更优方案:使用Selenium的“显式等待” --- from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By try: # 等待页面某个关键元素(比如登录后的用户头像)出现,最多等10秒 user_avatar = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, ".user-avatar")) ) print("关键元素加载完成,页面已就绪。") except Exception as e: print(f"等待元素超时或未找到: {e}") # 可以在这里记录日志或进行其他异常处理 # --- 模拟一些用户操作 --- # 示例1:随机滚动页面,模拟阅读 import random scroll_height = driver.execute_script("return document.body.scrollHeight") for _ in range(3): # 随机滚动到页面中部某个位置 scroll_to = random.randint(200, int(scroll_height * 0.6)) driver.execute_script(f"window.scrollTo(0, {scroll_to});") time.sleep(random.uniform(2, 4)) # 随机等待2-4秒 # 示例2:随机点击一些无害的链接或按钮(确保不会触发重要操作) # 首先找到所有可能是链接或按钮的元素 all_links = driver.find_elements(By.TAG_NAME, "a") safe_links = [link for link in all_links if link.is_displayed() and link.is_enabled()] if safe_links: link_to_click = random.choice(safe_links[:5]) # 只在前5个可见链接中随机选 try: link_to_click.click() print(f"随机点击了链接,当前URL变为: {driver.current_url}") time.sleep(random.uniform(3, 6)) # 点击后可以回退,或者继续在新页面操作 driver.back() time.sleep(2) except Exception as e: print(f"点击链接时出错: {e}")行为模拟的逻辑:完全静止的页面访问很容易被识别。加入随机滚动、随机等待时间、随机但安全的点击操作,可以极大地增加行为模式的“熵”,使其更像一个真实用户在浏览。核心原则是:所有操作都应该是“无害”的,避免点击“注销”、“删除”、“提交订单”等关键按钮。可以通过CSS选择器精准定位那些装饰性或导航性的元素。
3.3 防熄屏机制的实现原理与代码
这是项目的另一个核心点。电脑的锁屏和休眠是操作系统级别的电源管理策略,仅仅保持浏览器活动不一定能阻止。我们的策略是模拟用户输入,欺骗系统认为用户正在操作。
def keep_alive(interval=60): """ 防熄屏/防休眠函数。 通过模拟轻微的鼠标移动或按键操作,使系统认为用户处于活动状态。 :param interval: 执行操作的间隔时间,单位秒。默认60秒一次。 """ import pyautogui import random import threading def _worker(): while True: try: # 方案1:模拟极小的鼠标移动(推荐) # 获取当前鼠标位置 current_x, current_y = pyautogui.position() # 在当前位置基础上,随机偏移1-2个像素 move_x = current_x + random.choice([-1, 0, 1]) move_y = current_y + random.choice([-1, 0, 1]) pyautogui.moveTo(move_x, move_y, duration=0.1) # 方案2:模拟按下一次“Shift”键(备用) # pyautogui.press('shift') # 偶尔按一次,不要太频繁 print(f"[防熄屏] 已执行保活操作于 {time.strftime('%H:%M:%S')}") except Exception as e: print(f"[防熄屏] 操作执行出错: {e}") # 等待指定间隔 time.sleep(interval) # 创建一个守护线程,这样主程序退出时,这个线程也会自动结束 thread = threading.Thread(target=_worker, daemon=True) thread.start() print(f"防熄屏线程已启动,每 {interval} 秒活动一次。") # 在脚本主逻辑开始后,启动防熄屏 keep_alive(interval=300) # 每5分钟活动一次,对于防休眠通常足够原理与注意事项:
- 为什么是线程?防熄屏需要在一个独立的、循环运行的线程中执行,不能阻塞主线程(主线程可能在执行网页操作或等待)。
- 为什么用
pyautogui?它直接模拟系统级的输入事件,比单纯发送浏览器指令更有效。确保已安装:pip install pyautogui。 - 移动像素为什么这么小?移动1-2个像素,人眼几乎无法察觉,不会干扰你正常使用电脑(如果你同时在用的话)。移动幅度过大反而显得异常。
- 间隔时间设置:Windows和macOS的默认锁屏时间通常是几分钟到十几分钟。将间隔设置为300秒(5分钟)是一个比较安全且保守的值,既能有效防止休眠,又不会因操作过于频繁而产生不必要的系统负载。
- 守护线程(daemon):设置为
daemon=True意味着当主程序所有非守护线程结束时,这个保活线程会自动退出,避免程序无法正常关闭。
重要警告:请谨慎使用此功能,并确保你了解其影响。在不需要时应及时关闭脚本。切勿用于任何违反计算机使用规定或绕过合法系统限制的场景。
3.4 完整的挂机循环与异常处理
将以上模块组合起来,形成一个可持续运行、具备一定容错能力的挂机循环。
def main_hanging_session(duration_hours=2): """ 主挂机会话 :param duration_hours: 计划挂机的总时长(小时) """ start_time = time.time() end_time = start_time + (duration_hours * 3600) cycle_count = 0 while time.time() < end_time: cycle_count += 1 print(f"\n--- 开始第 {cycle_count} 个挂机周期 ---") try: # 1. 确保浏览器窗口在最前(可选) # driver.minimize_window() # time.sleep(1) # driver.maximize_window() # 2. 刷新页面(模拟用户重新访问或保持会话) driver.refresh() print("页面已刷新。") # 使用显式等待等待某个元素出现 WebDriverWait(driver, 15).until( EC.presence_of_element_located((By.TAG_NAME, "body")) ) # 3. 执行一系列模拟用户行为 simulate_user_activity(driver) # 4. 计算并等待下一个周期(随机化等待时间) # 基础等待时间 + 随机扰动,例如每10-15分钟一个周期 base_wait = 10 * 60 # 10分钟 random_wait = random.randint(-120, 180) # -2到+3分钟的随机扰动 next_wait = max(60, base_wait + random_wait) # 确保至少等待1分钟 print(f"本轮活动结束。等待 {next_wait//60} 分 {next_wait%60} 秒后开始下一轮。") time.sleep(next_wait) except KeyboardInterrupt: print("\n检测到用户中断(Ctrl+C),准备退出...") break except Exception as e: print(f"挂机周期中出现未预期错误: {e}") # 记录错误到日志文件 with open("hang_error.log", "a") as f: f.write(f"{time.ctime()}: {e}\n") # 尝试重启浏览器(简易版:先退出再启动) print("尝试恢复浏览器会话...") try: driver.quit() except: pass time.sleep(5) # 重新初始化driver (这里需要重新执行之前的初始化代码,为简洁起见,可以封装为一个函数) # driver = init_browser() # 如果重启失败,则退出循环 print("恢复失败,程序退出。") break print(f"\n挂机任务结束。总共运行了 {cycle_count} 个周期。") driver.quit() def simulate_user_activity(driver): """封装模拟用户活动的函数""" # 随机滚动 scroll_actions = random.randint(1, 3) for _ in range(scroll_actions): scroll_pixel = random.randint(100, 500) driver.execute_script(f"window.scrollBy(0, {scroll_pixel});") time.sleep(random.uniform(1.0, 2.5)) # 随机寻找并点击无害元素(例如页码、标签页、展开按钮) # 使用更精准的选择器避免误点 harmless_selectors = [ "a.pagination-link", "button[aria-label='更多']", ".tab-item", "div.collapsible-header", "span.icon-expand" ] for selector in harmless_selectors: if random.random() > 0.7: # 30%的概率执行该选择器的查找点击 try: elements = driver.find_elements(By.CSS_SELECTOR, selector) if elements: elem = random.choice(elements) if elem.is_displayed() and elem.is_enabled(): elem.click() time.sleep(random.uniform(2, 4)) print(f"模拟点击了: {selector}") break # 点击一次后即可退出 except: continue # 程序入口 if __name__ == "__main__": # 初始化浏览器 # driver = webdriver.Edge(service=service, options=edge_options) # 这里需要用到前面定义的service和options # 启动防熄屏 keep_alive(interval=300) # 开始挂机 main_hanging_session(duration_hours=2)4. 高级技巧、常见问题与排查实录
即使代码写好了,在实际运行中你肯定会遇到各种各样的问题。下面是我在多次实践中总结出来的“避坑指南”和问题排查思路。
4.1 如何应对网站的反爬/反自动化检测?
简单的隐藏自动化特征(如3.1节所述)可能不足以应对更高级的检测。网站可能会通过以下方式检测:
- WebDriver属性:我们已经通过CDP命令覆盖了
navigator.webdriver。 - 浏览器指纹:检查插件列表、语言、屏幕分辨率、时区等是否与正常浏览器一致。
- 行为模式:鼠标移动轨迹、点击速度、输入速度是否过于“机械”或“匀速”。
- Cookie和本地存储:自动化浏览器可能缺少正常的登录态或历史记录。
应对策略:
- 使用更真实的
User-Agent:有些自动化驱动会有特定的UA。可以手动设置一个常见的普通浏览器UA。edge_options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.183') - 引入随机延迟:在所有操作前后加入
time.sleep(random.uniform(a, b)),让等待时间不均匀。 - 模拟人类鼠标移动轨迹:
pyautogui的移动是直线瞬移。更高级的模拟可以使用pyautogui.moveTo(x, y, duration=t),其中duration参数让移动有耗时,或者使用更复杂的库生成贝塞尔曲线轨迹。 - 使用浏览器配置文件:让Selenium加载一个已登录的、有正常浏览历史的用户数据目录,这样浏览器指纹会更真实。
# 注意:需要先手动用Edge登录目标网站,并找到其用户数据路径 # user_data_dir = r'C:\Users\YourName\AppData\Local\Microsoft\Edge\User Data\Default' # edge_options.add_argument(f'user-data-dir={user_data_dir}')警告:此方法有隐私和安全风险,且Edge可能禁止多实例同时访问同一数据目录,需谨慎使用。
4.2 WebDriver版本不匹配或失效
这是最高频的错误。症状通常是:WebDriverException: Message: unknown error: cannot find Edge binary或SessionNotCreatedException。
排查步骤:
- 核对版本:再次确认Edge浏览器版本和下载的
msedgedriver版本号的主版本是否一致。 - 检查路径:确认代码中
executable_path指向的路径是否正确,文件名是否拼写错误。 - Edge自动更新:Edge浏览器会自动更新。如果某天脚本突然不能用了,第一反应就是检查Edge是否升级了,然后去下载新版本的驱动。
- 驱动权限:在Linux/macOS系统下,需要给
msedgedriver添加可执行权限:chmod +x msedgedriver。
4.3 元素定位失败:StaleElementReferenceException
在页面刷新或AJAX动态加载后,之前找到的元素引用会“过期”,再次操作就会抛出此异常。
解决方案:
- 重新查找元素:在
try...except块中捕获这个异常,然后在异常处理中重新使用find_element定位一次。 - 使用更稳定的定位策略:优先使用
ID、name,其次是CSS Selector或XPath。避免使用可能随内容变化的文本或索引位置。 - 使用POM(Page Object Model)模式:对于复杂的项目,将页面元素定位和操作封装成类,每次操作前重新获取元素,可以有效管理元素生命周期。
4.4 脚本在后台运行时被系统中断
即使有防熄屏代码,Windows的“睡眠”设置仍然可能中断所有程序。
系统级设置检查:
- 进入控制面板 -> 电源选项 -> 更改计划设置 -> 更改高级电源设置。
- 找到“睡眠”选项,将“经过此时间后睡眠”设置为“从不”。
- 找到“硬盘”选项,将“经过此时间后关闭硬盘”也设置为“从不”或一个很长的时间。
- 对于笔记本电脑,注意区分“接通电源”和“使用电池”两种模式,都要进行设置。
4.5 日志记录与监控
一个健壮的自动化脚本必须有日志。
import logging # 配置日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('auto_hang.log', encoding='utf-8'), logging.StreamHandler() # 同时输出到控制台 ] ) logger = logging.getLogger(__name__) # 在代码中使用logger代替print logger.info("已访问: %s", target_url) try: # some operation pass except Exception as e: logger.error("操作失败,错误信息: %s", e, exc_info=True) # exc_info=True会打印堆栈跟踪良好的日志可以帮助你在脚本长时间运行后,回溯发生了什么,尤其是当它半夜因为未知错误停止时。
4.6 资源管理与优雅退出
脚本可能运行数小时,需要管理好资源。
- 内存泄漏:虽然Selenium会管理大部分资源,但长时间运行后,如果反复创建
driver而不quit(),可能导致内存占用上升。确保异常处理中有driver.quit()。 - 优雅退出:使用
try...finally块确保无论是否出错,最后都能关闭浏览器。try: # 主逻辑 main_hanging_session() except KeyboardInterrupt: logger.info("用户手动终止程序。") except Exception as e: logger.exception("程序发生未捕获异常: ") finally: if 'driver' in locals(): driver.quit() logger.info("浏览器驱动已关闭。") print("程序退出。")
5. 项目扩展思路与优化建议
一个基础的自动挂机脚本完成后,你可以根据实际需求,将其扩展得更加强大和智能。
5.1 多账号与多任务管理
如果需要同时为多个账号挂机,或者在不同网站执行任务,可以考虑:
- 使用多线程或异步IO:为每个账号/任务启动一个独立的线程或异步任务。注意:每个线程需要独立的
webdriver.Edge实例和用户数据目录,避免会话混淆。 - 队列化管理:将任务(如URL、操作步骤)放入队列,由一组工作线程消费执行。
- 配置文件驱动:将账号信息、目标URL、操作步骤等写入JSON或YAML配置文件,脚本读取配置执行,提高可维护性。
5.2 集成任务调度与通知
- 定时启动:结合Windows任务计划程序(Task Scheduler)或Linux的Cron,让脚本在指定时间自动运行。
- 状态监控与通知:使用邮件(
smtplib)、钉钉/企业微信机器人、Telegram Bot等,在脚本启动、完成、出错时发送通知。例如,用requests库调用一个Webhook。
5.3 增加容错与自愈能力
- 网络异常重试:在访问网页或查找元素时,加入重试机制(如使用
tenacity库)。 - 心跳检测:定期检查浏览器窗口是否还存在,页面是否处于预期状态(如登录态是否保持)。如果发现异常,自动执行恢复流程(如重新登录)。
- OCR辅助:对于有验证码的网站(虽然本项目初衷是避开),可以集成OCR库(如
ddddocr、pytesseract)进行简单识别,但复杂验证码仍需人工或更高级方案。
5.4 性能与隐身优化
- 禁用图片和CSS:如果页面加载速度是瓶颈,可以添加参数加速。
prefs = {"profile.managed_default_content_settings.images": 2} # 2为禁用 edge_options.add_experimental_option("prefs", prefs) - 使用代理IP:如果需要,可以在
Options中配置代理服务器,但务必遵守法律法规和网站规定。 - 指纹混淆:研究并使用像
undetected-chromedriver这样的第三方库,它专门用于绕过自动化检测,但请注意其可能违反某些网站的服务条款。
最后,我必须再次强调自动化脚本的使用伦理。这个技术本身是中立的,但请务必将其用于合法的、符合目标网站服务条款的场景。用于学习、测试或者完成那些允许自动化的重复性工作是完全没问题的。尊重网站服务器的负载,在随机等待时间上设置得宽松一些,避免过于频繁的请求。技术是为了提高效率,而不是制造麻烦。在实际操作中,从简单的页面开始测试,逐步增加复杂性,耐心调试,你就能打造出一个稳定可靠的自动化助手。