
1. 项目概述当爬虫遇上JavaScript渲染十年前我刚入行做爬虫时90%的网站还是服务端渲染用requestsBeautifulSoup就能轻松搞定。但如今随着前端框架的普及越来越多的关键数据都通过JavaScript动态加载传统爬虫拿到的往往只是个空壳HTML。上周我帮朋友爬取一个电商平台的价格数据时就遇到了典型的动态渲染问题——页面源码里根本找不到商品价格标签因为价格是通过AJAX请求和前端模板渲染出来的。这就是我们需要Selenium这类工具的典型场景。不同于传统爬虫只获取初始HTMLSelenium能完整模拟浏览器行为等待JavaScript执行完毕后再获取最终DOM树。根据2023年的技术调研Top 100电商网站中有78%采用了前端渲染技术其中React/Vue占比超过60%这使得Selenium几乎成为现代爬虫工程师的必备技能。2. 核心需求解析2.1 为什么传统爬虫会失效以爬取小红书笔记列表为例如果用requests直接获取import requests url https://www.xiaohongshu.com/explore response requests.get(url) print(response.text) # 输出的HTML中找不到笔记内容你会发现返回的HTML里根本没有笔记数据只有一个div idapp/div的空容器。真正的数据是通过页面加载后执行JavaScript发送XHR请求到API接口将返回的JSON数据渲染到DOM中2.2 Selenium的解决方案Selenium通过控制真实浏览器如Chrome来解决这个问题from selenium import webdriver driver webdriver.Chrome() driver.get(https://www.xiaohongshu.com/explore) # 此时页面已完成JS渲染 print(driver.page_source) # 包含完整的DOM树关键优势在于完整执行所有JS代码自动处理AJAX异步加载支持与页面元素交互点击、滚动等3. 环境配置与基础用法3.1 环境搭建推荐使用Python 3.8配合最新版Seleniumpip install selenium还需要下载对应浏览器的WebDriverChromehttps://chromedriver.chromium.org/downloadsFirefoxhttps://github.com/mozilla/geckodriver/releases注意WebDriver版本必须与本地浏览器版本完全匹配否则会报错3.2 基础操作示例一个完整的动态爬取流程from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC driver webdriver.Chrome() try: driver.get(https://example.com) # 显式等待元素加载 element WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, .dynamic-content)) ) # 获取渲染后的数据 print(element.text) # 执行JavaScript driver.execute_script(window.scrollTo(0, document.body.scrollHeight);) finally: driver.quit() # 重要必须关闭浏览器4. 高级技巧实战4.1 处理无限滚动页面社交类网站常用无限滚动加载解决方案last_height driver.execute_script(return document.body.scrollHeight) while True: driver.execute_script(window.scrollTo(0, document.body.scrollHeight);) time.sleep(2) # 等待新内容加载 new_height driver.execute_script(return document.body.scrollHeight) if new_height last_height: break last_height new_height4.2 绕过反爬机制常见反爬手段及应对WebDriver检测# 修改navigator.webdriver属性 driver.execute_cdp_cmd( Page.addScriptToEvaluateOnNewDocument, {source: Object.defineProperty(navigator, webdriver, {get: () undefined})} )指纹识别options webdriver.ChromeOptions() options.add_argument(--disable-blink-featuresAutomationControlled)验证码处理使用2Captcha等付费服务设置cookie绕过登录4.3 性能优化技巧无头模式options webdriver.ChromeOptions() options.add_argument(--headlessnew) # Chrome 112新语法禁用图片加载prefs {profile.managed_default_content_settings.images: 2} options.add_experimental_option(prefs, prefs)复用浏览器会话# 首次启动时保存session信息 driver.command_executor._commands[GET] (GET, /session/$sessionId/url) session_id driver.session_id executor_url driver.command_executor._url # 后续可直接连接 driver2 webdriver.Remote(command_executorexecutor_url, optionsoptions) driver2.session_id session_id5. 企业级应用方案5.1 分布式爬虫架构结合ScrapySelenium的方案# middlewares.py class SeleniumMiddleware: def process_request(self, request, spider): if request.meta.get(selenium): driver spider.driver driver.get(request.url) body driver.page_source return HtmlResponse(driver.current_url, bodybody)5.2 容器化部署Docker-compose示例version: 3 services: chrome: image: selenium/standalone-chrome ports: - 4444:4444 shm_size: 2gb crawler: build: . depends_on: - chrome environment: - SELENIUM_HOSTchrome6. 常见问题排查6.1 元素定位失败典型错误# 错误示范直接查找动态元素 price driver.find_element(By.CSS_SELECTOR, .price) # 可能抛出NoSuchElementException # 正确做法使用显式等待 price WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, .price)) )6.2 内存泄漏处理长期运行的Selenium实例容易内存泄漏建议定期重启浏览器使用--disable-dev-shm-usage参数监控进程内存使用6.3 超时设置策略根据不同网络环境调整# 页面加载超时 driver.set_page_load_timeout(30) # 脚本执行超时 driver.set_script_timeout(10) # 元素查找超时 driver.implicitly_wait(5) # 不推荐优先用显式等待7. 最新技术演进7.1 Playwright替代方案微软推出的新工具比Selenium更快from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch() page browser.new_page() page.goto(https://example.com) print(page.content())7.2 CDP协议直接调用通过Chrome DevTools Protocol获取数据driver.execute_cdp_cmd(Network.enable, {}) driver.execute_cdp_cmd(Network.setRequestInterception, { patterns: [{urlPattern: *}] }) # 监听XHR响应 def intercept_request(request): if api/data in request[request][url]: print(request[response][body]) driver.add_listener(Network.responseReceived, intercept_request)我在实际项目中发现对于复杂的SPA应用结合Selenium的DOM操作和直接CDP调用能获得最佳效果。比如先通过Selenium触发页面交互再用CDP监听网络请求这样可以精准获取API数据而无需解析DOM。