☰
Selenium爬虫实战:从环境搭建到反爬应对的完整攻略
2026/10/16 10:29:30 网站建设 项目流程

目录页里经常藏着大量信息,这也是写爬虫时最优先要看的地方。我在拆解一个站点时,习惯先打开开发者工具(F12)里的Elements面板,直接在网页上右键选择"检查"定位到目标元素,看它在DOM树里的位置,再决定用哪种方式去锁定它。Selenium里封装好了现成的方法,直接调用就行。

定位这个环节有不少容易踩的坑,比如有些元素是动态加载出来的,页面刚打开时DOM里还没有;还有些元素看得到但定位不到,因为它在iframe里。后面我会专门讲这些问题的排查方法。

2. 环境搭建与浏览器配置

2.1 安装Selenium库与WebDriver

Selenium的安装非常简单,直接用pip装就行:

pip install selenium

但这只是第一步。Selenium本身只是一个自动化驱动框架,真正干活的是浏览器。你得告诉它要去操作哪个浏览器,以及怎么启动这个浏览器。

以Chrome为例,你需要下载对应的ChromeDriver,并且要特别注意:ChromeDriver的版本必须和本机Chrome浏览器的版本保持一致。这一点非常关键,版本不匹配时通常会直接报SessionNotCreatedException,浏览器能启动但驱动连不上,这个问题新手几乎都会遇到。

下载完成后,可以把它放在和脚本相同的目录下,或者配置到系统的环境变量里,这样Selenium才能在启动时自动找到它。

正常启动浏览器的代码如下:

from selenium import webdriver from selenium.webdriver.chrome.service import Service # 注意:这里的路径要改成你自己的 service = Service(executable_path='/path/to/chromedriver') driver = webdriver.Chrome(service=service) driver.get('https://example.com')

在现代版本里也可以直接用webdriver.Chrome(),它会自动在PATH中查找驱动,如果查不到就去常见的默认位置寻找。不过为了稳妥起见,我习惯显式指定Service路径,这样出了问题容易排查。

2.2 无头模式与防检测配置

日常调试爬虫时,浏览器窗口弹来弹去很影响效率,尤其是循环好几百个页面时。这时候可以用无头模式(Headless),让浏览器在后台运行,看不到界面,但功能一样。

from selenium import webdriver from selenium.webdriver.chrome.options import Options options = Options() options.add_argument('--headless=new') # 无头模式 options.add_argument('--disable-gpu') # 禁用GPU加速,减少资源占用 options.add_argument('--window-size=1920,1080') # 设置窗口尺寸,避免响应式布局导致元素隐藏 options.add_argument('--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36') driver = webdriver.Chrome(options=options)

这几个参数里我说明一下window-size的作用。有些网站的布局是响应式的,窗口变小时部分元素会被折叠进菜单里,原本能定位的按钮就找不到了。设置一个较大的固定窗口尺寸,能避免这类问题。

不过无头模式也有一些缺点,比如某些网站会对无头浏览器做特征探测,因为无头模式的User-Agent、Canvas指纹、WebGL渲染结果和正常浏览器会有差异。如果发现无头模式抓不到数据但正常模式可以,那就是被反爬识别了。解决思路是尽量伪装成正常浏览器,比如加上真实的User-Agent、配置特定的参数组合,或者直接用有头模式跑,配合界面规模小一点的方式去处理。

2.3 配置加载本地Profile与隐身模式

有些网站登录一次之后,下次再访问就自动带着登录状态。这是因为浏览器把Cookie存在了本地。Selenium默认每次启动都是全新会话,一个妙招就是加载本地Chrome的Profile目录,这样一次登录,之后脚本就能直接用到登录态。

options.add_argument('--user-data-dir=C:/Users/你的用户名/AppData/Local/Google/Chrome/User Data') options.add_argument('--profile-directory=Default')

