news 2026/9/28 14:19:22

Python Selenium实战:从零搭建到动态网页数据采集

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python Selenium实战:从零搭建到动态网页数据采集

1. 为什么会选择Selenium:requests做不到的事

1.1 从一次数据采集翻车说起

我之前一直习惯用Python写requests采集脚本,接口直接返回JSON,速度快、逻辑清爽。直到有一天,我需要抓一个数据报表页面,打开网页源码一看,里面根本没有数据,只有一个空的div容器。真正的数据是页面加载之后,由一段JavaScript去后端拉取、再通过DOM操作渲染出来的。requests拿到的是“毛坯房”,而你要的数据是“精装修”之后才出现的。

那段时间我试过自己解析接口参数,翻Network面板找XHR请求,结果发现请求头里带了一个滑动签名,每次生成的逻辑都不一样。纯靠requests模拟,成本越滚越高。后来我一个做测试的朋友提了一嘴:“你为什么不直接用浏览器跑呢?”这句话点醒了我。Python Selenium能把真实的浏览器拉起来,像人一样打开URL、输入文本、点击按钮、翻页,再把渲染完的网页内容交给你。浏览器自动化这个词听起来很高端,本质其实就是“让代码替你把键盘和鼠标干了”。

这篇内容包括环境搭建、元素定位、等待机制、综合实战和反爬边界,我把从零摸索过程中踩过的坑都写进去了。适合刚接触Python、想用Selenium做网页自动化、写采集脚本或者跑UI自动化验证的新手。如果你只是想要一个能跑通的demo,可以直接跳到第五节;如果你想少走弯路,建议从头到尾读一遍。

1.2 Selenium和requests的本质区别

很多人搞不清Selenium和requests到底该用哪个,其实它们的定位完全不同。requests是HTTP客户端,它模拟的是浏览器向服务器发送请求、接收响应,但它不负责解析JavaScript、不渲染页面。Selenium是浏览器自动化框架,它通过WebDriver协议去驱动真实的浏览器内核,Chrome打开页面之后所有的脚本都执行完了,你再拿到的DOM就是用户真正看到的内容。

对比维度requestsSelenium
工作原理发送HTTP请求拿到HTML/JSON驱动真实浏览器执行完整页面
处理JS动态内容需要自己找接口、模拟签名天然支持,页面渲染完再读取
运行速度快,毫秒级慢,秒级起步
资源开销低高,每个driver都占内存
适用场景公开API、静态页、接口调试动态渲染、表单操作、UI验证

用生活化的类比:requests像一个派去取快递的人,他只负责把包裹拿回来,不管里面是什么材质、怎么包装;Selenium则像个跑腿小哥,他会帮你一层层拆开包装、打开盒子、把里面的东西摆好再拍照发给你。前者轻快,但只能拿到表面的东西;后者笨重,但能触及最终形态。

选择上我的经验是:能直接抓到接口就优先requests,速度和稳定性都更好;一旦发现数据是JS动态渲染、需要登录态维持、有复杂交互操作,就别硬刚,直接上Selenium,你会发现世界一下子清净了。

1.3 Selenium适合谁、不适合谁

我知道很多人是因为采集动态页面才认识Selenium的,但它能做的事远不止这些。我身边有做测试的朋友用它跑回归用例,每天晚上自动登录系统、点击菜单、校验数据;有做运营的朋友用它定时巡检竞品公开页面,抓价格变化;还有做数据分析的同事,用它把内部报表系统的导出操作自动化,省去了每天手动点十几次按钮的重复劳动。

但Selenium不是万能的。如果你的目标是每天采集几百万条公开数据,用Selenium会很吃力——开浏览器占内存、网络开销又大,远不如用官方API或者接口采集划算。还有一些场景,比如验证码识别、极验滑块这类对抗型需求,我的个人建议是不要碰,技术上折腾成本非常高,法律风险和道德风险也不可控。Selenium最舒适的区间是“低频、中等规模、强交互”的自动化任务。

2. 环境配置中的三个大坑:Python、pip和WebDriver版本匹配

2.1 安装Python与Selenium库

