Selenium与Requests混合架构:自动化获取动态Referer与Sign参数实战

Selenium与Requests混合架构:自动化获取动态Referer与Sign参数实战
1. 项目概述为什么我们需要自动化获取动态参数在数据采集和自动化测试领域我们经常会遇到一些“狡猾”的网站。它们不再满足于简单的静态页面而是通过前端JavaScript动态生成关键参数比如Referer来源页和Sign签名来验证请求的合法性。这些参数就像进入高级会所的门票没有正确的票你连门都进不去。手动去浏览器开发者工具里一个个复制这些值不仅效率低下而且对于需要批量、定时执行的任务来说根本不可行。这就是“使用 Selenium 和 Requests 自动化获取动态 Referer 和 Sign”这个项目的核心价值所在。它不是一个简单的爬虫脚本而是一套完整的工程化解决方案旨在破解前端动态生成参数的反爬机制。简单来说它的工作流是用 Selenium 这个“浏览器机器人”模拟真人操作触发页面加载和JavaScript执行从而拿到动态生成的Referer和Sign然后将这些“新鲜出炉”的参数交给高效、轻量的 Requests 库去发起真正的数据请求。这种“Selenium 探路 Requests 冲锋”的组合拳完美兼顾了模拟的逼真性和执行的高效性。我遇到过太多类似的场景比如需要从某个电商平台抓取商品评论但评论数据是通过AJAX加载的请求头里必须携带一个由前端代码实时计算出来的sign参数又比如需要模拟登录一个后台系统其登录接口会校验Referer是否来自指定的登录页面。手动处理这些一天也干不了多少活。而这个自动化方案正是为这类中高级反爬场景量身定制的适合有一定Python基础希望提升数据获取自动化水平和工程化能力的朋友。2. 核心思路与架构设计为什么是“Selenium Requests”面对动态参数常见的思路有几种一是纯逆向工程硬啃JavaScript代码找到生成算法后用Python复现二是使用无头浏览器如Puppeteer, Playwright全程模拟。前者对逆向能力要求高且网站稍一更新算法就可能失效维护成本大后者虽然逼真但资源消耗大、速度慢不适合大规模请求。我们的“Selenium Requests”混合架构则取了一个巧妙的平衡点。它的核心设计哲学是将“参数获取”一个需要完整浏览器环境、但频率低的行为与“数据请求”一个无需浏览器环境、但频率高的行为解耦。2.1 架构拆解与选型理由Selenium 的角色参数“收割机”职责启动一个真实的浏览器如Chrome加载目标页面等待页面JavaScript执行完毕从而让动态参数Referer,Sign等在内存或网络请求中“现身”。为什么是Selenium它成熟、稳定社区支持好能完美模拟人类操作点击、输入、滚动对于需要交互才能触发参数生成的场景不可或缺。虽然速度不如更新的Playwright但其广泛的资料和兼容性使其成为稳妥的起点。Requests 的角色数据“搬运工”职责接收从Selenium那里获取到的、包含动态参数的请求信息URL、Headers、Cookies、Data以其高效、简洁的API发起HTTP请求并获取响应数据。为什么是Requests它是Python生态中事实标准的HTTP库极其高效、灵活。用Requests发起一千个请求的时间Selenium可能连十个页面都没加载完。将高频的数据抓取任务交给它能极大提升整体效率。关键桥梁信息提取与传递这是本项目的技术核心。Selenium如何把参数准确地“交给”Requests通常有两种途径监听网络请求使用Selenium的performance日志或第三方插件如selenium-wire、browser mob proxy拦截浏览器发出的XHR/Fetch请求直接从中提取请求头含Referer和请求体含Sign。执行JavaScript代码如果参数是在前端全局变量或某个函数执行结果中我们可以用Selenium的execute_script方法直接注入JS代码将参数值提取出来。2.2 方案优势与潜在挑战优势高成功率由于完全模拟了真实浏览器环境能绕过绝大多数基于客户端JavaScript的反爬。维护相对简单无需完全逆向参数生成算法只需定位到参数出现的位置即可。即使前端代码微调只要参数生成逻辑和出现位置不变脚本仍可能有效。效率折中相比纯浏览器自动化效率有数量级提升相比纯逆向开发成功率更高。挑战与应对Selenium 指纹检测一些高级网站会检测浏览器自动化特征。需要通过ChromeOptions添加--disable-blink-featuresAutomationControlled、设置excludeSwitches移除enable-automation等参数进行反检测。参数提取的稳定性网络请求的监听可能因网站升级而失效。代码中需要增加健壮性判断比如等待特定请求出现、设置超时、准备备用提取方案如执行JS。环境依赖需要安装浏览器驱动如chromedriver并管理其与浏览器版本的匹配。3. 环境准备与核心工具详解工欲善其事必先利其器。我们先来搭建一个稳定、可复现的自动化环境。3.1 基础环境搭建首先确保你已安装Python建议3.7及以上版本。然后通过pip安装核心库pip install selenium requests selenium-wireselenium: 浏览器自动化核心库。requests: HTTP请求库。selenium-wire: 这是本项目的“神器”之一。它在Selenium基础上扩展了拦截和修改浏览器网络请求的能力比使用原生performance日志更方便、更强大。3.2 浏览器与驱动配置我们以Chrome为例。你需要做两件事安装Chrome浏览器确保已安装。下载匹配的ChromeDriver访问 ChromeDriver官网 或使用国内镜像下载与你的Chrome浏览器版本号完全相同的驱动。将下载的chromedriver或chromedriver.exe放在一个已知目录如项目根目录或将所在路径添加到系统环境变量PATH中。注意浏览器与驱动版本不匹配是Selenium新手最常踩的坑会导致无法启动浏览器。务必确保版本号一致。3.3 关键库的深度配置打造“隐身”浏览器一个裸奔的Selenium浏览器很容易被识别。我们需要对其进行深度伪装。from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.chrome.service import Service import time def create_stealth_driver(): chrome_options Options() # 1. 基础反检测设置 chrome_options.add_argument(--disable-blink-featuresAutomationControlled) chrome_options.add_experimental_option(excludeSwitches, [enable-automation]) chrome_options.add_experimental_option(useAutomationExtension, False) # 2. 伪装成普通用户浏览器 chrome_options.add_argument(user-agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36) # 可以添加其他常见参数如禁用GPU加速某些环境下需要 # chrome_options.add_argument(--disable-gpu) # 3. 可选无头模式不显示浏览器界面适合服务器 # chrome_options.add_argument(--headlessnew) # Chrome 109 推荐方式 # 4. 使用Service指定驱动路径如果没加PATH # service Service(executable_path/path/to/your/chromedriver) # driver webdriver.Chrome(serviceservice, optionschrome_options) # 5. 直接启动如果chromedriver已在PATH driver webdriver.Chrome(optionschrome_options) # 6. 执行CDP命令覆盖navigator.webdriver属性 driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: Object.defineProperty(navigator, webdriver, { get: () undefined }); }) return driver实操心得--disable-blink-featuresAutomationControlled和excludeSwitches是隐藏自动化标志的关键。execute_cdp_cmd是在页面加载前注入JS覆盖navigator.webdriver属性这是应对检测的更深层手段。无头模式谨慎使用有些网站能检测无头模式。如果必须用需要添加更多参数伪装如--no-sandbox,--disable-dev-shm-usage并可能需配合user-agent和window-size的伪装。4. 动态参数捕获实战监听网络请求这是整个流程中最核心的一步。我们将使用selenium-wire来捕获浏览器发出的特定请求并从中提取我们需要的Referer和Sign。4.1 使用 Selenium-Wire 拦截请求selenium-wire的driver.requests属性记录了所有捕获到的请求。我们可以遍历它找到我们关心的那个。假设我们要抓取一个商品详情页的数据而数据是通过一个API接口https://api.example.com/product/detail以POST方式获取的其请求体中包含一个动态的sign参数请求头中的Referer是商品页的URL。from seleniumwire import webdriver # 注意从seleniumwire导入 import json import time def capture_dynamic_params(target_url): # 使用 selenium-wire 的配置它继承了普通Options options { disable_encoding: True, # 有时禁用编码更容易查看请求体 request_storage: memory, # 请求存储在内存处理完及时清理 } # 创建driver传入selenium-wire的选项和之前的chrome_options chrome_options create_stealth_driver().options # 假设create_stealth_driver返回driver这里获取其options driver webdriver.Chrome( seleniumwire_optionsoptions, optionschrome_options ) target_api_pattern api.example.com/product/detail # 目标API的URL特征 try: # 1. 访问目标页面触发动态请求 driver.get(target_url) print(页面加载中等待动态请求...) time.sleep(3) # 等待页面JS执行和网络请求。更好的做法是使用显式等待WebDriverWait # 2. 遍历所有请求找到目标请求 target_request None for request in driver.requests: if target_api_pattern in request.url and request.method POST: target_request request print(f找到目标请求: {request.url}) break if not target_request: print(未找到目标API请求可能页面未正确加载或模式不匹配。) # 可以在这里尝试滚动页面、点击按钮等交互来触发请求 # driver.execute_script(window.scrollTo(0, document.body.scrollHeight);) # time.sleep(2) # 再次遍历 driver.requests for request in driver.requests: if target_api_pattern in request.url and request.method POST: target_request request break if target_request: # 3. 提取关键参数 # 提取 Referer (来自请求头) referer target_request.headers.get(Referer, ) print(f捕获到 Referer: {referer}) # 提取请求体中的 sign # 注意请求体可能是bytes需要根据实际情况解码如json, form-data body target_request.body sign None if body: try: # 假设是JSON格式的请求体 body_decoded body.decode(utf-8) body_dict json.loads(body_decoded) sign body_dict.get(sign) # 根据实际字段名调整 except (UnicodeDecodeError, json.JSONDecodeError): # 如果不是JSON可能是form-urlencoded格式 # body_decoded body.decode(utf-8) # 使用 urllib.parse.parse_qs 解析 pass print(f捕获到 Sign: {sign}) # 4. 同时我们还需要这个请求的其他部分用于后续的Requests模拟 api_url target_request.url request_headers dict(target_request.headers) # 复制一份headers # 可能还需要cookies可以从driver获取 cookies driver.get_cookies() return { api_url: api_url, referer: referer, sign: sign, headers: request_headers, cookies: cookies, request_body: body # 原始请求体可能需要 } else: return None except Exception as e: print(f捕获过程中发生错误: {e}) return None finally: driver.quit() # 确保退出浏览器释放资源4.2 参数提取的进阶技巧与稳定性保障上面的基础方法可能不够稳定。我们需要增加更多保障。使用显式等待替代time.sleeptime.sleep是固定等待效率低。应使用WebDriverWait等待特定元素出现或特定条件成立这表示页面已加载完成。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By # 等待某个关键元素出现再开始捕获请求 wait WebDriverWait(driver, 10) wait.until(EC.presence_of_element_located((By.ID, some-product-element)))更精准的请求过滤与等待selenium-wire支持设置request_interceptor和response_interceptor可以在请求发出或响应返回时进行实时处理避免遍历全部请求。def interceptor(request): if target_api_pattern in request.url: print(f拦截到目标请求: {request.url}) # 此时可以直接存储或处理request # 注意在拦截器中修改request属性会影响实际发送的请求 driver.request_interceptor interceptor # 访问页面... # 请求发出后在interceptor函数中处理处理复杂的请求体格式除了JSON还可能是multipart/form-data或二进制。需要根据Content-Type头灵活解析。content_type target_request.headers.get(Content-Type, ) if application/json in content_type: # 按JSON解析 elif application/x-www-form-urlencoded in content_type: from urllib import parse body_decoded body.decode(utf-8) params_dict parse.parse_qs(body_decoded) sign params_dict.get(sign, [None])[0]应对动态Cookie有时Sign的生成依赖于Cookie中的某个令牌。我们需要确保Selenium拿到的Cookie被完整地传递给Requests会话。# 将Selenium的Cookie格式转换为Requests可用的字典格式 def get_cookies_dict(driver): selenium_cookies driver.get_cookies() cookies_dict {} for cookie in selenium_cookies: cookies_dict[cookie[name]] cookie[value] return cookies_dict5. 使用Requests发起高效数据请求拿到所有“门票”后就该让Requests上场了。我们的目标是完美复现之前浏览器发出的那个请求。5.1 构建请求会话Session使用requests.Session()可以自动保持Cookies模拟浏览器会话状态这对于需要登录或依赖会话的网站至关重要。import requests def make_authenticated_request(api_url, referer, sign, headers, cookies_dict, original_bodyNone): 使用捕获到的参数发起请求 # 1. 创建一个会话 session requests.Session() # 2. 更新会话的请求头关键一步 # 注意直接使用捕获的headers可能包含一些不希望被覆盖的默认头如Connection # 我们主要关心的是Content-Type, Referer, User-Agent等 session.headers.update({ User-Agent: headers.get(User-Agent, Mozilla/5.0 ...), Referer: referer, # 使用动态捕获的Referer Content-Type: headers.get(Content-Type, application/json), # 可以添加其他必要的头如 Accept, Origin 等 Accept: application/json, text/javascript, */*; q0.01, Origin: https://www.example.com, # 根据实际情况修改 X-Requested-With: XMLHttpRequest, # 常见于AJAX请求 }) # 3. 设置Cookies session.cookies.update(cookies_dict) # 4. 准备请求体 # 我们需要将sign参数塞回请求体 request_data None if original_body and sign is not None: try: # 假设是JSON我们解析后替换sign字段 body_decoded original_body.decode(utf-8) data_dict json.loads(body_decoded) data_dict[sign] sign # 更新sign request_data json.dumps(data_dict, ensure_asciiFalse) except: # 如果解析失败或者不是JSON可能需要更复杂的处理 # 例如form-data这里简化处理 print(警告请求体处理复杂可能需要自定义逻辑) request_data original_body # 暂时回退到原始bodysign可能已在内 # 5. 发起请求 try: # 根据请求方法决定 # 假设是POST response session.post(api_url, datarequest_data, timeout10) response.raise_for_status() # 如果状态码不是200抛出HTTPError # 6. 处理响应 print(f请求成功状态码: {response.status_code}) # 假设响应是JSON result_data response.json() return result_data except requests.exceptions.RequestException as e: print(fRequests请求失败: {e}) if hasattr(e, response) and e.response is not None: print(f错误响应内容: {e.response.text[:500]}) # 打印前500字符 return None except json.JSONDecodeError as e: print(f响应JSON解析失败: {e}) print(f原始响应文本: {response.text[:500]}) return None5.2 请求复现的精细调整真实场景往往更复杂需要注意以下几点Header 的清洗与覆盖从Selenium捕获的headers可能包含一些requests库会自动管理或不应被覆盖的头如Host,Connection,Content-Length。最好只选择性更新关键头。Sign 的时效性很多sign参数是具有时效性的可能几分钟后就失效。这意味着你不能用一个sign无限次请求。解决方案是将Selenium获取参数和Requests发起请求这两个步骤紧密耦合或者将获取参数的逻辑封装成一个函数在每次发起数据请求前或定期调用一次。频率控制与IP代理大规模请求时必须考虑频率控制time.sleep和使用IP代理池避免被目标网站封禁。import time from random import uniform # 在请求间随机休眠 time.sleep(uniform(1, 3)) # 休眠1到3秒之间的随机时间 # 使用代理需自行准备代理IP proxies { http: http://your-proxy-ip:port, https: http://your-proxy-ip:port, } response session.post(api_url, datarequest_data, proxiesproxies, timeout10)6. 工程化整合与完整流程示例现在我们把所有模块串联起来形成一个完整的、可复用的自动化流程。import json import time from urllib import parse from seleniumwire import webdriver from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By import requests class DynamicParamScraper: def __init__(self, driver_pathNone): self.driver None self.driver_path driver_path self.api_data None def init_driver(self): 初始化经过伪装的Selenium-Wire驱动 options { disable_encoding: True, request_storage: memory, suppress_connection_errors: False, # 显示连接错误 } chrome_options webdriver.ChromeOptions() chrome_options.add_argument(--disable-blink-featuresAutomationControlled) chrome_options.add_experimental_option(excludeSwitches, [enable-automation]) chrome_options.add_experimental_option(useAutomationExtension, False) chrome_options.add_argument(user-agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...) if self.driver_path: from selenium.webdriver.chrome.service import Service service Service(executable_pathself.driver_path) self.driver webdriver.Chrome(serviceservice, optionschrome_options, seleniumwire_optionsoptions) else: self.driver webdriver.Chrome(optionschrome_options, seleniumwire_optionsoptions) # 覆盖webdriver属性 self.driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: Object.defineProperty(navigator, webdriver, {get: () undefined}) }) return self.driver def capture_params_from_page(self, page_url, api_url_keyword, wait_element_selectorNone, timeout15): 从页面捕获动态参数 :param page_url: 目标页面URL :param api_url_keyword: 目标API URL包含的关键字 :param wait_element_selector: 等待页面元素出现的CSS选择器用于判断页面加载完成 :param timeout: 超时时间秒 :return: 参数字典 或 None print(f[*] 开始访问页面: {page_url}) self.driver.get(page_url) # 等待页面关键元素加载如果提供了选择器 if wait_element_selector: try: WebDriverWait(self.driver, timeout).until( EC.presence_of_element_located((By.CSS_SELECTOR, wait_element_selector)) ) print(f[] 页面元素 {wait_element_selector} 加载完成) except Exception as e: print(f[-] 等待页面元素超时或出错: {e}) # 不立即退出可能请求已经发出 # 等待一段时间确保网络请求完成可以结合更智能的等待 time.sleep(2) # 查找目标请求 target_request None print(f[*] 正在扫描网络请求寻找包含 {api_url_keyword} 的请求...) for request in self.driver.requests: if api_url_keyword in request.url: print(f[] 发现候选请求: {request.method} {request.url}) # 这里可以增加更多过滤条件如请求方法、特定header等 target_request request break # 找到第一个就退出可根据需要调整 if not target_request: print([-] 未找到目标API请求。尝试滚动页面或交互...) # 示例滚动到页面底部触发懒加载 self.driver.execute_script(window.scrollTo(0, document.body.scrollHeight);) time.sleep(2) # 再次查找 for request in self.driver.requests: if api_url_keyword in request.url: target_request request break if target_request: print(f[] 成功锁定目标请求: {target_request.url}) # 提取参数 referer target_request.headers.get(Referer, ) content_type target_request.headers.get(Content-Type, ) sign None request_body target_request.body # 解析请求体获取sign根据Content-Type if request_body: if application/json in content_type: try: body_str request_body.decode(utf-8) body_json json.loads(body_str) sign body_json.get(sign) or body_json.get(token) # 常见签名字段名 except Exception as e: print(f[-] JSON请求体解析失败: {e}) elif application/x-www-form-urlencoded in content_type: try: body_str request_body.decode(utf-8) parsed_qs parse.parse_qs(body_str) # parse_qs返回值为列表取第一个 sign_list parsed_qs.get(sign, []) if sign_list: sign sign_list[0] except Exception as e: print(f[-] Form-urlencoded请求体解析失败: {e}) else: print(f[*] 请求体格式为: {content_type}, 可能需要自定义解析逻辑。) # 获取Cookies cookies_list self.driver.get_cookies() cookies_dict {c[name]: c[value] for c in cookies_list} self.api_data { url: target_request.url, method: target_request.method, headers: dict(target_request.headers), cookies: cookies_dict, referer: referer, sign: sign, body: request_body, content_type: content_type } print(f[] 参数捕获完成。Referer: {referer[:50]}... | Sign: {sign}) return self.api_data else: print([-] 最终未能捕获到目标请求参数。) return None def make_request_with_params(self, extra_paramsNone): 使用捕获的参数发起请求 :param extra_params: 额外的请求参数用于覆盖或补充 if not self.api_data: print([-] 未找到已捕获的API数据请先运行 capture_params_from_page。) return None session requests.Session() # 1. 处理Headers # 从捕获的headers中选取关键部分避免冲突 captured_headers self.api_data[headers] session.headers.update({ User-Agent: captured_headers.get(User-Agent, session.headers[User-Agent]), Referer: self.api_data[referer], Content-Type: self.api_data[content_type], Accept: application/json, text/javascript, */*; q0.01, X-Requested-With: XMLHttpRequest, }) # 如果extra_params中有headers则更新 if extra_params and headers in extra_params: session.headers.update(extra_params[headers]) # 2. 处理Cookies session.cookies.update(self.api_data[cookies]) # 3. 处理请求体 data_to_send None if self.api_data[body] and self.api_data[sign] is not None: # 这里需要根据content_type和原始body重新构造包含最新sign的body # 这是一个简化示例实际需要更严谨的逻辑 if application/json in self.api_data[content_type]: try: body_str self.api_data[body].decode(utf-8) body_dict json.loads(body_str) body_dict[sign] self.api_data[sign] # 更新sign data_to_send json.dumps(body_dict, ensure_asciiFalse) except: data_to_send self.api_data[body] # 回退 else: # 其他格式暂时直接发送原始body假设sign已在其中 data_to_send self.api_data[body] # 4. 发起请求 try: print(f[*] 使用捕获的参数向 {self.api_data[url]} 发起 {self.api_data[method]} 请求...) if self.api_data[method].upper() POST: resp session.post(self.api_data[url], datadata_to_send, timeout15) elif self.api_data[method].upper() GET: # GET请求的参数通常在URL中这里简化处理 resp session.get(self.api_data[url], timeout15) else: print(f[-] 不支持的请求方法: {self.api_data[method]}) return None resp.raise_for_status() print(f[] 请求成功! 状态码: {resp.status_code}) # 尝试解析JSON响应 try: return resp.json() except: return resp.text[:1000] # 返回文本前1000字符 except requests.exceptions.RequestException as e: print(f[-] 请求异常: {e}) return None def close(self): 关闭浏览器驱动 if self.driver: self.driver.quit() print([*] 浏览器驱动已关闭) # 使用示例 if __name__ __main__: scraper DynamicParamScraper() try: scraper.init_driver() # 假设我们要抓取某个商品详情其数据接口URL包含 productDetail params scraper.capture_params_from_page( page_urlhttps://www.example.com/product/12345, api_url_keywordproductDetail, wait_element_selector.product-name, # 等待商品名称元素出现 timeout10 ) if params: # 可以在这里对params进行一些修改或补充如果需要 # extra {headers: {Custom-Header: value}} result scraper.make_request_with_params() if result: print([] 获取到的数据示例:, result) # 这里可以解析result保存数据... except Exception as e: print(f主流程出错: {e}) finally: scraper.close()7. 常见问题排查与实战技巧在实际操作中你肯定会遇到各种各样的问题。下面是我总结的一些常见坑点和解决思路。7.1 问题排查清单问题现象可能原因排查步骤与解决方案Selenium无法启动浏览器或闪退1. Chrome与ChromeDriver版本不匹配。2. 浏览器驱动不在PATH或路径错误。3. 端口被占用或浏览器已有实例在运行。1. 检查Chrome版本 (chrome://version/)下载对应驱动。2. 使用Service(executable_path‘绝对路径’)显式指定驱动路径。3. 关闭所有Chrome进程或使用options.add_argument(‘--remote-debugging-port9222’)指定其他端口。捕获不到目标网络请求1. 页面未完全加载请求尚未发出。2. 请求由其他框架如WebSocket发起非XHR/Fetch。3.selenium-wire配置问题或请求被过滤。4. 目标请求是图片、CSS等静态资源被忽略。1. 增加等待时间使用WebDriverWait等待特定元素或条件。2. 尝试在页面进行交互点击、滚动后再捕获。3. 检查seleniumwire_options确保未设置exclude_hosts等过滤。使用driver.requests查看所有请求列表。4. 确认目标请求的URL特征是否正确在浏览器开发者工具的Network面板中核实。提取的Sign参数无效或过期1. Sign具有时效性捕获后未立即使用。2. Sign的生成可能依赖其他动态变量如时间戳、随机数捕获的请求体不完整。3. 网站使用了更复杂的签名算法仅靠请求体中的字段不够。1.将捕获和请求两个步骤紧密连接捕获后立刻使用。对于批量任务考虑每次请求前都重新捕获一次参数可优化为间隔捕获。2. 分析多个请求找出Sign的生成规律。可能需要用execute_script执行页面JS来计算Sign。3. 升级为逆向工程方案或考虑使用PyExecJS、Node.js子进程来执行页面中的签名函数。使用Requests发起的请求返回403/404等错误1. Headers不完整或错误缺少关键头如Origin,X-CSRFToken。2. Cookies未正确传递或已过期。3. Referer不正确或格式不对。4. 请求体格式或编码错误。1. 仔细对比浏览器中原始请求和你的Requests请求的所有Headers用工具如diff比对差异。2. 确保Selenium获取的Cookies完整传递并检查是否有HttpOnly的CookieSelenium能获取Requests也能使用。3. 确保Referer值与浏览器中完全一致包括协议http/https和末尾斜杠。4. 使用Postman或curl先模拟成功请求再对照修改Python代码。网站检测到Selenium自动化1. 基础的enable-automation开关未禁用。2.navigator.webdriver属性未覆盖。3. 浏览器指纹如插件、语言、分辨率与普通用户不符。1. 确保使用了excludeSwitches和disable-blink-features参数。2. 确保执行了CDP命令覆盖navigator.webdriver。3. 添加更多伪装参数--langzh-CN,--window-size1920,1080。可考虑使用更高级的指纹对抗库如undetected-chromedriver。7.2 高级技巧与优化建议使用undetected-chromedriver应对高强度检测如果目标网站反爬极强可以尝试这个专门为绕过检测而修改的ChromeDriver。它能更好地隐藏自动化特征。pip install undetected-chromedriverimport undetected_chromedriver as uc driver uc.Chrome() # 注意uc可能与selenium-wire的集成需要额外处理分离参数获取与数据抓取对于需要抓取大量数据的场景频繁启动关闭Selenium效率太低。可以设计一个“参数刷新器”守护进程定期如每5分钟用Selenium获取一次最新的有效参数存入Redis或文件而多个“数据抓取器”进程则从共享存储中读取参数并用Requests发起请求。应对异步加载和无限滚动有些页面的数据是滚动到底部后异步加载的。你需要让Selenium模拟滚动操作并持续监听新的网络请求。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_height # 滚动完成后再捕获请求日志与错误重试在生产环境中务必加入详细的日志记录如logging模块并对网络请求失败、参数获取失败等情况设计重试机制和降级方案。这个方案的核心价值在于其灵活性和高成功率。它可能不是最快的但往往是破解那些依赖客户端JavaScript生成关键参数网站的最直接、最可靠的方法。随着你经验的积累可以在此基础上不断优化比如引入并发处理、设计更健壮的参数刷新策略、整合到Scrapy等大型爬虫框架中从而构建起一套强大的数据自动化获取系统。