注意:如果你用一个正在运行的Chrome实例的Profile,会启动失败,因为浏览器进程会锁住文件。正确做法是新建一个专用的Profile目录,比如单独建一个user_data文件夹来存放爬虫专用的登录态,这样既能复用登录状态,又不干扰日常使用。

隐身模式(Incognito)也可以配合使用:

options.add_argument('--incognito')

隐身模式的目的是不留下浏览历史、不存Cookie,适合只做临时访问的场景。

3. 元素定位与数据提取

3.1 八种定位方式与优先级

Selenium提供了八种定位元素的方式,分别是:id、name、class name、tag name、link text、partial link text、xpath、css selector。

从我的实践经验来看,定位优先级大致是这样的:

  • 优先用id,因为有id的元素基本是唯一标识,定位最稳定。
  • 其次用CSS Selector或XPath,这两种表达式能力最强,能应对复杂嵌套。
  • 再次是用class name,但要注意同一个class可能对应多个元素,需要用find_elements来拿全列表。

下面是一个综合示例,模拟在搜索结果页获取数据列表:

from selenium.webdriver.common.by import By # 按id定位 search_box = driver.find_element(By.ID, 'search-input') # 按class定位多个元素 item_list = driver.find_elements(By.CLASS_NAME, 'product-item') # 按CSS选择器定位 price = driver.find_element(By.CSS_SELECTOR, '.product-item .price') # 按XPath定位 title = driver.find_element(By.XPATH, '//h2[@class="title"]')

在讨论定位方式时有一个细节容易被忽略:find_element返回的是第一个匹配的元素,find_elements返回的是所有匹配元素的列表。很多时候定位不报错但取的值不对,就是误用了find_element拿到了一堆元素里的第一个,而那个元素并不是你想要的。

3.2 XPath与CSS选择器详解

XPath和CSS Selector是Selenium爬虫中最常用的两种定位方式,这里我展开捋一遍。

XPath是一种在XML/HTML文档中定位节点的语言,功能非常强大。常见用法:

# 根据属性定位 driver.find_element(By.XPATH, '//input[@name="username"]') # 包含匹配 driver.find_element(By.XPATH, '//a[contains(@href, "/detail/")]') # 文本匹配 driver.find_element(By.XPATH, '//button[contains(text(), "登录")]') # 通过层级关系定位 driver.find_element(By.XPATH, '//div[@class="list"]//div[@class="item"]/span[1]')

这里//表示在整个文档中查找,/表示从当前节点找直接子节点,@表示取属性,[]表示条件过滤。文本匹配和包含匹配在处理动态变化的元素时会特别好用。

CSS Selector的语法相对简洁一些:

# class定位 driver.find_element(By.CSS_SELECTOR, '.username') # id定位 driver.find_element(By.CSS_SELECTOR, '#login-btn') # 属性定位 driver.find_element(By.CSS_SELECTOR, '[data-id="123"]') # 层级定位 driver.find_element(By.CSS_SELECTOR, 'div.list > div.item:first-child')

两种方式怎么选?我的经验是:如果页面结构里class命名比较规范,CSS Selector可读性更好;如果元素之间的关系复杂、需要根据文本内容来定位,XPath更顺手。两者的效率差异在普通场景下可以忽略不计,关键还是可维护性和稳定性。

3.3 显式等待、隐式等待与强制等待

这是Selenium爬虫里最容易出问题的地方,没有之一。网页加载不是瞬间完成的,尤其是Ajax动态渲染的页面,元素出现的时机飘忽不定。如果脚本跑得太快,元素还没渲染出来就已经去定位了,那必然报NoSuchElementException。

Selenium里有三种等待机制,各有各的适用场景:

1. 强制等待(time.sleep)

import time time.sleep(2) # 固定等待2秒

优点是无脑、简单,缺点是效率低,而且太固定。网络一好一坏,就面临等待时间不够或白白等待的问题。

2. 隐式等待(implicitly_wait)

driver.implicitly_wait(10) # 最多等10秒