搭建环境的第一步是装Python。这里有一个非常常见的问题:很多新手直接去官网下载了最新版Python,结果在终端敲python命令,弹出来的是Windows应用商店的跳转页面,或者系统自带了Python但版本特别老。我的建议是去python.org下载官方安装包,安装时务必勾选“Add Python to PATH”,否则后面pip和python命令都会找不到。装完之后在命令行里跑:

python --version

能正常打印版本号说明环境没问题。接下来安装Selenium,命令非常简单:

pip install selenium

如果你用的是Python 3.12以上的版本,或者之前装过旧版本,我建议顺手升级一下:

pip install --upgrade selenium

顺便说一句,现在Selenium已经出到4.x版本了。4.x和3.x在API上有一个很大的区别:以前写find_element_by_id这种老方法已经废弃,统一改成了driver.find_element(By.ID, "xxxx"),所以你搜索中文教程时如果看到旧写法,最好手动替换成新写法,否则会有DeprecationWarning,虽然暂时能跑,但总有天会被移除。

2.2 WebDriver版本匹配:新手卡关重灾区

装好selenium之后,你写的第一段代码大概率长这样:

from selenium import webdriver driver = webdriver.Chrome() driver.get("https://www.baidu.com") print(driver.title) driver.quit()

然后你运行,结果报了一堆红字,最常见的是:

selenium.common.exceptions.SessionNotCreatedException: This version of ChromeDriver only supports Chrome version 116

这个报错的意思很明确:你下载的ChromeDriver和本机Chrome版本对不上。WebDriver本质上是一个翻译官,它负责把Selenium的命令翻译成浏览器能听懂的操作。如果翻译官和浏览器的语言版本不一致,整个通信就会失败。很多新手在这里心态直接崩了,以为是自己代码写错了,折腾半天发现是版本问题。

解决办法分两步。第一步,打开Chrome菜单里的“关于Chrome”,看当前版本号,比如你看到的是133.0.6943.126,那就要找对应主干版本133的ChromeDriver。第二步,去ChromeDriver的下载页面(或者国内镜像站)下载同版本的zip包,把里面的chromedriver.exe放到一个固定目录,然后在代码里指定路径:

from selenium import webdriver from selenium.webdriver.chrome.service import Service service = Service(r"D:\tools\chromedriver\chromedriver.exe") driver = webdriver.Chrome(service=service)

这里还藏着一个小细节:很多教程让你把chromedriver.exe放到和Python同一个目录,或者加到系统PATH里。这么做不是不行,但多个项目共用一个driver,只要版本一更新,全部脚本跟着遭殃。更稳妥的做法是每个项目单独维护一个driver/目录,版本锁定,互不干扰。

2.3 更省心的WebDriver管理方案

手动画版本匹配确实麻烦,而且Chrome有时候会悄悄自动升级,版本一升你的driver就废了。这里给你推荐一个我一直在用的库:webdriver-manager。安装一行命令:

pip install webdriver-manager

然后代码变成这样:

from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager service = Service(ChromeDriverManager().install()) driver = webdriver.Chrome(service=service)

它会自动检测你本机Chrome的版本,去下载匹配的ChromeDriver,然后在本地做缓存。第二次启动时如果浏览器没升级,就直接用缓存,省去重复下载。这个库解决的不只是Chrome,Firefox、Edge也能管。

另外提一嘴,Selenium 4.6以上版本内置了Selenium Manager,理论上你什么都不配置直接webdriver.Chrome()就能自动找driver。但有坑——首次运行时它要去官方仓库下载,国内网络经常卡住。所以我给你的后备方案仍然是手动指定Service路径,两条路都要会,出了问题才不会无路可走。

3. 定位元素的底层逻辑:8种定位方式怎么选

3.1 八种定位方式概览

Selenium所有操作的前提是先找到元素。官方最常用的定位方式一共有八种,对应关系如下:

定位方式写法适用场景
IDBy.ID元素有唯一id,优选方案
NAMEBy.NAME表单输入框的name属性
CLASS_NAMEBy.CLASS_NAME使用class属性定位
TAG_NAMEBy.TAG_NAME按标签名如div、a定位
CSS_SELECTORBy.CSS_SELECTOR灵活、速度快,推荐掌握
XPATHBy.XPATH万能兜底,支持文本和层级
LINK_TEXTBy.LINK_TEXT精确匹配链接文字
PARTIAL_LINK_TEXTBy.PARTIAL_LINK_TEXT模糊匹配链接文字

如果你用Chrome的开发者工具(F12)审查元素,会发现每个页面的HTML结构都不一样,但大多数情况下你只需要掌握三个:ID、CSS_SELECTOR、XPATH。ID是最简单的,但很多元素没有id;CSS_SELECTOR简洁高效;XPATH虽然慢一点,但表达能力强到没有边界。我的习惯是:能用ID就用ID,否则用CSS,只有面临复杂结构时才上XPATH。

3.2 一个能少走弯路的定位器编写顺序

很多新手一上来就照抄浏览器“复制XPath”功能,结果代码看起来一团乱麻,稍微改版就跑不起来。比如Chrome复制出来可能是这种:

//*[@id="app"]/div[2]/div/div[1]/div/div[2]/form/div[1]/div/div/span/input

这种绝对路径,中间任何一层结构变动,定位就失效。我自己写定位器时遵循下面几条规则:

第一,优先选靠近实际功能的属性。比如一个搜索框,它的id或者placeholder往往比它外层div的class更有意义。你定位的是“搜索框”这个操作对象,不是它的祖辈容器。

第二,避免依赖索引数字。div[2]这种写法很脆弱。如果页面在上面新增了一个块,索引就变了,脚本立马崩。尽量用能表达特征的路径,比如:

# 差的做法 driver.find_element(By.XPATH, '//form/div[1]/div/input') # 好的做法 driver.find_element(By.XPATH, '//input[@placeholder="请输入关键词"]')

第三,文字内容谨慎使用。页面上的文案经常变动,而且按钮文案、链接文字对国际化不友好。能用属性定位,就不用文本内容。

第四,学会用contains()和and解决动态属性。很多元素的id里带了随机数,比如user_abc123,这时候可以写成:

driver.find_element(By.XPATH, '//button[contains(@id, "user_") and contains(@class, "submit")]')

这些规则看着简单,但能帮你省掉后面大量的维护时间。

3.3 用百度首页做一次定位实战

我们拿百度首页练练手。打开首页,F12审查搜索框,可以看到它的id是kw,搜索按钮的id是su。于是“搜索Python”这个动作可以写成:

from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys driver = webdriver.Chrome() driver.get("https://www.baidu.com") search_box = driver.find_element(By.ID, "kw") search_box.send_keys("Python") search_box.send_keys(Keys.ENTER) # 等待页面加载 driver.implicitly_wait(3) print(driver.title) driver.quit()

如果你不想用id,也可以换成CSS选择器#kw,或者XPATH//input[@id="kw"]。三种写法效果一样,选哪种纯粹是个人习惯。我推荐你至少熟悉CSS和XPATH各一种写法,将来遇到没有id的页面才能灵活应付。

这里还有一个新手特别容易犯的错:send_keys是在输入框内输入内容,但如果你先点了其他地方再回来输入,焦点会丢。稳妥的做法是输入前先click一下输入框。有些页面还有默认占位内容,最好先清空再输入:

search_box.click() search_box.clear() search_box.send_keys("Python")

4. 等待的艺术:为什么脚本总在半夜才报错

4.1 三种等待方式,别再用sleep糊弄

Selenium脚本最常见的问题就是“时好时坏”:今天跑得好好的,明天报找不到元素;在家跑得好好的,到公司就崩。99%的原因是页面加载是异步的,你的代码却没等它加载完。新手最容易犯的错是到处乱加time.sleep(2),靠固定时间碰运气。网络慢的时候2秒不够,网络快的时候白白浪费2秒。

官方给的方案有三层:

# 1. 隐式等待:设定一个上限,轮询查找元素 driver.implicitly_wait(10) # 2. 显式等待:针对特定条件等待 from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC wait = WebDriverWait(driver, 10) element = wait.until(EC.presence_of_element_located((By.ID, "kw"))) # 3. 强制等待:能不用就不用 time.sleep(2)

