Selenium+Python爬虫实战:从环境搭建到高级技巧全解析

发布时间:2026/7/29 6:32:05
Selenium+Python爬虫实战:从环境搭建到高级技巧全解析 1. 从“点击”到“数据”为什么SeleniumPython是爬虫的终极利器如果你尝试过用requests和BeautifulSoup写爬虫大概率会遇到过这样的场景目标网站的数据在你用浏览器访问时明明就在那里但用代码一抓返回的要么是空页面要么是一堆看不懂的JavaScript代码。你检查了请求头加了Cookie甚至模拟了Ajax请求但数据就像跟你捉迷藏一样怎么也抓不到。这时候一个念头就会冒出来要是能让代码像真人一样去操作浏览器看到什么就抓什么该多好这个念头就是SeleniumPython爬虫方案的核心价值。它不再局限于传统的HTTP请求-响应模式而是直接驱动一个真实的浏览器比如Chrome去访问网页执行页面上的JavaScript渲染出完整的DOM树然后你再像在浏览器开发者工具里一样精准地定位和提取数据。简单来说它把爬虫从“网络协议层”提升到了“用户交互层”。我最初接触这个组合就是为了搞定一个需要先登录、再点击多个选项卡、最后在动态表格里翻页查询数据的内部系统。用传统方法几乎无从下手但用Selenium我写出的脚本就像有个隐形的员工在帮我操作电脑稳定运行了半年多抓取了上百万条记录。这套方案特别适合三类人一是爬虫新手面对复杂网站无从下手时Selenium提供了最直观的“所见即所得”的解决方案二是需要处理大量动态内容如由React、Vue等框架构建的单页应用的数据采集者三是需要模拟完整用户行为流登录、搜索、翻页、下单的自动化测试或业务流模拟工程师。它可能不是最快的方案但在成功率和易用性上往往是那个最可靠的“保底”选择。2. 环境搭建别在第一步就踩坑万事开头难环境配置是劝退很多新手的第一个门槛。网上教程五花八门但很多都忽略了版本兼容性这个致命细节。我见过太多人照着过时的教程安装了最新版的Selenium和Chrome浏览器结果代码一跑就报错折腾半天找不到原因。所以我们的第一步必须稳扎稳打。2.1 Python与Selenium库安装首先确保你有一个Python环境。我个人推荐使用Python 3.8或3.9版本这两个版本在生态兼容性上最为成熟稳定。安装Selenium库非常简单打开你的命令行CMD或终端使用pip命令即可pip install selenium这里有个关键点不要盲目追求最新版本。截至我写这篇文章时selenium 4.x是主流版本其API相比3.x有一些变化但核心用法一致。我们以4.x版本为基础进行讲解如果你安装的是3.x大部分代码也兼容但部分导入语句或选项设置可能需要微调。2.2 浏览器驱动的“配对”哲学这是Selenium工作的核心也是最容易出错的地方。Selenium库本身只是一个发出指令的“遥控器”它需要另一个叫做“浏览器驱动”的程序来实际控制浏览器。这个驱动必须和你的浏览器主程序版本严格匹配。以Chrome为例你需要的是ChromeDriver。查看你的Chrome版本打开Chrome浏览器点击右上角三个点 - 帮助 - 关于Google Chrome。记下版本号例如109.0.5414.120。下载对应版本的ChromeDriver访问ChromeDriver的官方下载站或国内镜像站。找到与你的Chrome版本号完全一致的驱动进行下载。如果官网只有接近的版本比如你是109.0.5414.120但官网只有109.0.5414.*通常小版本号差异可以兼容但最好还是寻找完全一致的。放置驱动并配置路径下载后是一个可执行文件如chromedriver.exeon Windows。你有两种方式让Python找到它方法A推荐尤其对新手将这个驱动文件直接放在你的Python脚本所在的同一个目录下。Selenium会优先在当前目录查找。方法B一劳永逸将驱动文件放在某个固定目录如C:\WebDriver\或/usr/local/bin然后将该目录路径添加到系统的环境变量PATH中。注意很多教程会教你用webdriver-manager这类库自动管理驱动这对于快速测试和学习是方便的。但在生产环境或需要长期稳定运行的脚本中我强烈建议手动指定确定版本的驱动。自动下载可能因为网络问题失败也可能在你不知情时更新到不兼容的版本导致线上任务突然崩溃。2.3 编写你的第一个“Hello World”脚本环境就绪我们来写一个最简单的脚本打开百度并搜索一个关键词。创建一个新的Python文件比如first_crawl.py。from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys import time # 1. 创建浏览器驱动对象这将启动一个Chrome浏览器窗口 driver webdriver.Chrome() # 如果chromedriver不在当前目录或PATH需要指定路径webdriver.Chrome(executable_path你的路径/chromedriver) # 2. 打开百度首页 driver.get(https://www.baidu.com) # 3. 等待页面加载一下实际项目中会用更智能的等待方式这里先用time.sleep time.sleep(2) # 4. 定位搜索框。通过检查元素发现搜索框的id是kw search_box driver.find_element(By.ID, kw) # 5. 在搜索框中输入文本 search_box.send_keys(Selenium自动化测试) # 6. 模拟按下回车键进行搜索 search_box.send_keys(Keys.RETURN) # 7. 等待搜索结果加载 time.sleep(3) # 8. 打印当前页面的标题 print(当前页面标题是, driver.title) # 9. 关闭浏览器窗口 driver.quit()运行这个脚本你会看到一个Chrome浏览器窗口自动打开访问百度输入关键词并搜索最后在控制台打印出标题然后关闭。恭喜你已经成功迈出了第一步这个脚本包含了Selenium最核心的几个操作启动驱动、访问URL、定位元素、模拟输入、模拟键盘事件、获取页面属性、关闭驱动。3. 核心操作详解像侦探一样定位元素Selenium爬虫的绝大部分工作都可以归结为“定位元素”和“与元素交互”。网页上的每一个按钮、输入框、链接、文本块都是一个HTML元素。我们的脚本要做的就是找到它们然后点击、输入或读取。3.1 八种定位器找到元素的“钥匙”Selenium提供了8种主要的定位方式通过By类来调用。掌握它们是你精准抓取数据的基础。from selenium.webdriver.common.by import By # 假设 driver 已经创建并打开了一个网页 # 1. 通过ID定位 (最优先选择通常唯一且稳定) element driver.find_element(By.ID, “login-button”) # 2. 通过NAME定位 (常用于表单元素) element driver.find_element(By.NAME, “username”) # 3. 通过CLASS_NAME定位 (注意class可能有多个用空格分隔这里只能匹配其中一个) element driver.find_element(By.CLASS_NAME, “btn-primary”) # 4. 通过TAG_NAME定位 (如 ‘input’, ‘a’, ‘div’通常不够精确需结合其他条件) element driver.find_element(By.TAG_NAME, “h1”) # 5. 通过LINK_TEXT定位 (精确匹配链接的完整可见文本) element driver.find_element(By.LINK_TEXT, “忘记密码”) # 6. 通过PARTIAL_LINK_TEXT定位 (匹配链接文本的一部分) element driver.find_element(By.PARTIAL_LINK_TEXT, “密码”) # 7. 通过CSS_SELECTOR定位 (功能强大语法灵活是主力定位方式) element driver.find_element(By.CSS_SELECTOR, “#content .list-item:nth-child(2)”) # 定位id为content下第二个list-item类的元素 # 8. 通过XPATH定位 (功能最强大但语法相对复杂可以遍历XML/HTML文档树) element driver.find_element(By.XPATH, “//div[id‘main’]//a[contains(text(), ‘下一页’)]”) # 定位id为main的div下文本包含“下一页”的a标签定位策略心得优先级IDNAMECSS_SELECTORXPATH 其他。ID和NAME通常是开发人员赋予的具有语义的标识最稳定。如果都没有CSS Selector通常是首选因为它性能好且语法简洁。避免脆弱的定位不要使用绝对路径的XPATH如/html/body/div[3]/div[2]/div[4]页面结构稍有变动就会失效。尽量使用相对路径和属性组合。使用浏览器开发者工具这是你最好的朋友。在网页上右键“检查”可以查看元素的HTML代码。在“Elements”面板中右键某个元素选择“Copy” - “Copy selector”或“Copy XPath”可以快速获取定位表达式但需要人工校验其稳定性。3.2 元素交互模拟真实用户操作找到元素后我们就可以与之交互了。最常用的操作如下# 点击元素按钮、链接、复选框等 element.click() # 在输入框内输入文本会先清空原有内容 element.send_keys(“你要输入的文字”) # 清空输入框内容 element.clear() # 获取元素的文本内容即用户看到的文字 text element.text print(text) # 获取元素的某个属性值如href, src, value等 link_url element.get_attribute(“href”) print(link_url) # 判断元素是否可见、是否可点击、是否被选中用于复选框、单选框 is_displayed element.is_displayed() is_enabled element.is_enabled() is_selected element.is_selected()3.3 等待的艺术告别time.sleep在我们第一个脚本中使用了time.sleep(3)来等待页面加载。这是显式等待固定等待简单但低效。如果页面2秒就加载完了你白白等了1秒如果网络慢5秒才加载完你的代码就会因为找不到元素而报错。Selenium提供了两种更优雅的等待方式隐式等待 (Implicit Wait)为整个driver会话设置一个全局的等待时间。当查找元素时如果元素没有立即出现WebDriver会轮询DOM一段时间直到找到元素或超时。driver.implicitly_wait(10) # 单位秒 # 此后所有 driver.find_element 操作都会最多等待10秒显式等待 (Explicit Wait)针对某个特定条件进行等待更加灵活和精确。这是生产环境推荐的做法。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 等待直到ID为‘result’的元素出现在DOM中并且可见最多等10秒每0.5秒检查一次 wait WebDriverWait(driver, 10, poll_frequency0.5) result_element wait.until(EC.visibility_of_element_located((By.ID, “result”))) # 等待直到某个元素可以被点击 button wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, “.submit-btn”))) button.click() # 等待直到页面标题包含特定文字 wait.until(EC.title_contains(“搜索结果”))expected_conditions模块提供了很多预置条件如元素是否存在、是否可见、是否可点击、文本是否出现等。这能确保你的代码在元素真正就绪时才进行操作极大提高了稳定性和运行效率。4. 实战构建一个健壮的爬虫流程掌握了基本操作我们把这些知识串联起来构建一个处理常见爬虫场景的完整流程。我们以一个需要登录、然后查询并翻页抓取列表数据的模拟网站为例。4.1 处理登录与验证码很多网站的数据隐藏在登录之后。Selenium处理登录非常直观就是模拟输入用户名密码并点击登录按钮。def login(driver, username, password): driver.get(“https://example.com/login”) wait WebDriverWait(driver, 10) # 定位并输入用户名密码 user_input wait.until(EC.presence_of_element_located((By.NAME, “username”))) pass_input driver.find_element(By.NAME, “password”) user_input.send_keys(username) pass_input.send_keys(password) # 处理可能的验证码这里以简单图片验证码为例复杂验证码需要额外处理 # 1. 定位验证码图片元素 captcha_img driver.find_element(By.ID, “captcha_image”) # 2. 截图保存验证码图片实际项目中这里可能需要对接打码平台 captcha_img.screenshot(“captcha.png”) print(“请查看当前目录下的captcha.png文件输入验证码”) captcha_code input() # 等待手动输入自动化需要接入OCR或打码API # 3. 输入验证码 captcha_input driver.find_element(By.ID, “captcha”) captcha_input.send_keys(captcha_code) # 4. 点击登录按钮 login_button driver.find_element(By.XPATH, “//button[type‘submit’]”) login_button.click() # 5. 等待登录成功后的页面跳转例如等待用户头像出现 wait.until(EC.presence_of_element_located((By.CLASS_NAME, “user-avatar”))) print(“登录成功”)关于验证码的深入讨论这是爬虫与反爬对抗的重点。除了简单的图形验证码还有滑动拼图、点选文字、短信验证等。对于简单图形码可以尝试使用开源的OCR库如pytesseract但识别率有限。对于复杂验证码商业打码平台是更可靠的选择它们提供API接口你上传图片它们返回识别结果。如果网站验证码极其复杂或动态变化可能需要考虑其他技术路线如尝试寻找无需验证码的API接口但这已超出Selenium的范畴。4.2 数据提取与解析登录成功后进入数据列表页。我们需要提取每一行的数据。假设列表页的HTML结构如下table id“data-table” tr th姓名/thth年龄/thth城市/th /tr tr td class“name”张三/tdtd28/tdtd class“city”北京/td /tr tr td class“name”李四/tdtd35/tdtd class“city”上海/td /tr /tabledef extract_data_from_current_page(driver): data_list [] # 定位到表格的所有行跳过表头索引从1开始 rows driver.find_elements(By.CSS_SELECTOR, “#data-table tr”)[1:] # find_elements 返回列表 for row in rows: # 在每一行内定位各个单元格 cells row.find_elements(By.TAG_NAME, “td”) if len(cells) 3: item { “name”: cells[0].text, # 获取文本 “age”: int(cells[1].text), # 转换为整数 “city”: cells[2].text } # 也可以获取特定属性比如如果名字单元格里有链接 # link cells[0].find_element(By.TAG_NAME, “a”).get_attribute(“href”) data_list.append(item) return data_list关键技巧注意find_element和find_elements的区别。前者返回第一个匹配的元素一个WebElement对象如果没找到会抛出异常后者返回所有匹配元素的列表一个列表对象如果没找到则返回空列表。在遍历列表或不确定元素是否存在时使用find_elements更安全。4.3 自动翻页与循环终止数据通常分页显示。我们需要自动点击“下一页”直到没有下一页为止。def crawl_all_pages(driver, start_url): driver.get(start_url) all_data [] page_num 1 while True: print(f“正在抓取第 {page_num} 页...”) # 提取当前页数据 page_data extract_data_from_current_page(driver) all_data.extend(page_data) # 尝试定位‘下一页’按钮 try: # 下一页按钮可能是一个链接也可能是一个按钮 # 示例1链接文本是‘下一页’ next_button driver.find_element(By.LINK_TEXT, “下一页”) # 示例2一个带有‘next’类的按钮 # next_button driver.find_element(By.CSS_SELECTOR, “.next-page:not(.disabled)”) # 检查按钮是否可点击是否被禁用 if next_button.is_enabled(): next_button.click() # 等待新页面加载完成例如等待表格刷新或某个元素出现 WebDriverWait(driver, 10).until( EC.staleness_of(next_button) # 等待旧按钮从DOM中消失表示页面已跳转 ) # 或者等待新页面的某个标志性元素出现 # WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, “data-table”))) page_num 1 else: print(“已是最后一页。”) break except Exception as e: # 如果找不到‘下一页’按钮说明已经是最后一页 print(f“未找到下一页按钮抓取结束。错误信息可忽略{e}”) break # 可选防止意外无限循环设置最大页数限制 if page_num 50: print(“已达到最大页数限制停止抓取。”) break return all_data这个循环逻辑是爬虫的核心之一。关键在于如何可靠地判断“已是最后一页”。通常有几种方式1“下一页”按钮变为不可点击is_enabled()为False或增加了disabled类2“下一页”按钮在最后一页直接消失find_element会抛出NoSuchElementException3页面有明确的页码提示如“当前第5页/共10页”。你需要根据目标网站的具体情况来调整判断逻辑。5. 高级技巧与性能优化当你的爬虫需要长时间运行或处理大量页面时基础操作可能不够用。下面是一些提升脚本稳定性、效率和隐蔽性的高级技巧。5.1 绕过检测与浏览器指纹伪装越来越多的网站会检测Selenium等自动化工具。它们通过检测浏览器环境中的一些特定JavaScript变量如navigator.webdriver来判断。我们可以通过添加Chrome选项来尝试绕过。from selenium.webdriver.chrome.options import Options from selenium.webdriver.chrome.service import Service chrome_options Options() # 1. 添加常规反检测参数 chrome_options.add_argument(“--disable-blink-featuresAutomationControlled”) chrome_options.add_experimental_option(“excludeSwitches”, [“enable-automation”]) chrome_options.add_experimental_option(‘useAutomationExtension’, False) # 2. 修改 navigator.webdriver 属性在Selenium 4中这行代码可能需要在driver创建后执行CDP命令 # 更推荐使用下面的CDP命令方法 # 3. 使用 Chrome DevTools Protocol (CDP) 直接执行JavaScript覆盖webdriver属性 # 这需要在创建driver后执行 def create_stealth_driver(): options Options() options.add_argument(“--disable-blink-featuresAutomationControlled”) options.add_experimental_option(“excludeSwitches”, [“enable-automation”]) options.add_experimental_option(‘useAutomationExtension’, False) # 可以添加用户代理模拟真实浏览器 options.add_argument(‘user-agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36’) driver webdriver.Chrome(optionsoptions) # 执行CDP命令将 navigator.webdriver 设置为 undefined driver.execute_cdp_cmd(‘Page.addScriptToEvaluateOnNewDocument’, { ‘source’: ‘ Object.defineProperty(navigator, ‘webdriver’, { get: () undefined }); ‘ }) return driver driver create_stealth_driver()此外还可以考虑禁用图片加载加速页面加载。chrome_options.add_argument(‘--blink-settingsimagesEnabledfalse’)使用无头模式不显示浏览器GUI节省资源。chrome_options.add_argument(‘--headless’)。注意无头模式更容易被一些高级反爬系统识别。设置窗口大小有些网站会检测窗口尺寸。driver.set_window_size(1920, 1080)5.2 使用WebDriver Action Chains模拟复杂操作对于拖拽、鼠标悬停、右键菜单、组合键等复杂交互需要使用ActionChains类。from selenium.webdriver.common.action_chains import ActionChains # 鼠标悬停 menu driver.find_element(By.ID, “dropdown-menu”) ActionChains(driver).move_to_element(menu).perform() # 悬停后出现的子菜单 submenu wait.until(EC.visibility_of_element_located((By.LINK_TEXT, “子选项”))) submenu.click() # 拖拽元素 source driver.find_element(By.ID, “draggable”) target driver.find_element(By.ID, “droppable”) ActionChains(driver).drag_and_drop(source, target).perform() # 组合键操作 ActionChains(driver).key_down(Keys.CONTROL).send_keys(‘c’).key_up(Keys.CONTROL).perform() # 模拟CtrlC5.3 多线程与并发控制Selenium的每个driver实例都是一个独立的浏览器进程资源消耗较大。直接开上百个线程跑Selenium会导致系统崩溃。合理的做法是使用线程池控制并发数量。from concurrent.futures import ThreadPoolExecutor, as_completed import threading def worker(task_url): # 每个线程创建自己的driver实例避免资源共享冲突 thread_local threading.local() if not hasattr(thread_local, “driver”): options Options() options.add_argument(“--headless”) # 无头模式节省资源 thread_local.driver webdriver.Chrome(optionsoptions) driver thread_local.driver try: driver.get(task_url) # ... 执行抓取任务 ... data extract_data(driver) return data except Exception as e: print(f“抓取 {task_url} 失败{e}”) return None # 要抓取的URL列表 urls_to_crawl [“url1”, “url2”, “url3”, …] # 使用最多3个线程的线程池 all_results [] with ThreadPoolExecutor(max_workers3) as executor: future_to_url {executor.submit(worker, url): url for url in urls_to_crawl} for future in as_completed(future_to_url): url future_to_url[future] try: data future.result() if data: all_results.append(data) print(f“成功抓取{url}”) except Exception as exc: print(f‘{url} 产生了异常{exc}’) # 所有任务完成后记得关闭各个线程的driver这里示例简化实际需在每个线程内或最后统一清理重要警告多线程Selenium必须确保每个线程有自己独立的driver实例绝不能在多个线程间共享同一个driver这会导致不可预知的错误。使用threading.local()是一种简洁的管理方式。6. 常见问题排查与实战避坑指南即使按照教程一步步来在实际操作中你还是会遇到各种各样的问题。下面是我在长期使用中总结的一些典型“坑”及其解决方案。6.1 元素定位失败NoSuchElementException这是最常见的问题。脚本报错说找不到某个元素。排查步骤等待不足这是首要原因。页面还没加载完你就去找元素。解决方案使用WebDriverWait配合expected_conditions进行显式等待而不是time.sleep。页面内有iframe/框架页你要找的元素嵌套在iframe里。解决方案需要先切换到对应的iframe。# 通过ID或索引切换到iframe iframe driver.find_element(By.ID, “iframe_id”) driver.switch_to.frame(iframe) # 现在可以定位iframe内的元素了 # 操作完成后切回主页面 driver.switch_to.default_content()定位表达式写错了可能是复制出来的CSS Selector或XPath不稳定。解决方案在浏览器的开发者工具Console里测试你的表达式。例如在Console里输入$$(“你的CSS选择器”)或$x(“你的XPath表达式”)看是否能正确选中元素。元素是动态生成的有些元素是在你执行了某个操作如点击、滚动后才出现的。解决方案确保在触发元素出现的操作执行后再等待并定位它。页面发生了跳转或刷新你获取到的元素对象在页面刷新后已经“过期”stale。解决方案在页面刷新或跳转后需要重新定位元素。使用EC.staleness_of(element)可以等待一个旧元素“失效”。6.2 页面加载异常或空白用driver.get(url)后页面一片空白或加载不全。排查步骤检查网络和URL确保网络通畅URL正确。浏览器控制台报错在脚本中启动浏览器时不要立即关闭查看浏览器控制台F12 - Console是否有JavaScript错误。有时网站JS报错会导致页面渲染失败。证书或安全警告访问HTTPS网站可能遇到证书问题。解决方案添加选项忽略证书错误仅用于测试环境。chrome_options.add_argument(‘--ignore-certificate-errors’) chrome_options.add_argument(‘--allow-insecure-localhost’)网站屏蔽了自动化访问返回了验证页面或错误信息。解决方案尝试上述的“绕过检测”技巧添加更真实的浏览器指纹和用户代理。6.3 脚本运行速度慢Selenium因为要启动和渲染完整浏览器天生就比requests慢。但我们可以优化。优化策略启用无头模式chrome_options.add_argument(‘--headless’)。去掉GUI渲染能快不少。禁用图片、CSS、字体等非必要资源prefs {“profile.managed_default_content_settings.images”: 2} chrome_options.add_experimental_option(“prefs”, prefs)优化等待策略用精确的显式等待替代固定的time.sleep和过长的隐式等待。复用浏览器会话对于需要多次登录或保持会话的场景可以考虑使用Chrome DevTools Protocol更底层地控制浏览器或者使用selenium-wire这类库来直接复用Cookie避免每次重新登录。但这属于更高级的用法。6.4 内存泄漏与浏览器进程残留长时间运行爬虫脚本后可能会发现电脑内存被大量Chrome进程占用。解决方案确保driver.quit()被调用这是最重要的。driver.quit()会关闭浏览器并结束WebDriver进程。而driver.close()只关闭当前标签页。务必在try…except…finally块中或在脚本最后调用driver.quit()。driver None try: driver webdriver.Chrome() # … 你的爬虫逻辑 … except Exception as e: print(f“运行出错{e}”) finally: if driver: driver.quit() # 确保无论如何都会执行清理定期重启对于需要7x24小时运行的爬虫可以设计一个机制在抓取一定数量页面或运行一段时间后主动调用driver.quit()并重新创建新的driver实例以释放积累的内存碎片。监控进程在任务管理器中监控chromedriver.exe和chrome.exe进程的数量。如果发现只增不减说明存在资源未释放的问题。7. 项目结构与代码封装从脚本到工程当你的爬虫逻辑越来越复杂不再是一个简单的.py文件时良好的代码组织就至关重要了。这能提升代码的可读性、可维护性和复用性。一个建议的项目结构如下your_crawler_project/ ├── config.py # 配置文件存放URL、账号密码、数据库连接信息等 ├── main.py # 主程序入口 ├── core/ │ ├── __init__.py │ ├── browser.py # 封装浏览器创建、配置、关闭等逻辑 │ ├── login.py # 封装登录逻辑 │ └── parser.py # 封装页面解析和数据提取逻辑 ├── utils/ │ ├── __init__.py │ ├── logger.py # 日志记录模块 │ └── file_io.py # 文件读写辅助函数 ├── storage/ │ ├── __init__.py │ └── data_handler.py # 数据处理和存储如存到CSV、数据库 └── requirements.txt # 项目依赖包列表核心模块browser.py示例# core/browser.py from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.support.ui import WebDriverWait import logging logger logging.getLogger(__name__) class BrowserDriver: def __init__(self, headlessFalse, stealthTrue): self.options Options() if headless: self.options.add_argument(“--headless”) if stealth: self._add_stealth_options() # 其他通用配置 self.options.add_argument(“--disable-gpu”) self.options.add_argument(“--window-size1920,1080”) self.driver None def _add_stealth_options(self): self.options.add_argument(“--disable-blink-featuresAutomationControlled”) self.options.add_experimental_option(“excludeSwitches”, [“enable-automation”]) self.options.add_experimental_option(‘useAutomationExtension’, False) def start(self): “”“启动浏览器”“” try: self.driver webdriver.Chrome(optionsself.options) if self.options.arguments.get(“--disable-blink-features”): self.driver.execute_cdp_cmd(‘Page.addScriptToEvaluateOnNewDocument’, { ‘source’: ‘ Object.defineProperty(navigator, ‘webdriver’, { get: () undefined }); ‘ }) logger.info(“浏览器启动成功。”) return self.driver except Exception as e: logger.error(f“启动浏览器失败{e}”) raise def get_wait(self, timeout10): “”“获取一个WebDriverWait对象”“” return WebDriverWait(self.driver, timeout) def quit(self): “”“安全关闭浏览器”“” if self.driver: self.driver.quit() logger.info(“浏览器已关闭。”)这样在你的主程序main.py中代码会非常清晰# main.py from core.browser import BrowserDriver from core.login import login from core.parser import DataParser from storage.data_handler import save_to_csv import config def main(): browser_manager BrowserDriver(headlessTrue, stealthTrue) driver browser_manager.start() try: # 登录 login(driver, config.USERNAME, config.PASSWORD) # 进入目标页面 driver.get(config.TARGET_URL) # 初始化解析器 parser DataParser(driver) all_data [] while parser.has_next_page(): page_data parser.parse_current_page() all_data.extend(page_data) parser.go_to_next_page() # 保存数据 save_to_csv(all_data, “output.csv”) print(f“共抓取 {len(all_data)} 条数据。”) except Exception as e: print(f“程序运行出错{e}”) finally: browser_manager.quit() if __name__ “__main__”: main()这种模块化的设计使得登录逻辑、解析逻辑、浏览器管理、数据存储都分离开来。未来如果登录方式变了你只需要修改login.py如果网站改版导致解析规则变了你只需要修改parser.py。代码的维护性和可测试性都大大增强。最后再分享一个我个人的小习惯在编写复杂的页面解析逻辑时我会先用Selenium打开页面手动操作一遍然后用driver.page_source将完整的HTML源码保存到本地文件再用编辑器慢慢研究结构、编写和测试XPath或CSS选择器。这比反复运行脚本调试要高效得多。SeleniumPython爬虫是一个强大的工具它的价值在于能处理那些“传统爬虫”束手无策的复杂场景。虽然速度不是它的强项但它的高成功率和模拟真实性在很多时候是无价的。掌握它意味着你打开了一扇处理动态网页数据的新大门。