这个设置了之后,每次用find_element都会生效,只要元素在10秒内出现了就继续执行。它的问题是异常发生时排查困难,因为无法精确知道到底等了多久,而且它不会对页面上的Ajax请求做等待。

3. 显式等待(WebDriverWait)显式等待是推荐的做法,它只在特定条件满足时才放行:

from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC wait = WebDriverWait(driver, 10) login_btn = wait.until(EC.element_to_be_clickable((By.ID, 'login-btn')))

显式等待的好处是精确、高效、可控。它可以等待元素可见、可点击、存在、文本变化等多种条件。平时写爬虫时建议以显式等待为主,等待核心元素出现之后再做后续操作。等的方式从"猜"变成了"等结果",稳定性提升非常明显。

4. 模拟登录状态

4.1 在脚本中保存和复用登录Cookie

很多站点需要登录之后才能抓取数据,最原始的方案是每次跑脚本都走一遍登录流程:输入用户名密码、点登录、处理滑块验证。这套方案又慢又脆弱,验证码稍微复杂一点就卡住了。

更优雅的思路是:把登录过程做一次,然后保存Cookie,后续脚本直接加载Cookie实现免登录。

用Selenium获取当前页面的Cookie非常容易:

import json # 登录操作完成后,保存Cookie cookies = driver.get_cookies() with open('cookies.json', 'w', encoding='utf-8') as f: json.dump(cookies, f)

下次启动时加载:

# 先访问目标域名,再注入Cookie driver.get('https://example.com') with open('cookies.json', 'r', encoding='utf-8') as f: cookies = json.load(f) for cookie in cookies: driver.add_cookie(cookie) # 然后刷新页面,即可保持登录状态 driver.get('https://example.com/需要登录的页面')

这里有个关键细节:add_cookie之前必须先get当前域名的页面。因为Cookie是和域名绑定的,你不在那个域下面直接添加是无效的。常见做法是先访问一次首页,加载完再添加Cookie,最后跳转到目标页面。

4.2 处理常见验证机制的实际经验

现在普通的登录验证已经少了很多,更多的是滑块验证、无感验证、行为验证这几种。这些验证本质上是在判断操作者是人还是机器,核心维度包括:鼠标轨迹、点击节奏、页面停留时间、设备指纹。

作为爬虫开发,我个人的经验是:

  • 简化目标:先确认目标站点有没有配置风控策略,很多站点在普通网络环境下只要求滑块验证,这种情况可以模拟拖拽滑块,用ActionChains来实现从位置A移动到位置B的拖拽操作。
  • 控制节奏:每个步骤之间加一个随机延时,模拟人类的思考时间。比如输入完用户名后等0.5-1.5秒再输入密码,输入密码等0.8-1.2秒再点登录。
  • 优先降低登出频率:如果站点允许长时间保持登录态,那建议用Cookie复用方案,减少触发现验证的概率。

滑块验证的具体模拟,大致思路是找到滑块元素,然后从当前位置按下鼠标左键,移动一段距离再松开。移动的距离根据缺口的位置来定,可以用底图对比或者访问图片获取缺口坐标。这里就不展开具体代码了,因为每个站点的实现差别很大,而且绕过验证这块本身带有灰色性质,不建议走歪路,合规获取数据才是长久之道。

4.3 登录后页面跳转与状态确认

登录成功后,不要急着去抓数据,先确认登录状态是否真的生效了。可以检查页面上是否有用户名、退出登录按钮、个人中心入口等特征元素。

try: wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'user-info'))) print('登录成功') except Exception: print('登录失败,可能被验证或密码错误')

这样提前确认状态的目的是避免后续大量报错浪费时间。我见过好多人脚本跑了几千行,结果因为登录没成功,最后抓下来的全是登录页面的空模板,这个教训很深刻。

5. 数据抓取与页面解析

5.1 提取元素的文本、属性和链接

定位到元素之后,最重要的动作就是"取数据"。常用的操作包括取文本、取属性值、取链接。