implicitly_wait就像一个默认超时时间,每次你调用find_element时,如果元素没立刻出现,它会最多等10秒。优点是省事,但隐藏的问题是:它会同时作用于所有查找操作,某些情况反而掩盖了真正的错误,影响调试。

WebDriverWait才是工程上最推荐的方式。它结合expected_conditions可以非常精确地表达需求,比如等待元素“可见”用visibility_of_element_located,等待按钮“可以点击”用element_to_be_clickable。这种做法的核心不是磨时间,而是“等到指定条件满足再继续”,网络慢就多等,网络快就少等,效率最高。

4.2 点击无效和找不到元素的真实原因

写Selenium的日子里,我总结出点击“无效”的几大原因,基本都是玄学问题的根源:

第一种,页面加载了但目标元素被遮挡。这种情况常常发生在弹窗、广告、浮层盖住了按钮。即使你定位到了元素,点击事件实际点在了别的元素上。解决方法是先关掉浮层,或者模拟键盘操作。

第二种,元素在iframe框架里。你直接driver.find_element找不到它,必须先切换进去:

driver.switch_to.frame("frame-name") # 操作里面元素 driver.switch_to.default_content()

第三种,元素本身是隐藏的。有些网站用display:none控制元素,等用户操作后才显示。通过By.XPATH能找到但不可见,此时可以考虑先操作它的触发条件。

第四种,页面发生了跳转或局部刷新,你之前持有的元素引用已经失效了。遇到这种就重新定位一次,别一直攥着旧引用不放。

调试这类问题我在实践中最喜欢用的方法是在定位前打印当前页面源码,然后搜索目标特征,可以快速定位是“元素没出现”还是“元素不可操作”。

4.3 把等待封装成工具函数

与其在每一段业务代码里写重复的WebDriverWait,不如抽出一个工具模块。这是我封装过的一套常用等待函数,分享出来直接抄:

from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def wait_element(driver, by, value, timeout=10): """等待元素出现在DOM中(不要求可见)""" return WebDriverWait(driver, timeout).until( EC.presence_of_element_located((by, value)) ) def wait_clickable(driver, by, value, timeout=10): """等待元素可见且可点击""" return WebDriverWait(driver, timeout).until( EC.element_to_be_clickable((by, value)) ) def wait_visible(driver, by, value, timeout=10): """等待元素可见""" return WebDriverWait(driver, timeout).until( EC.visibility_of_element_located((by, value)) ) def safe_click(driver, by, value, timeout=10): """安全点击:先等待可点击,再滚动到位,再点击""" element = wait_clickable(driver, by, value, timeout) driver.execute_script("arguments[0].scrollIntoView();", element) element.click() return element

我在实际项目中基本只用safe_click和wait_visible这两个函数。有了它们,脚本的稳定性会大幅提升。关键是理解一个点:等待不是时间长度的博弈,而是条件的博弈。你等待的一定是一个“能表明页面确实进入期望状态”的信号,而不是一个固定秒数。

5. 综合实战:一个完整的登录-翻页-采集流程

5.1 演示站点与流程拆解

理论讲太多容易飘,我拿两个公开的练习站点演示完整流程。登录部分用SauceDemo,这是一个专门给UI测试使用的演示商城,账号密码都是公开的,随便造,很适合练手。翻页采集部分用BooksToScrape,一个专门给爬虫练习的书籍列表站,有50页数据。

整个流程拆成四步:

  1. 打开登录页,输入用户名和密码,点击登录按钮
  2. 等待登录后页面元素出现,确认登录成功
  3. 进入列表页,循环采集当前页数据,点击“下一页”
  4. 把采集结果输出到CSV文件

幂等性很重要:每一步都在上一个状态的确定结果上继续,绝不靠猜。下面一步步来。

5.2 登录模块:用SauceDemo演示账号流程

登录页的地址是https://www.saucedemo.com,账号standard_user,密码secret_sauce。核心代码如下:

from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from utils import wait_clickable, wait_visible # 使用前面封装好的工具 service = Service(ChromeDriverManager().install()) driver = webdriver.Chrome(service=service) driver.implicitly_wait(5) driver.get("https://www.saucedemo.com") # 输入账号密码 username = wait_visible(driver, By.ID, "user-name") username.send_keys("standard_user") password = wait_visible(driver, By.ID, "password") password.send_keys("secret_sauce") # 点击登录 wait_clickable(driver, By.ID, "login-button").click() # 验证是否登录成功:等待商品列表标题出现 wait_visible(driver, By.CLASS_NAME, "inventory_container") print("登录成功,当前标题为:", driver.title)

这里有个要点:登录按钮在DOM里一直存在,但只有表单填完后它才真正可以点击,所以用wait_clickable比wait_visible更合适。验证登录成功我选择等待商品容器出现,而不是等几秒再看URL,因为等待真正的结果元素才是最可靠的。

如果发现自己脚本登录不上去,第一件事是打开一个真实浏览器手动走一遍流程,看看有没有弹窗、验证码、新手指引之类干扰元素。Selenium登录失败九成都是被这类“人类验证”卡住了。

5.3 翻页与采集:BooksToScrape数据抓取

登录这个环节在SauceDemo上跑通之后,我们来到BooksToScrape实现翻页采集。这个站的页面结构非常稳定,每个书籍条目都在article.product_pod里,书名在h3 > a的title属性,价格在p.price_color,翻页按钮是li.next > a。

核心循环代码:

import csv import time from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager service = Service(ChromeDriverManager().install()) driver = webdriver.Chrome(service=service) driver.implicitly_wait(5) driver.get("https://books.toscrape.com") with open("books.csv", "w", newline="", encoding="utf-8") as f: writer = csv.writer(f) writer.writerow(["书名", "价格"]) while True: # 采集当前页数据 books = driver.find_elements(By.CSS_SELECTOR, "article.product_pod") for book in books: title = book.find_element(By.CSS_SELECTOR, "h3 a").get_attribute("title") price = book.find_element(By.CSS_SELECTOR, "p.price_color").text writer.writerow([title, price]) print(f"已抓取:{title} - {price}") # 找下一页按钮 next_buttons = driver.find_elements(By.CSS_SELECTOR, "li.next a") if not next_buttons: break next_buttons[0].click() time.sleep(1) # 等待新一页数据渲染 driver.quit() print("采集完成")

这里我想特别说一下while True这种循环写法。它靠“找不到下一页按钮就break”结束,而不是提前硬编码页数。这样做的好处是,如果站点临时增加了新数据页,脚本会自动多翻几页,不用改代码。

采集逻辑里我故意每页结束后加了time.sleep(1),理由不是为了等加载,而是避免请求太快给服务器压力。实际生产中这个延时应该做成随机的,比如random.uniform(0.8, 1.5)。

5.4 异常兜底和日志输出

跑单页demo很容易,真正难的是脚本在无人值守环境下连续跑几小时不出问题。我在实践中总结了一套兜底机制:

import logging import traceback logging.basicConfig( level=logging.INFO, filename="auto_run.log", format="%(asctime)s %(levelname)s %(message)s" ) def safe_run(): driver = None try: # driver初始化与业务逻辑 pass except Exception as e: logging.error(f"发生异常:{e}") if driver: # 出问题时候截图永远是最有用的排查手段 driver.save_screenshot("error.png") logging.info("已保存错误截图 error.png") finally: if driver: driver.quit() if __name__ == "__main__": safe_run()

日志的作用是让你第二天早上复盘时知道脚本到底死在哪一步。截图则是直接把当时的页面状态保留下来。我的习惯是在每一个关键节点都打一行日志,比如“开始登录”“登录成功,开始翻页”“当前第3页采集完成”。这样一旦挂了,看日志就知道是哪个环节出的问题,不用靠猜。

另外,整段逻辑里千万不要遗漏driver.quit()。开发调试的时候没感觉,一旦挂后台跑,浏览器进程会越积越多,最终把内存吃光。

6. 关于反爬,新手最容易踩的边界

6.1 网站凭什么判断你是Selenium

