Python Selenium爬虫实战:从环境搭建到反反爬策略
1. 项目概述:为什么Selenium在爬虫领域依然坚挺?
提到Python爬虫,很多人第一反应是requests搭配BeautifulSoup或者Scrapy。确实,对于绝大多数静态网页,这套组合拳又快又好用。但当你面对的是一个充斥着JavaScript动态渲染、登录验证复杂、操作流程繁琐的网站时,传统的HTTP请求库就显得力不从心了。这时,Selenium就该登场了。它本质上是一个浏览器自动化工具,能模拟真人操作浏览器的一切行为:点击、输入、滚动、下拉选择等等。在爬虫领域,它扮演着“终极解决方案”的角色,专治各种不服——特别是那些数据藏在JS动态加载背后的网站。
我最初接触Selenium是为了抓取一个需要登录、然后通过多次点击和筛选才能看到数据的后台管理系统。用requests去逆向分析它的API接口,发现其加密逻辑复杂,且经常变动,维护成本极高。转而使用Selenium,虽然速度慢了点,但代码逻辑直白得像写操作说明书,稳定性反而大大提升。所以,别再把Selenium仅仅看作一个“备胎”,在应对现代Web应用(尤其是单页面应用SPA)时,它往往是那条最稳妥、最省心的“主干道”。本教程将带你从零开始,深入Selenium在Python爬虫中的每一个实用细节,避开我踩过的那些坑。
2. 环境搭建与核心组件解析
工欲善其事,必先利其器。用Selenium搞爬虫,第一步就是把环境配通。这里面的门道,直接关系到你后续脚本是稳定运行还是频频报错。
2.1 浏览器与驱动:版本匹配是生命线
Selenium工作的原理是:你的Python代码(客户端)通过WebDriver协议向一个特定的浏览器驱动程序(如chromedriver)发送指令,驱动程序再去控制真实的浏览器(如Chrome)。因此,浏览器、驱动程序、Selenium库三者的版本兼容性至关重要。
浏览器选择:Chrome/Edge(Chromium内核)是首选,因为生态最好,资料最全。Firefox也可用,但驱动(geckodriver)的更新有时稍慢。
驱动下载与管理:
- 查看浏览器版本:打开Chrome,在地址栏输入
chrome://version/,找到“Google Chrome”后面的版本号(例如,120.0.6099.109)。 - 下载对应驱动:访问ChromeDriver官网或国内镜像站。关键点来了:你下载的ChromeDriver主版本号必须与你的Chrome浏览器主版本号完全一致。例如,Chrome是120.x.x.x,就必须找120.x.x.x系列的ChromeDriver。
- 放置驱动:将下载的
chromedriver.exe(Windows)或chromedriver(Mac/Linux)放到一个目录,并将该目录添加到系统的PATH环境变量中。更简单的做法是,在代码里指定驱动路径。
注意:这是新手最容易栽跟头的地方。经常有人报错“This version of ChromeDriver only supports Chrome version XX”,就是因为版本没对上。我的习惯是,将驱动放在项目根目录的
drivers文件夹下,在代码中显式指定路径,这样项目移植时不会出错。
安装Selenium库:这个最简单,pip install selenium即可。建议使用虚拟环境管理项目依赖。
2.2 基础脚本:从打开浏览器到拿到源码
环境配好,我们来写第一个脚本,感受一下Selenium的“所见即所得”。
from selenium import webdriver from selenium.webdriver.chrome.service import Service import time # 1. 指定ChromeDriver路径 driver_path = './drivers/chromedriver' # 根据你的实际路径修改 service = Service(executable_path=driver_path) # 2. 配置浏览器选项(可选,但很重要) options = webdriver.ChromeOptions() # 添加常用选项 options.add_argument('--headless') # 无头模式,不显示浏览器界面 options.add_argument('--disable-gpu') # 禁用GPU,在某些系统上需要 options.add_argument('--no-sandbox') # 在Linux环境下可能需要 options.add_argument('--disable-dev-shm-usage') # 解决共享内存问题 # 防止被检测为自动化工具(初级反反爬) options.add_experimental_option('excludeSwitches', ['enable-automation']) options.add_experimental_option('useAutomationExtension', False) # 3. 启动浏览器 driver = webdriver.Chrome(service=service, options=options) # 执行CDP命令,进一步隐藏自动化特征 driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', { 'source': 'Object.defineProperty(navigator, "webdriver", {get: () => undefined})' }) try: # 4. 访问网页 driver.get('https://www.example.com') # 等待页面加载,简单粗暴的方式,后面会讲更优的等待策略 time.sleep(3) # 5. 获取页面信息 print('当前标题:', driver.title) print('当前URL:', driver.current_url) # 获取渲染后的完整HTML源码,这是requests做不到的 page_source = driver.page_source # print(page_source) # 内容太多,通常先打印看看结构 # 6. 一个简单操作:在百度搜索框输入内容 driver.get('https://www.baidu.com') search_box = driver.find_element('id', 'kw') # 找到搜索框 search_box.send_keys('Selenium爬虫教程') # 输入文字 search_box.submit() # 提交表单 time.sleep(2) finally: # 7. 关闭浏览器 driver.quit()这个脚本涵盖了最基本的流程。其中,浏览器选项(ChromeOptions)的配置是进阶使用的开始。无头模式(--headless)对于服务器部署爬虫至关重要,可以节省资源。而excludeSwitches和CDP命令则是为了应对一些网站对自动化工具的简单检测,属于初步的“反反爬”措施。
3. 元素定位:八仙过海,各显神通
定位页面元素是Selenium所有操作的基础。找不到元素,点击、输入都无从谈起。Selenium提供了多达8种定位策略,但常用的就几种。
3.1 八大定位器详解与选用策略
driver.find_element(by, value)用于定位单个元素,返回第一个匹配项。driver.find_elements(by, value)用于定位所有匹配元素,返回一个列表。
- ID定位 (
By.ID): 最优先选择。ID通常唯一,定位最快、最准。driver.find_element('id', 'su')。 - Name定位 (
By.NAME): 次优先。常用于表单元素。driver.find_element('name', 'wd')。 - Class Name定位 (
By.CLASS_NAME): 注意,class属性可能有多个值(用空格分隔),这里需要传入完整的单个class值,或其中的一个。driver.find_element('class name', 's_ipt')。 - Tag Name定位 (
By.TAG_NAME): 按标签名定位,如input,div,a。通常用于找多个同类元素。driver.find_elements('tag name', 'a')。 - Link Text / Partial Link Text (
By.LINK_TEXT,By.PARTIAL_LINK_TEXT): 专门用于定位超链接(<a>标签)。前者需要完整文本,后者只需部分文本。driver.find_element('link text', '新闻')。 - CSS Selector (
By.CSS_SELECTOR):功能最强大、最灵活的定位方式。可以组合ID、class、属性、层级关系进行精准定位。语法和前端CSS选择器一模一样。强烈建议掌握。#id:通过ID定位。.class:通过class定位。input[name='wd']:通过标签名和属性组合定位。div#container > ul.list > li:nth-child(2):通过层级关系定位。
- XPath (
By.XPATH): 和CSS Selector并列的定位神器。它通过XML路径来定位元素,功能极其强大,甚至可以定位没有ID、Class的元素,或者通过文本内容定位。- 绝对路径:
/html/body/div[1]/form/input(脆弱,不推荐)。 - 相对路径:
//input[@id='kw'](推荐)。 - 包含文本:
//a[contains(text(), '登录')]。 - 多个属性:
//input[@type='text' and @name='user']。
- 绝对路径:
选用策略:
- 首选ID、Name:简单直接。
- 复杂场景用CSS Selector或XPath:对于动态ID、嵌套复杂的元素,这是唯一选择。
- CSS vs XPath:CSS Selector通常性能稍好,语法更简洁(特别是对于class)。XPath功能更强,可以向上遍历DOM树(找父节点),并且对文本内容的支持更好。我个人更偏爱CSS Selector,因为写起来快,但在需要通过文本定位时,XPath是无可替代的。
3.2 定位实战与常见坑点
假设我们要定位百度首页的搜索框和“百度一下”按钮。
from selenium.webdriver.common.by import By # 访问百度 driver.get('https://www.baidu.com') # 方法1:通过ID定位搜索框(最稳) search_box = driver.find_element(By.ID, 'kw') # 方法2:通过Name定位(同样很稳) search_box = driver.find_element(By.NAME, 'wd') # 方法3:通过Class Name定位(注意,它的class是多个) search_box = driver.find_element(By.CLASS_NAME, 's_ipt') # ‘s_ipt’是其中一个class # 方法4:通过CSS Selector定位(灵活) search_box = driver.find_element(By.CSS_SELECTOR, '#kw') # ID选择器 search_box = driver.find_element(By.CSS_SELECTOR, 'input.s_ipt') # 标签+class组合 search_box = driver.find_element(By.CSS_SELECTOR, 'input[name=\"wd\"]') # 属性选择器 # 方法5:通过XPath定位(强大) search_box = driver.find_element(By.XPATH, '//input[@id=\"kw\"]') search_box = driver.find_element(By.XPATH, '//*[@id=\"kw\"]') # 用*代替标签名 # 定位“百度一下”按钮 # 它没有ID,可以用class或者XPath submit_btn = driver.find_element(By.CSS_SELECTOR, '#su') # 其实它有ID‘su’ submit_btn = driver.find_element(By.XPATH, '//input[@type=\"submit\"]') # 操作:输入并搜索 search_box.send_keys('天气预报') submit_btn.click()常见坑点:
- 元素未加载/不可见:这是最最常见的错误。你代码执行太快,页面还没渲染出来就去定位,当然找不到。解决方案是“等待”,下一章详述。
- 元素在iframe/frame内:如果元素嵌套在
<iframe>里,你需要先切换到对应的frame中才能定位。# 1. 先找到这个iframe元素 iframe = driver.find_element(By.TAG_NAME, 'iframe') # 2. 切换进去 driver.switch_to.frame(iframe) # 3. 现在可以定位iframe内的元素了 inner_element = driver.find_element(...) # 4. 操作完成后,切回主文档 driver.switch_to.default_content() - 动态ID/Class:有些前端框架(如React, Vue)会生成随机的ID或Class。此时绝不能依赖它们。要寻找其父级或相邻元素中稳定的特征,使用CSS Selector或XPath的层级关系来定位。
- 多个匹配元素:使用
find_element只会返回第一个。如果你需要操作第二个、第三个,请使用find_elements获取列表后按索引操作,或者使用更精确的选择器(如:nth-child(n))。
4. 等待的艺术:告别time.sleep,拥抱智能等待
新手最爱用time.sleep(10),简单粗暴。但这是最低效的方式,因为你永远不知道网络或服务器到底需要多久。设置短了,元素没出来就报错;设置长了,大量时间在空等。Selenium提供了两种优雅的等待方式:显式等待和隐式等待。
4.1 显式等待:精准狙击
显式等待是针对某个特定条件进行等待,条件满足则立即继续执行,超时则抛出异常。这是推荐的主要等待策略。
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By # 设置一个最长等待10秒的等待器 wait = WebDriverWait(driver, 10) try: # 等待直到ID为‘kw’的元素出现在DOM中并且可见 search_box = wait.until( EC.visibility_of_element_located((By.ID, 'kw')) ) # 一旦等到,元素就可用了 search_box.send_keys('hello') except TimeoutException: print('等待超时,元素未出现') # 其他常用的预期条件 (EC) # EC.presence_of_element_located: 元素出现在DOM中(不一定可见) # EC.element_to_be_clickable: 元素可点击 # EC.text_to_be_present_in_element: 元素中包含特定文本 # EC.title_is / EC.title_contains: 标题判断显式等待的核心优势:它轮询检查条件(默认每0.5秒一次),一旦条件满足就立刻返回,最大程度节省时间。你需要根据操作目的选择合适的条件。例如,要点击一个按钮,就用element_to_be_clickable;要获取一个元素的文本,用presence_of_element_located通常就够了。
4.2 隐式等待:全局守候
隐式等待是设置一个全局的等待时间,在查找任何一个元素时,如果元素没有立即找到,WebDriver会等待一段时间,直到超时。
driver.implicitly_wait(10) # 单位:秒 # 此后,所有 driver.find_element 操作都会最多隐式等待10秒 element = driver.find_element(By.ID, 'someId')隐式等待的坑:
- 只对
find_element系列方法生效,对get(url)等无效。 - 和显式等待混用可能导致总等待时间变长。例如,隐式等待10秒,显式等待也10秒,在最坏情况下,实际等待时间可能是20秒。
- 不够灵活,无法针对特定条件(如可点击、文本出现)进行等待。
最佳实践:
- 几乎总是使用显式等待,因为它更精确、更高效。
- 可以设置一个很短的隐式等待作为兜底(如3秒),处理一些简单的、确定性的元素。
- 绝对避免混用长时间的隐式等待和显式等待。我的个人习惯是:
driver.implicitly_wait(0)禁用隐式等待,全程使用显式等待,代码控制力更强。
4.3 强制等待与页面加载策略
time.sleep()就是强制等待,除非在调试或者等待非元素相关的固定流程(如等待页面跳转完成),否则应尽量避免。
此外,浏览器的页面加载策略也会影响等待:
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities caps = DesiredCapabilities.CHROME # ‘normal’:等待整个页面(包括静态资源)加载完成。默认值。 # ‘eager’:等待DOMContentLoaded事件完成(DOM树构建好),不等待图片、样式表等。 # ‘none’:完全不等待。 caps['pageLoadStrategy'] = 'eager' # 或 'none' driver = webdriver.Chrome(desired_capabilities=caps)在爬虫场景,设置为eager可以显著加快driver.get()的速度,因为我们通常只关心DOM结构,不关心图片是否加载完。
5. 元素操作与高级交互
定位到元素后,就可以对它为所欲为了。基础操作包括点击、输入、清空、提交等。
5.1 基础操作与表单处理
# 假设我们已经定位到元素 elem elem = driver.find_element(By.ID, 'someId') # 1. 点击 elem.click() # 2. 输入文本 elem.send_keys('你要输入的文字') # 3. 清空输入框 elem.clear() # 通常在send_keys之前调用 # 4. 提交表单 # 如果元素在一个表单里,可以用submit()。但更通用的做法是找到提交按钮并click()。 elem.submit() # 5. 获取元素属性、文本、状态 print(elem.get_attribute('href')) # 获取属性值,如<a>标签的href print(elem.text) # 获取元素内的可见文本(非常重要!) print(elem.is_displayed()) # 是否可见 print(elem.is_enabled()) # 是否可用(可操作) print(elem.is_selected()) # 是否被选中(用于复选框、单选框) # 6. 处理下拉选择框(Select) from selenium.webdriver.support.ui import Select select_elem = driver.find_element(By.NAME, 'country') select = Select(select_elem) # 三种选择方式 select.select_by_index(1) # 通过索引(从0开始) select.select_by_value('cn') # 通过option的value属性 select.select_by_visible_text('中国') # 通过显示的文本 # 获取所有选项 all_options = select.options5.2 高级交互:动作链与执行JS
有些交互,比如拖拽、鼠标悬停、复杂组合键,需要用到ActionChains(动作链)。
from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.keys import Keys # 鼠标悬停 menu = driver.find_element(By.ID, 'menu') sub_menu = driver.find_element(By.ID, 'submenu') actions = ActionChains(driver) actions.move_to_element(menu).perform() # 移动到menu上,触发悬停效果 # 等待子菜单出现(这里需要显式等待) wait.until(EC.visibility_of(sub_menu)) sub_menu.click() # 拖拽元素 source = driver.find_element(By.ID, 'draggable') target = driver.find_element(By.ID, 'droppable') actions.drag_and_drop(source, target).perform() # 组合键操作 actions.key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() # 模拟Ctrl+C执行JavaScript:这是Selenium的“大招”,可以完成WebDriver API无法直接实现的操作。
# 滚动到页面底部 driver.execute_script('window.scrollTo(0, document.body.scrollHeight);') time.sleep(2) # 等待滚动后内容加载 # 滚动到某个元素 element = driver.find_element(By.ID, 'footer') driver.execute_script('arguments[0].scrollIntoView(true);', element) # 修改元素属性(可用于绕过一些前端限制) driver.execute_script("document.getElementById('readonlyInput').removeAttribute('readonly');") # 获取通过JS渲染后的数据(有时.text属性拿不到) shadow_content = driver.execute_script('return document.querySelector(...).shadowRoot.innerHTML;') # 点击被遮挡的元素 driver.execute_script('arguments[0].click();', element)6. 爬虫实战:应对复杂场景与反爬策略
现在,我们把所有知识串联起来,完成一个模拟真实爬虫需求的实战。
6.1 实战案例:爬取动态加载的电商商品列表
目标:爬取一个类似淘宝搜索页的商品列表,该列表是向下滚动时通过AJAX动态加载的。
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 import time import json options = webdriver.ChromeOptions() # 无头模式,并添加一些参数防止被识别 options.add_argument('--headless') options.add_argument('--disable-blink-features=AutomationControlled') prefs = {'profile.managed_default_content_settings.images': 2} # 不加载图片,加速 options.add_experimental_option('prefs', prefs) driver = webdriver.Chrome(options=options) wait = WebDriverWait(driver, 15) base_url = 'https://s.taobao.com/search?q=手机' driver.get(base_url) product_list = [] last_height = driver.execute_script('return document.body.scrollHeight') scroll_pause_time = 2 # 每次滚动后等待加载的时间 max_scroll = 10 # 最多滚动10次,防止无限循环 scroll_count = 0 while scroll_count < max_scroll: # 1. 滚动到底部 driver.execute_script('window.scrollTo(0, document.body.scrollHeight);') time.sleep(scroll_pause_time) # 等待新内容加载 # 2. 计算新的滚动高度,判断是否已到底部 new_height = driver.execute_script('return document.body.scrollHeight') if new_height == last_height: # 高度未变,可能已加载完毕或遇到“加载更多”按钮 try: # 尝试寻找并点击“加载更多”按钮 load_more_btn = driver.find_element(By.CSS_SELECTOR, '.load-more-btn') load_more_btn.click() time.sleep(scroll_pause_time * 2) except: # 没有找到按钮,可能真的到底了 print('已滚动到底部或无法继续加载。') break last_height = new_height scroll_count += 1 print(f'已滚动第 {scroll_count} 次,当前页面高度:{new_height}') # 3. 开始解析当前页面中的所有商品项 # 假设每个商品项有一个共同的CSS选择器 items = driver.find_elements(By.CSS_SELECTOR, '.item.J_MouserOnverReq') print(f'共找到 {len(items)} 个商品项。') for index, item in enumerate(items): try: # 使用相对定位,在item元素内查找子元素,避免全局定位冲突 # 商品标题 title_elem = item.find_element(By.CSS_SELECTOR, '.title a') title = title_elem.text.strip() link = title_elem.get_attribute('href') # 价格 price_elem = item.find_element(By.CSS_SELECTOR, '.price strong') price = price_elem.text.strip() # 销量和店铺名(可能不存在) try: sales_elem = item.find_element(By.CSS_SELECTOR, '.deal-cnt') sales = sales_elem.text.strip() except: sales = 'N/A' try: shop_elem = item.find_element(By.CSS_SELECTOR, '.shopname span') shop = shop_elem.text.strip() except: shop = 'N/A' product_info = { 'index': index + 1, 'title': title, 'price': price, 'sales': sales, 'shop': shop, 'link': link } product_list.append(product_info) print(f'已提取: {title[:30]}...') except Exception as e: print(f'解析第{index+1}个商品时出错: {e}') continue # 4. 保存数据 with open('taobao_products.json', 'w', encoding='utf-8') as f: json.dump(product_list, f, ensure_ascii=False, indent=2) print(f'数据已保存,共 {len(product_list)} 条记录。') driver.quit()这个案例涵盖了:无头模式、滚动加载、动态等待、异常处理、数据提取和保存。关键在于滚动逻辑和使用相对定位(在item元素内find_element),这能大大提高定位的准确性和鲁棒性。
6.2 常见反爬策略与应对措施
- 检测WebDriver:一些网站会检测
navigator.webdriver属性。我们之前用CDP命令已经将其置为undefined。还可以通过options.add_experimental_option("excludeSwitches", ["enable-automation"])来移除“正受到自动测试软件控制”的提示。 - 行为指纹:检测鼠标移动轨迹、点击速度等是否像机器人。应对方法:使用
ActionChains时加入随机延迟,或者使用更高级的自动化框架如puppeteer-extra的Stealth插件(在Python中对应undetected-chromedriver库)。 - 验证码:这是硬骨头。简单图形验证码可以用OCR库(如
ddddocr,pytesseract)尝试识别,但成功率有限。复杂验证码(如点选、滑块)需要接入打码平台(如超级鹰、图鉴)或机器学习方案。对于爬虫来说,遇到复杂验证码通常意味着需要降低频率、更换IP,或者考虑是否值得投入成本破解。 - IP限制与封禁:最有效的应对方法是使用代理IP池。
更常见的做法是使用from selenium.webdriver.common.proxy import Proxy, ProxyType proxy_ip_port = 'http://123.45.67.89:8080' proxy = Proxy() proxy.proxy_type = ProxyType.MANUAL proxy.http_proxy = proxy_ip_port proxy.ssl_proxy = proxy_ip_port capabilities = webdriver.DesiredCapabilities.CHROME proxy.add_to_capabilities(capabilities) driver = webdriver.Chrome(desired_capabilities=capabilities)--proxy-server命令行选项:options.add_argument(f'--proxy-server=http://{proxy_ip_port}') - User-Agent检测:定期更换User-Agent。
from fake_useragent import UserAgent ua = UserAgent() random_ua = ua.random options.add_argument(f'user-agent={random_ua}')
7. 性能优化、调试与最佳实践
用Selenium爬虫,效率是永恒的痛点。以下是提升效率和稳定性的关键点。
7.1 性能优化技巧
- 无头模式(Headless):
--headless,必备,节省大量GUI渲染资源。 - 禁用图片/样式表/字体:大幅减少流量和加载时间。
prefs = { 'profile.managed_default_content_settings.images': 2, # 2为禁用 'permissions.default.stylesheet': 2, } options.add_experimental_option('prefs', prefs) - 设置页面加载策略为
eager或none:如前所述。 - 合理使用等待:杜绝全局
sleep,多用显式等待,设置合理的超时时间。 - 复用浏览器会话:对于需要登录的网站,可以保存cookies,下次启动时加载,避免重复登录。但Selenium直接保存会话状态较复杂,通常需要配合
pickle库保存cookies。 - 并发与多线程/进程:一个WebDriver实例对应一个浏览器进程,资源消耗大。实现并发爬取通常采用多进程(
multiprocessing),每个进程独立一个driver。注意:线程间共享driver是危险且不推荐的。
7.2 调试技巧
- 非无头模式运行:在开发调试阶段,注释掉
--headless选项,亲眼看着浏览器操作,能快速定位问题。 - 截图:出错时自动截图,是强大的调试工具。
try: some_operation() except Exception as e: driver.save_screenshot('error.png') print(f'操作失败,已截图。错误信息:{e}') raise - 打印页面源码或元素HTML:当定位不到元素时,打印
driver.page_source或element.get_attribute('outerHTML'),检查元素是否真的存在,或者你的选择器是否正确。 - 使用浏览器开发者工具:在非无头模式下,按F12打开开发者工具。在
Elements面板,可以用Ctrl+F测试你的CSS Selector或XPath是否唯一匹配。Console面板可以直接执行document.querySelector(...)进行测试。
7.3 项目结构与代码最佳实践
- 配置与代码分离:将浏览器路径、代理、用户代理等配置项放在
config.py或settings.yaml中。 - 使用Page Object模式:对于大型爬虫项目,将每个页面的定位器和操作封装成一个类。这极大提高了代码的可读性和可维护性。
class LoginPage: def __init__(self, driver): self.driver = driver self.username_input = (By.ID, 'username') self.password_input = (By.ID, 'password') self.submit_button = (By.ID, 'submitBtn') def login(self, username, password): self.driver.find_element(*self.username_input).send_keys(username) self.driver.find_element(*self.password_input).send_keys(password) self.driver.find_element(*self.submit_button).click() - 完善的日志记录:使用Python的
logging模块记录运行信息、错误和警告,而不是简单print。 - 异常处理与重试机制:网络不稳定、元素偶尔加载失败是常态。使用
try...except包裹关键操作,并配合重试库(如retrying)实现自动重试。 - 资源清理:务必在
finally块或使用with上下文管理器调用driver.quit(),确保浏览器进程被关闭,防止内存泄漏。
Selenium爬虫是一把重型武器,它用资源换来了无与伦比的兼容性和拟真度。在动态网页数据抓取、需要复杂交互的自动化任务面前,它往往是最高效(从开发效率而非运行效率而言)的解决方案。掌握其核心原理与避坑技巧,就能让这把武器在你手中发挥出最大威力。记住,爬虫之道,贵在平衡——在数据获取、效率、稳定性和对目标网站的尊重之间找到属于你的那个平衡点。