# 取文本 title = title_element.text # 取属性 image_url = img_element.get_attribute('src') # 取链接 detail_link = link_element.get_attribute('href')

这里提个容易踩的坑:有些元素的text属性拿不到内容,可能是因为内容渲染在子元素里,或者当前元素根本不含文本节点,数据是通过伪元素(::before、::after)生成的内容。如果是伪元素生成的内容,Selenium的text是拿不到的,需要用execute_script配合getComputedStyle来取:

content = driver.execute_script('return getComputedStyle(arguments[0], "::before").content', element)

还有一种情况是元素的文本内容在页面加载时是空的,后续通过JavaScript写入的,这时需要等待之后再提取,或者检查提取的时机是否太早了。

5.2 翻页与循环抓取的完整逻辑

好多爬虫在单页抓取上表现很好,但一遇到翻页就各种问题。最常见的原因就是翻页按钮的状态判断有误,或者下一页的定位方式发生了变化。

翻页逻辑的核心是:先确认当前页,再找下一页入口,点击之后等待内容加载完成。示例代码如下:

while True: # 抓取当前页数据 items = driver.find_elements(By.CLASS_NAME, 'item') for item in items: title = item.find_element(By.CLASS_NAME, 'title').text # 写进文件或数据库 # 找下一页按钮 try: next_btn = driver.find_element(By.CSS_SELECTOR, '.pagination .next') if 'disabled' in next_btn.get_attribute('class'): break # 没有下一页了 next_btn.click() wait.until(EC.staleness_of(next_btn)) # 等待旧元素失效,暗示页面已刷新 except Exception: break

这里我用了staleness_of(旧元素失效)来替代单纯的time.sleep,这样翻页后的内容加载完之前不会继续抓取。逻辑上比固定等待要可靠得多。

有些网站翻页时URL会变化,比如第1页是page=1,第2页是page=2。这种可以直接通过拼接URL来翻页,速度比点击按钮快得多,也稳定得多。先用Selenium拿到整体的请求链路,再改成直接遍历URL,这种方案值得参考。

5.3 使用页面快照与截图辅助调试

调试Selenium脚本时,最痛苦的事情就是页面报错了,但你看不到页面当前长什么样。Selenium提供了截图方法,出现异常时将页面状态保存成图片,非常有利于排查。

driver.save_screenshot('debug.png')

我习惯在关键节点(比如登录成功后、翻页后、数据加载后)截图留存,一旦脚本出错,直接看截图就能定位问题。还有一个辅助手段是打印当前页面的标题和URL,确认页面跳转是否符合预期:

print(driver.current_url) print(driver.title)

5.4 数据存储到CSV与数据库的取舍

抓到的数据最终要落盘,常见的存储方式有CSV文件、Excel文件、SQLite数据库和MySQL数据库。

CSV最简单直接、方便Excel里打开查看,适合数据量小、结构简单的场景:

import csv with open('data.csv', 'w', newline='', encoding='utf-8', errors='ignore') as f: writer = csv.writer(f) writer.writerow(['标题', '价格', '链接']) for item in items: writer.writerow([title, price, url])

不过这里有个编码问题:Windows下Excel打开CSV时经常中文乱码,一种可行做法是将编码换成UTF-8 with BOM,也就是编码写成utf-8-sig。

如果数据量大、需要做联表查询,SQLite会更合适。Python自带sqlite3模块,不需要额外安装,文件型数据库也方便迁移。MySQL适合多端协同、服务化的场景,但需要额外部署。对于个人爬虫项目,CSV和SQLite基本够用。

6. 性能优化与反爬应对

6.1 提升抓取速度的几种手段

Selenium爬虫的最大缺点是慢,因为它要完整加载浏览器页面(包括图片、脚本、样式表)。性能优化主要从三个方向入手:减少加载资源、控制并发、缩短等待。

第一,通过参数禁用不必要的资源加载。图片、CSS、字体这些对数据提取没直接帮助,可以通过BrowserMob代理或CDP(Chrome DevTools Protocol)来拦截请求。简单的例子:

# 通过CDP禁用图片加载 driver.execute_cdp_cmd('Network.setBlockedURLs', {'urls': ['*.png', '*.jpg', '*.css', '*.woff']})

第二,用多线程或进程池并发执行多个Selenium实例。因为Selenium本身是单浏览器实例,你可以用ThreadPoolExecutor开几个浏览器同时工作。但要注意:并发数太高时,目标站点会更容易触发风控,通常3-5个实例就差不多了,太高了得不偿失。

第三,把稳定的等待时间压缩到最低。用显式等待取代固定sleep,让脚本在最短时间内继续下一步。

6.2 识别与应对基础的反爬机制

站点的反爬思路主要是这几个方向:判断请求频率、分析User-Agent、识别浏览器指纹、检测是否使用自动化工具。

Selenium在启动时默认会在浏览器中暴露一些自动化特征,比如window.navigator.webdriver这个属性会是true。某些站点会检测这个属性然后果断拦截。基础的应对方式是启动参数里加上防检测配置:

options.add_argument('--disable-blink-features=AutomationControlled')

这种手段对一部分站点有效,但并不能解决所有问题。现代风控系统的检测维度非常广,光靠这个参数并不足够。我的看法是:如果目标站点的反爬强度比较高,用Selenium硬闯并不是好的思路,你应该先评估是否值得付出这个成本,或者换一种更合规的获取数据的方式(比如官方API、申请授权、购买数据服务)。

6.3 被抓风险与合规注意事项

这里说点发自内心的经验。很多人学爬虫一开始只是兴趣,但爬着爬着就会碰到法律边界的问题。根据我的观察,这些行为有较高的法律风险:

  • 抓取个人敏感信息且未脱敏;
  • 绕过站点登录或验证码获取非公开数据;
  • 高频访问导致站点服务异常或瘫痪;
  • 把抓取数据用于商业竞争或发布牟利。

做爬虫开发,重要的是提前评估目标网站的robots.txt、用户协议和数据使用条款。合法数据获取的前提是尊重网站的规则。我的原则是:优先使用公开数据、优先申请接口、控制抓取频率、不公开不当传播。这不是套话,是保护自己也保护项目的基本素养。

7. 常见问题与排查技巧

7.1 常见异常类型对照表

Selenium爬虫开发中常见的异常,我整理成了一张表,方便排查:

异常类型触发原因常见解决方法
NoSuchElementException元素未找到检查定位表达式、增加等待、确认页面是否跳转
TimeoutException等待超时调大等待时间、检查选择器是否正确
ElementNotInteractableException元素不可交互检查元素是否被遮挡、是否不可点击、用JS点击
StaleElementReferenceException元素已被替换或过期重新定位元素
SessionNotCreatedExceptionDriver与浏览器版本不匹配更新ChromeDriver到对应版本
InvalidSelectorException定位表达式语法错误检查XPath/Selector语法
WebDriverException驱动或浏览器崩溃查看浏览器进程、更新驱动、重启环境

7.2 处理动态加载的超时与重试机制

除了单个等待异常,还有一种更让人头疼的情况:某个页面正常加载时偶尔会超时,或者某个元素有时能出现有时不能。这种间歇性故障会让整个脚本崩溃。

解决办法是加一个重试机制。比如定位一个关键元素失败时,重试3次,每次间隔随机延时;页面加载失败时,刷新页面或重新请求。示例:

def get_element_with_retry(driver, by, value, retries=3): for i in range(retries): try: element = WebDriverWait(driver, 10).until(EC.presence_of_element_located((by, value))) return element except Exception: print(f'第{i+1}次尝试失败,重新尝试...') driver.refresh() raise Exception(f'元素 {value} 在 {retries} 次重试后仍然未找到')

这个重试逻辑能大幅度提升脚本的健壮性。跑长任务的爬虫,最怕的就是中途挂掉,加几个重试机制成本低、收益高。