很多新手在公开网站上采集数据时都会遇到一个问题:明明用的是Chrome,为什么站点好像知道你是自动化脚本?这里面的核心机制之一就是浏览器暴露的自动化标记。

就拿Chrome来举例,Selenium启动的浏览器进程里,navigator.webdriver属性默认会被设为true。普通用户手动打开的浏览器,这个值是false或者不存在。很多站点会在前端脚本里检查这个标记,一旦发现就认为你不是真人。除此之外,还有一套更隐蔽的检测维度:浏览器指纹和用户行为模型。比如你的鼠标有没有移动轨迹、点击速度是不是均匀、滚动页面的时候是不是一滑到底、两次访问的间隔是不是精确到秒,这些在服务器端都能形成特征。

我在做合规采集时也研究过这些知识,但这里要特别提醒一句:理解反爬机制的初衷,是为了让你知道哪些事不该做,而不是教你破解。尤其是涉及登录账号、验证码、付费内容的站点,任何“绕过检测”的手段在法律和平台条款上都有风险。把技术用在合法授权的数据采集上才是正路。

6.2 合规操作的正确姿势

所谓合规,我个人的判断标准就三条:第一,目标网站允许爬虫访问公开数据,或者你已经获得了授权;第二,遵守网站的robots.txt;第三,采集行为不会给对方服务器造成明显压力。在这个前提下,你可以通过下面这些正常途径提高脚本的稳定性,而不是去对抗站点的风控。

最简单也是最有效的是限制频率。把上面那个time.sleep(1)改成随机延时:

import random time.sleep(random.uniform(1, 3))

随机延时的意义在于模拟人类操作的自然间隔,也避免固定节奏被服务端统计成特征。另外可以设置更真实的User-Agent和浏览器窗口大小:

options = webdriver.ChromeOptions() options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36") options.add_argument("window-size=1920,1080")

还有一个我一直在用的好习惯:在采集之前,先把页面里的图片加载关掉、禁用JavaScript之类的操作可以大幅提升性能且减少指纹暴露面。不过这类优化要看具体场景,如果网站用JS渲染数据就绝对不能关JavaScript。

6.3 一个小方法检查自己是否“可被识别”

如果你想验证自己的Selenium脚本在目标网站上是否容易被识别,可以直接在浏览器控制台里看一个值。我用一段很小的检测脚本帮新手理解这件事:

driver.get("https://example.com") result = driver.execute_script("return navigator.webdriver;") print(result) # 大多数默认配置下会输出 True

如果输出True,说明浏览器把这个自动化标记暴露给了网页。但这并不意味着你要去伪造它,而是说:你知道了站点有办法识别自动化脚本,做任何采集前都必须评估风险。我的建议是,碰到反爬严格的网站,优先去找官方API,或者看看有没有授权接口。如果都没有,那就换一个数据源,不要跟反爬系统硬碰硬——时间和精力花在正经渠道上,回报率高得多。

7. 我在无数次失败后才想明白的几件事

7.1 无头模式不是免费的午餐

很多采集脚本追求“无头模式”,就是不显示浏览器窗口,在后台悄悄跑。听起来很美,既像黑客又省资源,但实际用下来坑很多。某些网站会根据浏览器是否支持无头模式返回不同内容,甚至直接拒绝服务。Selenium的旧版无头模式还有一个问题:它和真实有头模式在navigator对象上有细微差异,更容易被站点识别。

如果你确实需要无头运行,新版Chrome已经支持了--headless=new模式,Selenium 4里可以这样写:

options = webdriver.ChromeOptions() options.add_argument("--headless=new") driver = webdriver.Chrome(options=options)

但我的建议是:开发调试阶段千万不要开无头。你盯着真实浏览器跑一遍,能看到它每一步在干什么、卡在哪了、弹了什么窗,排错效率会高很多。等你把脚本调稳了,再决定要不要用无头模式上生产环境。

7.2 浏览器资源管理与程序退出

Selenium在单机上的资源消耗比大多数人想象得大。一个Chrome实例基本占200到500MB内存,如果你为了省时间开了多个浏览器实例并发采集,16GB内存也架不住几个进程一起跑。而且ChromeDriver的进程在崩溃之后经常残留,不手动杀掉,第二天你会发现系统莫名其妙卡顿。

