Selenium-Requests:高效融合浏览器自动化与HTTP请求的Python爬虫利器
1. 项目概述当Selenium遇上Requests如果你做过Web自动化测试或者数据抓取Selenium这个名字你一定不陌生。它就像一个能模拟真人操作的浏览器机器人点击、输入、跳转页面无所不能。但干这行久了你肯定会遇到一个头疼的问题有些操作用浏览器模拟的方式去做效率实在太低了。比如你需要在一个列表页里获取几十个商品的详情数据按照传统Selenium的做法你得让这个“机器人”挨个点击每个商品链接等新页面加载完再解析DOM提取数据。这个过程不仅慢而且极其消耗资源页面加载的CSS、JavaScript、图片都在无情地吞噬你的时间和内存。这时候你可能会想要是能让Selenium直接发送HTTP请求来获取数据就好了。毕竟对于纯粹的数据获取requests库才是王者它轻量、快速、直接。于是一个很自然的想法诞生了能不能把这两者结合起来让Selenium负责处理那些需要JavaScript渲染、复杂交互的登录和导航而让requests来高效地处理后续的数据抓取这就是Selenium-Requests这个开源项目诞生的背景。它不是一个替代品而是一个功能强大的“桥梁”或“拓展包”旨在弥补Selenium在网络请求层面的短板让你在同一个脚本里既能享受Selenium的自动化交互能力又能调用requests的高效请求功能并且最关键的是——共享同一个会话Session和Cookies。这意味着什么意味着你可以用Selenium完成复杂的登录验证比如有滑动验证码的网站登录成功后Selenium-Requests能帮你无缝接管这个已经通过认证的会话。之后的所有数据请求你都可以用轻快的requests风格去完成无需再劳烦笨重的浏览器实例。这对于需要登录后爬取数据、进行自动化测试断言比如直接校验API返回的JSON结构、或者构建混合型自动化工作流的场景来说简直是效率神器。它解决的核心痛点就是在保证状态一致性的前提下将重型浏览器操作与轻型网络请求解耦从而大幅提升脚本的执行效率和资源利用率。2. 核心原理与架构设计解析2.1 会话Session共享的魔法Selenium-Requests最核心的魔法在于它实现了Selenium的WebDriver会话与Pythonrequests库的Session对象之间的状态同步。理解这一点是理解整个项目价值的关键。当我们用Selenium打开一个网站并进行登录操作时浏览器WebDriver会与服务器建立连接并在后续的请求中通过HTTP Cookies来维持登录状态。这些Cookies由服务器下发浏览器自动存储并在每次请求时携带。Selenium-Requests所做的就是在合适的时机通常是在你初始化拓展后的Driver时将当前WebDriver实例中的所有Cookies“提取”出来然后“注入”到一个新建的requests.Session对象中。这个“提取”和“注入”的过程就是会话共享的桥梁。具体来说它通过Selenium WebDriver提供的driver.get_cookies()方法获取到一个包含所有Cookie字典的列表。然后它遍历这个列表使用requests.Session对象的cookies.set()方法将每一个Cookie的键值对、域名、路径等信息原封不动地设置到requests的会话里。这样一来这个requests.Session对象就“继承”了浏览器当前的全部认证状态。此后你用这个Session发起的任何HTTP请求GET、POST等都会自动携带这些Cookies服务器就会认为这是同一个已登录用户的请求从而返回登录后才能访问的数据。这个过程对用户是透明的你只需要像使用普通requests一样调用对应方法即可。2.2 与纯Selenium及纯Requests方案的对比为了更清晰地看到Selenium-Requests的价值我们把它放在一个二维坐标系里进行对比特性维度纯Selenium方案纯Requests方案Selenium-Requests混合方案JavaScript渲染完美支持。可执行JS渲染动态内容。不支持。只能获取初始HTML无法处理JS生成的内容。完美支持。继承Selenium能力。复杂交互登录、点击擅长。可模拟几乎所有用户操作。极难实现。需要手动逆向分析并构造请求对加密、验证码等束手无策。擅长。继承Selenium能力处理复杂步骤。数据请求效率极低。每个请求都需加载完整页面HTML、CSS、JS、图片速度慢资源占用高。极高。直接发送HTTP请求只获取目标数据如JSON速度快资源占用低。极高。在复杂交互后切换到Requests模式进行高效数据抓取。状态Cookies管理自动管理但局限于浏览器实例内。需手动管理Session和Cookies构造请求头。自动同步。从Selenium继承状态后续Requests请求自动携带。开发复杂度中等。定位元素有时不稳定但逻辑直观。高。需要抓包、分析参数、处理反爬如签名、Token。中等偏低。用Selenium解决最难的部分登录用Requests做简单的部分抓数据。典型应用场景需要完整渲染的UI自动化测试、操作流程自动化。无需登录的静态数据抓取、API接口测试。登录后数据抓取、混合型自动化先交互后校验API、性能敏感型爬虫。从对比可以看出Selenium-Requests并非要取代二者而是取其精华。它承认了一个现实在当今的Web生态中纯粹的技术栈往往力有不逮。用Selenium处理那些requests搞不定的“脏活累活”如复杂登录然后用requests去高效完成Selenium不擅长的“批量作业”如数据拉取这才是务实且高效的工程选择。2.3 项目架构浅析虽然Selenium-Requests的源码并不复杂但其设计体现了清晰的职责分离思想。它通常以一个自定义的WebDriver类的形式提供比如SeleniumRequests。这个类继承自你原本使用的WebDriver如ChromeDriver或FirefoxDriver。在初始化这个自定义Driver时它内部会做两件事初始化父类正常启动一个浏览器实例确保所有原始的Selenium功能完好无损。创建并同步Session在后台创建一个requests.Session实例并将当前浏览器的Cookies同步过去。同时它会将requests.Session对象的方法如.get(),.post(),.put()等以新的属性或方法形式“混入”Mixin到这个自定义Driver对象中。这样你就可以通过driver.request.get(url)这样的方式来发起一个携带了浏览器状态的HTTP请求其返回的直接就是一个requests.Response对象你可以用.json()、.text、.status_code等熟悉的方式去处理它。这种设计非常巧妙它没有破坏Selenium原有的任何API只是在其基础上增加了新的、可选的功能。你可以选择继续用driver.find_element(...).click()也可以随时切换到driver.request.get(...)两者共享同一个底层会话状态互不干扰又紧密协作。3. 环境搭建与核心API实战3.1 安装与基础配置安装过程非常简单通过pip即可完成。建议在虚拟环境中操作。pip install selenium-requests同时你需要确保已经安装了对应浏览器的WebDriver。以Chrome为例你需要安装Chrome浏览器。从 ChromeDriver官网 下载与你的Chrome版本匹配的驱动程序。将chromedriver可执行文件放在系统PATH路径下或者直接在代码中指定其路径。一个最基本的启动示例如下from seleniumrequests import Chrome # 指定chromedriver路径如果不在PATH中 driver_path /path/to/your/chromedriver # 创建SeleniumRequests的Chrome驱动实例 driver Chrome(executable_pathdriver_path) # 注意对于新版Selenium可能需要使用Service对象 # 或者使用无头模式不显示浏览器界面适合服务器环境 from selenium.webdriver.chrome.options import Options chrome_options Options() chrome_options.add_argument(--headless) # 开启无头模式 chrome_options.add_argument(--disable-gpu) chrome_options.add_argument(--no-sandbox) # Linux服务器有时需要 driver Chrome(optionschrome_options)注意Selenium 4.x版本之后初始化WebDriver的方式有所变化推荐使用Service类来指定驱动路径。selenium-requests的示例可能基于旧版你需要根据实际情况调整from selenium.webdriver.chrome.service import Service from seleniumrequests import Chrome service Service(executable_pathdriver_path) driver Chrome(serviceservice, optionschrome_options)3.2 核心API详解与使用模式初始化driver后你就拥有了一个强化版的WebDriver对象。除了所有标准的Selenium方法如find_element,get,execute_script它还新增了一个request属性这是一个标准的requests.Session对象。1. 执行Selenium操作处理复杂交互这部分和纯Selenium完全一样。例如完成一个登录# 1. 导航到登录页 driver.get(https://example.com/login) # 2. 定位元素并操作 username_input driver.find_element(id, username) password_input driver.find_element(id, password) submit_button driver.find_element(xpath, //button[typesubmit]) username_input.send_keys(your_username) password_input.send_keys(your_password) submit_button.click() # 3. 等待登录成功可根据页面变化判断 # 例如等待某个登录后才出现的元素 from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC wait WebDriverWait(driver, 10) welcome_element wait.until(EC.presence_of_element_located((id, welcome-msg))) print(登录成功:, welcome_element.text)2. 切换至Requests模式高效获取数据登录成功后假设你需要从另一个API接口获取用户数据。# 使用driver.request发起GET请求 # 注意此时的请求会自动携带浏览器中的登录Cookies api_url https://example.com/api/user/profile response driver.request.get(api_url) # response是一个requests.Response对象 print(f状态码: {response.status_code}) print(f响应头: {response.headers}) if response.status_code 200: # 假设接口返回JSON user_data response.json() print(f用户名: {user_data.get(name)}) # 或者获取文本 # text_content response.text else: print(f请求失败: {response.text})3. 发起更复杂的Requests请求driver.request支持requests库的所有参数你可以轻松地设置请求头、传递数据、使用代理等。# 发起一个POST请求携带JSON数据 post_url https://example.com/api/data/submit payload {key1: value1, key2: value2} headers {X-Custom-Header: MyValue} # 注意这里用的是driver.request.post post_response driver.request.post(post_url, jsonpayload, headersheaders) print(post_response.json()) # 使用代理如果需要 proxies {http: http://10.10.1.10:3128, https: http://10.10.1.10:1080} proxied_response driver.request.get(http://httpbin.org/ip, proxiesproxies, timeout5)4. 会话的持久化与复用一个更高级的用法是你可以将当前的会话状态主要是Cookies保存下来在另一个脚本或稍后的时间点重新加载避免重复登录。# 保存Cookies到文件 import pickle after_login_cookies driver.get_cookies() with open(session_cookies.pkl, wb) as f: pickle.dump(after_login_cookies, f) driver.quit() # 在另一个脚本中恢复会话 driver2 Chrome() driver2.get(https://example.com/) # 先访问一下域名让浏览器建立上下文 with open(session_cookies.pkl, rb) as f: cookies pickle.load(f) for cookie in cookies: # 添加cookie时可能需要删除‘expiry’字段因为它可能是浮点数 if expiry in cookie: # 有时需要处理过期时间格式 cookie.pop(expiry, None) try: driver2.add_cookie(cookie) except Exception as e: print(f添加cookie失败: {cookie.get(name)}, 错误: {e}) # 刷新页面此时应处于登录状态 driver2.refresh() # 现在driver2.request也可以使用恢复的会话了3.3 实战案例爬取需要登录的电商网站商品列表假设我们要爬取某电商网站“我的订单”列表。这个页面需要登录且列表是通过前端JavaScript异步加载的但我们可以找到其后台API。步骤1用Selenium完成登录from seleniumrequests import Chrome from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By import time driver Chrome() driver.get(https://fake-ecommerce.com/login) # 输入凭据 driver.find_element(id, username).send_keys(my_account) driver.find_element(id, password).send_keys(my_password) driver.find_element(id, login-btn).click() # 等待登录成功跳转到首页或出现用户菜单 wait WebDriverWait(driver, 15) wait.until(EC.presence_of_element_located((By.ID, user-avatar))) print(登录成功) time.sleep(2) # 稍作等待确保会话稳定步骤2分析并找到数据API在浏览器开发者工具F12的“网络”Network选项卡中过滤XHR/Fetch请求浏览“我的订单”页面观察哪个请求返回了订单列表的JSON数据。假设我们找到的API是https://fake-ecommerce.com/api/orders?page1size20。步骤3用Requests模式高效抓取我们不需要用Selenium去翻页点击直接构造请求即可。base_order_api https://fake-ecommerce.com/api/orders all_orders [] for page in range(1, 6): # 假设抓取前5页 params {page: page, size: 20} # 关键使用driver.request自动携带登录cookies resp driver.request.get(base_order_api, paramsparams) if resp.status_code 200: page_data resp.json() orders page_data.get(data, []) all_orders.extend(orders) print(f第{page}页获取到{len(orders)}条订单。) if len(orders) 20: # 如果不满一页说明是最后一页 break # 出于礼貌可以加个短暂延迟避免请求过快 time.sleep(1) else: print(f第{page}页请求失败状态码{resp.status_code}) break print(f总共获取到{len(all_orders)}条订单。) # 后续可以处理all_orders数据如保存到文件 import json with open(orders.json, w, encodingutf-8) as f: json.dump(all_orders, f, ensure_asciiFalse, indent2) driver.quit() # 最后关闭浏览器通过这个案例你可以清晰地看到两种模式的完美配合Selenium攻克了登录堡垒而Requests则以极高的效率完成了数据的批量收割。这正是Selenium-Requests的核心价值所在。4. 高级技巧、性能优化与反反爬策略4.1 请求头User-Agent管理与伪装虽然driver.request会自动同步Cookies但默认的请求头可能和浏览器发出的不完全一致这有时会被服务器识别为脚本请求。一个重要的优化点是统一或伪装User-Agent。Selenium启动的浏览器有它自己的User-Agent。我们可以获取它并让requests会话使用同一个。# 获取Selenium控制浏览器的真实User-Agent selenium_ua driver.execute_script(return navigator.userAgent;) print(f浏览器UA: {selenium_ua}) # 将其设置到driver.request的会话头中 driver.request.headers.update({ User-Agent: selenium_ua, # 可以添加其他常见浏览器头使其更像真实浏览器请求 Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Connection: keep-alive, }) # 现在通过driver.request发出的所有请求UA都和浏览器一致了4.2 处理动态Token与签名现代网站尤其是涉及敏感操作的API经常使用动态Token如CSRF Token或对请求参数进行签名。这些Token往往嵌在页面的HTML中或者由初始的JavaScript文件生成。策略先用Selenium提取再用于Requests。# 1. 在Selenium渲染的页面中提取Token driver.get(https://target.com/data-page) # 假设Token在一个id为‘csrf-token’的meta标签里 csrf_token_element driver.find_element(css selector, meta[namecsrf-token]) csrf_token csrf_token_element.get_attribute(content) print(f提取到的CSRF Token: {csrf_token}) # 2. 将Token加入到Requests的请求头中 headers_for_api { X-CSRF-TOKEN: csrf_token, X-Requested-With: XMLHttpRequest } # 3. 使用带Token的请求头调用API api_response driver.request.post(https://target.com/api/submit, json{action: query}, headersheaders_for_api)对于更复杂的签名Signature算法如果算法逻辑在前端JavaScript中你甚至可以用Selenium的execute_script方法直接执行那段JS函数来计算签名然后将结果用于requests的请求参数中。这实现了“Selenium解密Requests运输”的分工。4.3 性能优化与资源管理无头模式Headless在不需要观察浏览器界面的生产环境或服务器上务必使用无头模式。这能节省大量GUI渲染资源。from selenium.webdriver.chrome.options import Options opts Options() opts.add_argument(--headlessnew) # Chrome较新版本推荐使用‘new’ opts.add_argument(--disable-gpu) opts.add_argument(--no-sandbox) opts.add_argument(--disable-dev-shm-usage) # 解决Linux下共享内存问题 driver Chrome(optionsopts)智能等待与请求间隔Selenium侧使用WebDriverWait配合expected_conditions进行显式等待绝对避免使用time.sleep进行固定时长等待这能大幅缩短Selenium操作阶段的耗时。Requests侧在循环发起大量请求时务必在请求间添加随机延迟如time.sleep(random.uniform(1, 3))并处理429Too Many Requests等状态码实现简单的速率限制避免对目标服务器造成压力或被封禁。会话复用与池化对于需要长时间运行或处理大量独立任务的脚本可以考虑维护一个“已登录Driver池”。初始化多个Driver并完成登录后放入池中。任务到来时从池中取出一个Driver及其关联的request会话使用用完后归还。这避免了为每个小任务都重复执行耗时的登录和浏览器启动过程。4.4 错误处理与健壮性设计网络请求和浏览器自动化充满了不确定性健壮的错误处理至关重要。from requests.exceptions import RequestException, Timeout, ConnectionError from selenium.common.exceptions import NoSuchElementException, TimeoutException def safe_selenium_operation(driver, url): try: driver.get(url) # ... 其他操作 except TimeoutException: print(f页面加载超时: {url}) # 可以尝试刷新或记录错误 except NoSuchElementException: print(未找到页面元素页面结构可能已变化。) # 可能需要更新元素定位器 def safe_request_operation(driver, api_url, retries3): for attempt in range(retries): try: resp driver.request.get(api_url, timeout10) resp.raise_for_status() # 如果状态码不是200会抛出HTTPError异常 return resp.json() except Timeout: print(f请求超时 ({attempt1}/{retries}): {api_url}) time.sleep(2 ** attempt) # 指数退避 except ConnectionError: print(f网络连接错误 ({attempt1}/{retries}): {api_url}) time.sleep(2) except RequestException as e: print(f请求发生异常: {e}) break # 其他请求错误可能不需要重试 print(f请求失败已重试{retries}次: {api_url}) return None # 在代码中组合使用 safe_selenium_operation(driver, https://example.com/login) # ... 登录操作 data safe_request_operation(driver, https://example.com/api/data) if data: process_data(data)这种结构化的错误处理能让你脚本的容错能力大大增强在遇到网络波动、页面改版或临时反爬时不至于直接崩溃。5. 常见问题排查与实战心得在实际使用Selenium-Requests的过程中你会遇到一些典型问题。这里记录了我踩过的一些坑和解决方案。5.1 Cookies同步失败或请求未登录状态问题现象用Selenium成功登录后使用driver.request.get()请求接口却返回401未授权或跳转到登录页。排查思路与解决检查目标域名确保Selenium登录的网站域名和你用driver.request请求的API域名是相同的或者是主域与子域的关系如www.example.com和api.example.com。requests的Session默认会根据Cookie的domain和path属性决定是否发送。如果跨域了Cookie不会自动发送。你需要检查浏览器中Cookie的Domain属性并在初始化Driver后手动为driver.request的Session添加跨域Cookie如果安全策略允许。检查Cookie的‘Secure‘和‘HttpOnly‘属性通过driver.get_cookies()查看Cookie详情。如果关键的会话Cookie被标记为HttpOnly这意味着它无法通过JavaScriptdocument.cookie读取但Selenium的get_cookies()方法通常可以获取到。然而Secure标记意味着该Cookie只应通过HTTPS连接传输。确保你的driver.request请求使用的是https://协议。同步时机问题selenium-requests的Cookie同步通常发生在Driver初始化时。如果你在Driver初始化之后才进行登录操作那么driver.request会话中的Cookie还是旧的、未登录的状态。解决方案是在登录完成后手动触发一次Cookie同步。虽然该库可能没有直接提供同步方法但我们可以手动实现def sync_cookies_to_requests(driver): 将当前浏览器的Cookies同步到driver.request的session中 selenium_cookies driver.get_cookies() # 清空requests session旧的cookies driver.request.cookies.clear() # 将Selenium的cookies添加到requests session for cookie in selenium_cookies: # 构造一个requests兼容的Cookie对象 # 注意需要处理一些可能不兼容的属性如‘expiry‘可能是浮点数 c {k: v for k, v in cookie.items() if k.lower() in (name, value, domain, path)} # 可选处理secure和httponly但requests的SimpleCookie可能不支持直接设置 # 更简单的方式是使用set方法 driver.request.cookies.set(cookie[name], cookie[value], domaincookie.get(domain), pathcookie.get(path, /)) # 在登录成功后调用 sync_cookies_to_requests(driver)会话过期登录状态可能有过期时间。如果Selenium操作和Requests请求间隔时间过长会话可能已失效。需要在关键请求前检查状态或实现会话保活/重登录机制。5.2 请求被目标网站屏蔽反爬虫即使使用了真实的浏览器UA和正确的Cookies你的请求仍可能被识别为爬虫。应对策略请求头完善除了User-Agent尽量补全其他常见的请求头如Accept、Accept-Language、Referer这个很重要可以设置为上一个页面的URL、Accept-Encoding等。用浏览器访问一次目标API在开发者工具中复制完整的请求头信息进行模仿。请求参数模拟有些API会校验Referer来源或者要求携带特定的非功能性参数。仔细比对浏览器发出的请求和你用driver.request发出的请求在参数上是否有差异。频率控制这是最重要的。避免在极短时间内发出大量请求。加入随机延迟并监控响应状态码。如果收到429请求过多或403禁止访问应立即暂停一段时间如10-30分钟。IP代理如果单个IP被封锁需要使用代理IP池。可以通过driver.request.get(..., proxiesproxies)参数来设置。注意代理的管理获取、验证、切换本身是一个复杂的子课题。降级到Selenium如果所有requests模式的请求都被封而通过浏览器手动访问却正常说明对方可能使用了更高级的反爬手段如TLS指纹、浏览器指纹等。此时可能不得不完全退回到Selenium模式来获取数据尽管效率会降低。5.3 与异步Ajax加载页面的配合很多现代网页的数据是滚动加载或点击“加载更多”按钮异步获取的。对于这种场景Selenium-Requests依然有优势。策略用Selenium触发数据加载但用Requests捕获数据包。用Selenium打开页面并执行触发加载的动作如滚动到底部、点击按钮。在触发动作之前打开浏览器开发者工具的“网络”选项卡并清空记录。触发动作后观察“网络”选项卡中新增的XHR/Fetch请求找到真正返回数据的那个API请求记录下它的URL、方法、请求头、请求体参数。在代码中你可以选择方案A推荐直接用driver.request按照记录的格式去模拟这个API请求。这通常是最快的因为可能只需要请求一次就能拿到大量数据而无需等待多次滚动加载。方案B如果你无法完全模拟API请求例如参数有复杂加密那么可以继续用Selenium多次触发加载动作但每次触发后用driver.request去请求那个固定的、已识别的API URL前提是它的参数是可预测的如page2。5.4 个人实战心得明确分工牢记Selenium-Requests的最佳实践模式是“Selenium做一次性的、复杂的、必须用浏览器才能完成的工作主要是登录和导航到目标数据页Requests做重复性的、简单的、纯粹的数据获取工作”。不要用它来点击每一个分页链接那是Selenium的笨办法。状态检查在重要的数据抓取循环开始前可以先用driver.request发一个简单的、到用户信息页的请求检查返回的数据是否包含登录信息以此验证会话是否依然有效。资源清理脚本结束时务必调用driver.quit()来彻底关闭浏览器和对应的WebDriver进程。使用try...finally块来保证这一点避免留下僵尸进程。日志记录为你的脚本添加详细的日志记录包括登录成功/失败、请求的URL、状态码、获取的数据条数等。这在调试和监控长时间运行的爬虫任务时非常有用。它不是银弹Selenium-Requests极大地扩展了我们的能力但它并不能解决所有反爬问题。面对顶尖的反爬系统如Cloudflare 5秒盾、Distil等你可能需要更专业的工具如undetected-chromedriver或服务。理解它的边界在合适的场景使用它才能最大化其价值。这个项目本质上是一种“务实”的工程思维体现它不追求技术上的纯粹性而是以解决问题、提升效率为导向。当你下次面对一个需要登录且数据量大的采集任务时不妨先想想能不能用Selenium-Requests这把“瑞士军刀”把难题分解然后优雅地解决。