7.3 浏览器崩溃与内存泄漏的处理

长时间运行的Selenium爬虫,经常会出现内存占用逐步增大、浏览器越来越卡、最终崩溃的问题。主要原因是浏览器每条tab都有独立的渲染进程,页面上未释放的JS定时器、事件绑定等,都会慢慢积累内存。

我的建议是:定期重启浏览器实例。比如每抓取500个页面之后,主动把当前driver退出,重新启动一个新的driver,然后加载Cookie继续。这样虽然损失了一点时间,但能换来长期稳定运行。跑一次几千上万条的采集任务,中途不崩反而更重要。

还有一种情况是ChromeDriver进程在异常退出后残留。Windows下可以用命令行清理:

taskkill /F /IM chromedriver.exe

macOS/Linux下:

pkill -f chromedriver

7.4 反爬拦截时的降级处理方案

如果真的被站点拦截了,页面返回的内容往往是验证码、空页面、或者403错误。怎么快速发现?最简单的方式是检查页面上是否有特征词:

if 'captcha' in driver.page_source or '访问验证' in driver.title: print('可能被拦截') # 进行降级处理:比如降低频率、切换UA、重启浏览器等

遇到被拦截的情况,暂时停一会儿再继续是比较简单有效的办法。冷却时间可以设置10-30分钟,或者等风控窗口过去后再恢复。如果是IP级别的拦截,那就需要换IP这类操作了。但换IP本身有合规性风险,我不建议在公开内容里教读者怎么利用代理绕封禁,合规地控制频率、协商接口才是更对人负责的选择。

8. 实战项目源码:基于Python+Selenium的爬虫详解

前面把框架和知识点都过了一遍,下面我用一个完整的实战项目来串联这些内容。这个项目比较典型:需要登录、有翻页、有列表数据、有详情数据。我会把完整的源码放出来,代码里注释都写清楚,可以直接参考。

8.1 项目背景与目标页面模拟

假设项目X是一个模拟的内部任务系统,首页有一个文章列表,列表分页展示(10条一页),每篇文章包含标题、发布日期、作者、摘要、链接。列表页右下角有"下一页"按钮,最后一页时按钮会变成disabled状态。文章详情页包含正文全文和浏览量。

目标是:自动化登录系统,遍历全部分页,抓取每篇文章的标题、摘要、链接,并进入详情页抓取正文和浏览量,最后保存到CSV文件。

这个业务流程非常有代表性,它覆盖了登录、翻页、列表抓取、详情页抓取、数据持久化这几个核心环节。把这一套逻辑跑通,以后再遇到别的爬虫项目,只改选择器和解析规则就行。

8.2 完整代码实现与逐行解析

