兄弟们,用Selenium搞爬虫是不是经常被气得想砸键盘?明明代码看着没问题,浏览器却突然闪退;好不容易定位到元素,一翻页就报错失效;还有那阴魂不散的验证码和永远加载不完的页面!别慌,这些坑我都踩过,今天就用大白话给你总结一套防坑指南,让你爬虫效率直接起飞!

Selenium 是强大的自动化工具,但在爬虫过程中常常会遇到一些"坑"。我会为你梳理常见的错误及其解决方案,希望能帮助你更顺利地完成爬虫任务。
下面这个表格汇总了 Selenium 爬虫时你可能遇到的常见问题、原因及快速解决办法。
错误类型/问题现象 | 主要原因 | 推荐解决方案 | 引用来源 |
---|---|---|---|
浏览器闪退、页面立即关闭 | 被网站检测到自动化工具(如 navigator.webdriver 属性存在) |
使用 undetected-chromedriver 或通过 CDP 命令修改 navigator.webdriver 属性为 undefined 。 |
|
StaleElementReferenceException (元素过期) |
页面刷新或重新加载后,之前获取的元素引用失效 | 等待页面加载完成后再重新定位元素,或尝试在新标签页中打开页面。 | |
InvalidElementStateException (元素状态无效) |
尝试与不可交互的元素(如不可见、被禁用、被覆盖、只读)进行操作 | 操作前等待元素变为可交互状态(可见、启用),并检查元素状态。 | |
爬取的文本内容错误、缺失或为空 | 元素定位方式不准确、页面结构变化、动态加载内容未完全加载 | 确保选择器准确,使用显式等待 (WebDriverWait) 等待特定元素加载完成。 | |
页面加载超时 (TimeoutException ) |
网络问题、网站拦截、资源加载缓慢 | 合理设置 set_page_load_timeout() ,并考虑使用代理IP。 |
|
多线程爬虫时数据竞争或崩溃 | 多线程共享 WebDriver 实例或数据未加锁 | 为每个线程创建独立 WebDriver 实例,或使用 threading.Lock 保护共享数据。 |
|
遇到验证码(特别是滑动验证码) | 网站反爬机制触发 | 可考虑专业验证码处理服务,或模拟人工滑动(注意轨迹和速度)。 | |
翻页后无法获取新内容或重复旧内容 | 翻页操作后未等待新页面完全加载,代码执行速度比页面加载快 | 翻页后使用显式等待条件等待新页面关键元素加载完成,再提取内容。 |
1、绕过浏览器检测与反爬机制
网站通过检测 navigator.webdriver
等属性识别自动化脚本。
-
使用
undetected-chromedriver
(推荐) 这是一个专门为绕过检测而设计的库。pythonimport undetected_chromedriver as uc driver = uc.Chrome(version_main=114, headless=False) # 匹配你的 Chrome 版本,慎用无头模式 driver.get("https://目标网站.com")
-
修改
navigator.webdriver
属性 在普通 Selenium 中通过 CDP 命令修改属性。pythonfrom selenium import webdriver from selenium.webdriver.chrome.options import Options chrome_options = Options() chrome_options.add_argument("--disable-blink-features=AutomationControlled") chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"]) chrome_options.add_experimental_option("useAutomationExtension", False) driver = webdriver.Chrome(options=chrome_options) driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", { "source": """ Object.defineProperty(navigator, 'webdriver', { get: () => undefined }) """ })
-
模拟人类操作行为 随机延迟、模拟鼠标移动等行为有助于降低被检测的风险。
arduinoimport time import random from selenium.webdriver.common.action_chains import ActionChains time.sleep(random.uniform(1, 3)) # 随机延迟 actions = ActionChains(driver) actions.move_by_offset(random.randint(10, 50), random.randint(10, 50)) # 随机移动鼠标 actions.perform()
2、处理动态加载与元素等待
页面元素是异步加载的,必须等待其出现后再操作。
-
显式等待 (Explicit Wait) 显式等待是针对特定条件进行的等待,比如等待某个元素存在、可见、可点击等。
pythonfrom selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC try: # 等待最多10秒,直到ID为 'dynamicContent' 的元素出现并可见 element = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, "dynamicContent")) # 也可用 EC.visibility_of_element_located ) print(element.text) except Exception as e: print("元素未找到或超时:", e) # 等待元素可点击 clickable_element = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.ID, "myButton")) ) clickable_element.click()
-
隐式等待 (Implicit Wait) 隐式等待是设置一个全局的等待时间,针对所有元素定位操作。
bashdriver.implicitly_wait(10) # 设置隐式等待10秒 driver.find_element(By.ID, "someElement")
3、处理页面刷新与导航后的"元素过期"
页面刷新或跳转后,之前获取的元素引用会失效,需要重新定位。
-
显式等待新页面或元素 在操作后(如点击翻页)等待新页面的某个特定元素加载完成,然后再进行后续操作。
ini# 点击翻页按钮 next_page_button = driver.find_element(By.ID, "nextPage") next_page_button.click() # 等待新页面的特定元素(例如第二页的某个独有元素)加载完成 WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, "contentOnPage2")) ) # 重新获取当前页面的元素列表 job_elements_list = driver.find_elements(By.CLASS_NAME, "job-item")
4、处理翻页问题
翻页时,除了等待新页面加载,有时网站结构可能导致翻页困难。
-
直接分析翻页URL规律 如果翻页是通过URL变化实现的(如
page=2
),直接构造URL可能是最稳定的方式。 -
确保点击动作成功 有时点击翻页按钮需要用 JavaScript 直接执行。
ini# 假设翻页按钮是一个 <a> 标签 next_page_button = driver.find_element(By.CSS_SELECTOR, "a.next-page") driver.execute_script("arguments[0].click();", next_page_button) # 用JS点击
5、多线程爬虫注意事项
在多线程环境中使用 Selenium 需要格外小心。
-
为每个线程创建独立的 WebDriver 实例 这是最简单也是最安全的方式,避免资源共享冲突。
arduinoimport threading from selenium import webdriver def crawl_task(url): # 每个线程有自己的driver实例 local_driver = webdriver.Chrome() local_driver.get(url) # ... 进行爬取操作 # 操作完成后关闭 local_driver.quit() threads = [] for url in url_list: thread = threading.Thread(target=crawl_task, args=(url,)) threads.append(thread) thread.start() for thread in threads: thread.join()
-
使用锁 (Lock) 保护共享资源 如果必须共享某些资源(如写入同一个文件),使用锁来确保线程安全。
pythonfrom threading import Lock write_lock = Lock() shared_file = open("data.txt", "a", encoding="utf-8") def safe_write(data): with write_lock: # 获取锁 shared_file.write(data + "\n") # 写入数据 # 离开with块后自动释放锁 # 在爬虫线程中调用 safe_write("Some crawled data")
6、应对验证码
遇到验证码,特别是滑动验证码,处理起来比较复杂。
-
专业验证码处理服务 对于复杂验证码,可以考虑使用第三方服务(如 2Captcha、DeathByCaptcha)。
-
简单图形验证码 可以下载图片并使用 OCR 库(如 Tesseract,配合
pytesseract
库)识别。 -
滑动验证码 处理滑动验证码通常需要分析缺口位置、生成模拟人的滑动轨迹。
ini# 示例:生成滑动轨迹(部分代码,源自搜索结果) def calculate_tracks(distance): # 模拟加速和减速过程,生成滑动轨迹列表 v = 0 t = 0.2 forward_tracks = [] current = 0 mid = distance * 3 / 5 while current < distance: if current < mid: a = 2 # 加速度 else: a = -3 # 减速度 s = v * t + 0.5 * a * (t ** 2) v = v + a * t current += s forward_tracks.append(round(s)) return forward_tracks # 使用 ActionsChains 执行滑动 slider = driver.find_element(By.ID, "slider") tracks = calculate_tracks(150) # 假设需要滑动150像素 ActionChains(driver).click_and_hold(slider).perform() for track in tracks: ActionChains(driver).move_by_offset(xoffset=track, yoffset=0).perform() ActionChains(driver).release().perform()
请注意,这只是一个示例,实际处理需要根据具体网站的验证码机制进行调整。滑动验证码的破解越来越难,很多网站会有更复杂的防护。
7、网络问题与超时处理
-
设置页面加载超时
pythonfrom selenium.common.exceptions import TimeoutException try: driver.set_page_load_timeout(30) # 设置页面加载超时为30秒 driver.get("https://目标网站.com") except TimeoutException: print("页面加载超时,可能是网络问题或网站拦截") driver.quit()
-
使用代理IP 如果IP被封锁,可以考虑使用代理IP。
javascriptfrom selenium import webdriver from selenium.webdriver.chrome.options import Options chrome_options = Options() chrome_options.add_argument("--proxy-server=http://你的代理IP:端口") # 例如 http://123.45.67.89:8080 driver = webdriver.Chrome(options=chrome_options)
请注意,代理IP的稳定性和匿名性需要自行确保。
总之记住几个核心要领:爬虫别用无头模式容易暴露,元素定位要多等别硬睡,每个线程单独开浏览器最稳妥。遇到验证码别头铁,该用专业服务就别硬刚。只要把这些技巧摸透,Selenium爬虫基本就能横着走了!如果还遇到新问题,欢迎随时来交流~