为了保证程序正常退出,我强烈建议你用try/finally或者with语句包住主流程。更简单粗暴的办法是,在脚本最后写:

driver.quit()

但注意,quit()和close()是两回事。close()只关闭当前标签页,窗口还在;quit()才是关闭整个浏览器并清理driver进程。排错时如果发现进程残留,就检查一下自己是不是写成了close()。Windows下实在杀不掉,就任务管理器手工处理,或者用taskkill /F /IM chromedriver.exe。

7.3 Selenium之外,你还可以关注什么

浏览器自动化这个方向不只Selenium一个选项。如果你现在还是零基础,学Selenium入门完全够用,知识点通用。等你熟练了,可以考虑去看Playwright和Puppeteer。Playwright的优势是API设计更现代、支持多浏览器、自动等待做得更好,安装时还会自动下载适配的浏览器内核,省去了WebDriver匹配的烦恼。但Playwright的生态相对年轻,中文资料没Selenium多。

我的看法是:工具永远是服务于业务的,先把手头问题解决,再去追新。Selenium学了之后转Playwright并不会浪费,因为定位、等待、浏览器原理这些都是通用的。浏览器自动化的核心从来不是某个库的API背得有多熟,而是你愿不愿意在真实环境里反复调试、把细节抠明白。

最后分享一个小技巧,如果某天你的脚本在日本网站上跑出乱码,或者CSV文件用Excel打开中文全变了,多半是编码问题。写文件时养成好习惯:encoding="utf-8",读网页时用driver.page_source后再配合BeautifulSoup解析,效果往往比纯Selenium遍历DOM更清晰。学到这里,你已经能写一个像样的浏览器自动化脚本了,接下来就是多练、多踩坑、多总结,没有别的捷径。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 14:18:21

深度强化学习Q-Learning优化协作认知无线电频谱接入决策

简介:面向通信与网络方向的深度强化学习应用资源,聚焦Q-Learning算法在协作认知无线电网络中的频谱分配与决策建模,适合正在研究动态频谱接入、智能无线网络的研究生或工程师进行算法复现与代码参考。资源包共14个文件,以12个Matl…

作者头像 李华
网站建设 2026/9/28 14:17:40

泛修饰抗体揭秘癌症调控“共同密码”:原理、实验与临床前景

泛修饰抗体在癌症研究里已经不算新鲜词,但绝大多数人刚接触时都会犯同一个错——把它当成普通单点抗体的“加强版”来用。我最早做泛乙酰化抗体免疫沉淀时也这样,以为无非是识别位点更多、信号更强,结果实验做出来一团糊,背景高得…

作者头像 李华
网站建设 2026/9/28 14:17:00

API计费机制与模型蒸馏攻击防御

我无法根据提供的输入内容生成符合要求的博文。原因如下:输入中缺少关键必要字段:按照你设定的严格输入格式,必须包含以下四项完整内容:项目标题: [标题] 项目正文: [原始描述] 关键词: [关键词1, 关键词2, ...] 摘要描述: …

作者头像 李华
网站建设 2026/9/28 14:16:57

51单片机软串口对接LU-ASR01语音模块:实现双向通信完整方案

做语音控制类项目的人,应该都遇到过这个尴尬:51单片机只有一个硬件串口,接了下载器就没法同时接语音模块,想接多个设备只能硬切引脚来回折腾。我这次用LU-ASR01语音识别模块做离线语音控制,一开始也被这个问题卡住了&a…

作者头像 李华
网站建设 2026/9/28 14:15:59

AWE2026德施曼智能锁全解析:从3D人脸到掌静脉的AI进化

1. AWE2026现场的智能锁热区,德施曼凭什么成了“顶流打卡地”1.1 第一眼的直观感受:人墙、排队、和满墙的黑科技今年AWE2026我一进展馆,其实最先感受到的不是某个单品,而是整个智能锁展区的空气温度。德施曼的展位大概从上午十点开…

作者头像 李华