import csv import time import random 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 from selenium.webdriver.chrome.options import Options from selenium.webdriver.chrome.service import Service from selenium.webdriver.common.action_chains import ActionChains def init_driver(): options = Options() options.add_argument('--disable-blink-features=AutomationControlled') options.add_argument('--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36') options.add_argument('--window-size=1920,1080') service = Service(executable_path='/path/to/chromedriver') driver = webdriver.Chrome(service=service, options=options) return driver def login(driver, username, password): driver.get('https://example.com/login') WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, 'username'))).send_keys(username) time.sleep(random.uniform(0.5, 1.0)) driver.find_element(By.ID, 'password').send_keys(password) time.sleep(random.uniform(0.8, 1.5)) driver.find_element(By.CSS_SELECTOR, 'button[type="submit"]').click() # 等待登录成功特征出现 try: WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CLASS_NAME, 'user-info'))) return True except Exception: return False def crawl_list_page(driver, writer): # 等待列表加载 items = WebDriverWait(driver, 10).until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, '.article-list .item'))) for item in items: try: title = item.find_element(By.CSS_SELECTOR, '.title a').text summary = item.find_element(By.CSS_SELECTOR, '.summary').text link = item.find_element(By.CSS_SELECTOR, '.title a').get_attribute('href') publish_date = item.find_element(By.CSS_SELECTOR, '.publish-date').text # 打开详情页 driver.execute_script('window.open(arguments[0], "_blank");', link) driver.switch_to.window(driver.window_handles[-1]) # 提取正文和浏览量 content = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CLASS_NAME, 'article-content'))).text view_count = driver.find_element(By.CLASS_NAME, 'view-count').text.replace('阅读量:', '') writer.writerow([title, summary, link, publish_date, view_count, content[:200]]) driver.close() driver.switch_to.window(driver.window_handles[0]) time.sleep(random.uniform(0.5, 1.0)) except Exception as e: print(f'采集失败:{e}') continue def main(): driver = init_driver() access = login(driver, 'username', 'password') if not access: print('登录失败,程序退出') driver.quit() return driver.get('https://example.com/list') # 列表首页 with open('articles.csv', 'w', newline='', encoding='utf-8-sig') as f: writer = csv.writer(f) writer.writerow(['标题', '摘要', '链接', '发布日期', '浏览量', '正文前200字']) page_num = 1 while True: print(f'正在抓取第{page_num}页') crawl_list_page(driver, writer) # 判断是否有下一页 try: next_btn = driver.find_element(By.CSS_SELECTOR, '.pagination .next') if 'disabled' in next_btn.get_attribute('class'): print('已到最后一页,抓取完成') break next_btn.click() # 等待页面刷新 time.sleep(random.uniform(1, 2)) page_num += 1 except Exception: print('找不到下一页,抓取完成') break driver.quit() print('全部数据已保存到 articles.csv') if __name__ == '__main__': main()

这段代码里每一步都加了等待和异常兜底,把前面讲过的方法都用起来了。实际跑的时候,改动最大的地方会是在选择器上,因为你面对的真实页面和这个模拟页面肯定不一样,但整体的骨架是可以参考的。

8.3 采集结果的验证与质量检查

脚本跑完之后,不要急着收工,数据质量检查是必须的。我会做这几件事:

  • 检查CSV总行数和预期的文章总数是否一致;
  • 抽查几条记录,验证标题、链接、浏览量是否存在明显异常;
  • 检查是否有大量重复数据,如果有,说明翻页逻辑或去重逻辑出了问题;
  • 检查详情页抓取失败的占比,如果失败的太多,说明详情页的等待逻辑或者选择器需要优化。

每次抓完数据,把这些检查做完,数据的可用性就有了基本的保障。

9. 项目经验总结与进一步学习建议

Selenium爬虫这套技术,核心并不在Selenium本身,而在于"稳定可靠地拿到数据"这件事。写完这些内容之后,我再分享几点在实操中最直接的感受。

第一,尽量把等待写精致。很多爬虫快是快,但脆弱得一塌糊涂。网上大部分所谓的Selenium教程都是先教你爬某个特别简单的页面,各种写死sleep,一旦页面加载慢一点就报错。真正能扛住长时间跑的爬虫,靠的是合理的等待策略和异常兜底。

第二,学会看浏览器里的网络面板。Selenium虽然可以模拟操作,但有些数据是藏在接口请求里的,用requests直接调接口比Selenium快几十倍。我现在的习惯是先分析页面得数据是通过接口返回的还是通过渲染生成的,如果是接口返回直接调接口,只有接口难以模拟时才用Selenium。这样效率高很多。

第三,爬虫只是手段,数据才有价值。花几天时间把爬虫写得很酷,不如花点心思思考怎么把抓到的数据清洗、存储、分析出结论来。数据采集只是数据链路的第一环,后面清洗和建模的空间更大。

最后提醒一句:动手之前先看目标网站的规则。做个懂规矩的爬虫工程师,比做个顶级的爬虫工程师更走得长远。这个技术框架是通用的,顺着这套思路去研究新的站点,基本都能有一个清晰的方向。如果这篇文章能让你把Selenium爬虫从"会用"提升到"用得稳",那就达到目的了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询