news 2026/9/28 5:41:56

爬虫遇到403 Forbidden怎么办?requests与Selenium双方案实战破解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
爬虫遇到403 Forbidden怎么办?requests与Selenium双方案实战破解

做爬虫最烦的一行输出是什么?不是超时,不是编码错误,而是那醒目的403 Forbidden。我在做一个公开数据归集项目时就栽过跟头:requests老老实实带上 UA,带上 Referer,带上 Accept,一切看起来都很正常,结果对方服务器给了一个裸 403,响应体里连“请求过于频繁”的提示都没有,就是纯粹的拒绝。后来试了很多技巧,真正解决我手头 80% 403 问题的方案其实就两条路:一条是把普通请求的每一个细节打磨到位,另一条是直接换 Selenium 上真实浏览器。

这篇文章就把这两条路线摊开聊。我不打算只给你贴几个代码片段,而是把我从原理到实战、从踩坑到选型的一整套判断方法写出来。适合谁看?如果你是用requests写爬虫被反爬拦得怀疑人生的人,或者正在纠结“要不要为了一个页面去学 Selenium”的人,这篇就是写给你的。

1. 403 错误是怎么来的:先搞懂服务器在拒绝什么

1.1 403 的玄机:服务器为什么认识你这个机器人

HTTP 状态码 403 翻译成大白话就是:你的请求到达了服务器,服务器也完全理解你的意图,但它就是不让你过。这和 404 有本质区别——404 是“这里没有这个东西”,403 是“东西就在这,但不给你”。

在爬虫场景里,触发 403 的原因大致归成三类:

  • 请求特征异常:你的 User-Agent 太老、缺失或明显不是浏览器产生的;Headers 字段之间矛盾;请求顺序不符合正常人浏览页面的逻辑。
  • 访问频率异常:短时间大量请求同一接口,触发 IP 级别的频率限制。这类 403 往往带有提示,比如“请求过快,请稍后再试”。
  • 浏览器环境异常:服务器那边有 JS 检测、浏览器指纹校验、Cookie 合法性校验。你根本没执行 JS,或者执行了但指纹对不上,它就会认为你不是一个真实用户。

第一类最友好,改改 Header 基本能过。第二类要靠代理、限速和重试策略。第三类最棘手,也是我们后面要聊的 Selenium 主战场。

1.2 一个请求从发出到被拒绝,中间发生了什么

从抽象角度看,服务器判断你是否为真人,发生在两个层面。

第一个层面是HTTP 请求层。requests发出的请求和 Chrome 浏览器发出的请求,在协议栈上其实有大量细节差异:浏览器的请求头字段更全、字段顺序更接近真实语义、带 Sec-Fetch 系列、带完整的 Accept 和 Accept-Language;更关键的是浏览器会先请求 HTML,再根据 HTML 里的资源链接发起 CSS、JS、图片等子请求,最后才轮到数据接口。普通爬虫是“单刀直入”直接打接口,服务器看一眼请求上下文就明白了。

第二个层面是执行环境层。现代网站可以在页面里嵌入一段 JS,这段 JS 会读取浏览器的 navigator、window、canvas、WebGL 等大量属性,然后把计算结果发回服务器。如果计算结果异常,或这段 JS 根本没被执行,你的请求就会被标上“非真人”标签。普通requests连 JS 都执行不了,在这一层天然吃亏。

1.3 先判断你的 403 是哪一种

拿到一个 403,先别急着改代码。我建议你用五分钟做一轮定位判断,这能帮你少走很多弯路。

先看响应内容。如果响应体里明确写了“Request blocked”“Too many requests”“需要完成安全验证”之类的字样,那基本是频率或 JS 挑战类问题。如果响应体是空的、或者只是默认的 nginx 错误页,那更可能是请求头或 IP 被拉黑。

再看触发条件。今天第一次访问就 403?还是爬了 200 条数据之后才开始 403?前者多半是请求头或环境问题,后者多半是频率问题。最后一个技巧:换一个完全干净的 IP 再试一次。如果新 IP 下同样的请求能通,说明你原来的 IP 已经被标记了。

这三类判断做完,你在后面两个方案里就知道该选哪条路。

2. 普通请求方案:requests 如何破解 403

2.1 先把请求头伪装到位

很多人的第一个 403 纯粹是“裸请求”造成的。requests.get(url)默认的 User-Agent 是python-requests/x.x.x,这是极其明显的爬虫标记,服务器看到它基本可以无脑拒绝。

解决思路不是随便换一个 UA 就行,而是把一整套请求头都补齐,形成一个“自洽的浏览器形象”。服务器校验的往往不是单个字段,而是多个字段之间的关联性。比如你要带上 Chrome 120 的 UA,那 Accept、Accept-Language、Sec-Fetch-Mode、Sec-Ch-Ua 这些字段最好都和 Chrome 120 的实际行为对得上。

下面这套是我常用的基础模板:

import requests HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/120.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9," "image/avif,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Accept-Encoding": "gzip, deflate, br", "Connection": "keep-alive", "Upgrade-Insecure-Requests": "1", "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "same-origin", "Sec-Fetch-User": "?1", } resp = requests.get("https://data.example.com/api/list", headers=HEADERS) print(resp.status_code)

注意这里的两个细节。一是Accept-Encoding别乱填,如果你请求了br(Brotli)压缩但requests没有解压,拿到的就是乱码,不是 403 的问题却是另一种坑。二是Sec-Fetch-*这几个字段很重要,它们是这几年服务器做“请求是否符合浏览器行为”判断的重要依据。

2.2 Session 与 Cookie:从“每次重新自我介绍”到“服务器记得我”

如果你用requests.get直接逐个请求,每次都是一个全新的匿名身份。真实用户不是这么浏览网站的:他先打开首页,服务器种下 Cookie,然后他带着这些 Cookie 去访问详情页。服务器通过 Cookie 识别“这个人刚刚来过”。

所以爬虫也要模拟这个过程。用requests.Session()代替裸的requests.get(),Session 对象会自动保存服务器返回的 Cookie,并且在后续请求中自动带上。

session = requests.Session() session.headers.update(HEADERS) # 先访问入口页,拿到基础 Cookie 和必要的隐藏参数 home_resp = session.get("https://data.example.com/") print(home_resp.status_code) # 再访问目标接口,此时带上了第一步种下的 Cookie api_resp = session.get("https://data.example.com/api/list") print(api_resp.status_code)

这一步对很多 403 都有效。特别是一些页面通过中间件种 Cookie 做“首次访问合法性校验”的网站,你直接打接口必然 403,但先逛一圈首页再进接口就能过。

2.3 重试、限速与代理:请求频率才是大头

Header 和 Cookie 只是第一关。很多时候你明明伪装得很好,但爬了几百条之后突然 403,这就是触发了频率限制。

我见过太多人栽在“不加间隔的 for 循环”上。服务器端的频率判断通常是滑动窗口机制:比如 60 秒内同一个 IP 超过 30 次请求就拉黑。你要做的很简单:在每次请求之间加随机延迟,让请求节奏看起来更像真人。

import time import random from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry session = requests.Session() session.headers.update(HEADERS) # 配置重试策略:遇到 403/5xx 时指数退避重试 retry = Retry( total=3, status_forcelist=[403, 500, 502, 503], backoff_factor=1, # 第1次等1秒,第2次等2秒,第3次等4秒 allowed_methods=["GET", "POST"], ) adapter = HTTPAdapter(max_retries=retry) session.mount("http://", adapter) session.mount("https://", adapter) # 先进首页拿 Cookie session.get("https://data.example.com/") for page in range(1, 11): resp = session.get(f"https://data.example.com/api/list?page={page}") if resp.status_code == 403: print(f"第 {page} 页被拒,睡觉后重试") time.sleep(30) else: print(f"第 {page} 页 OK,状态 {resp.status_code}") # 随机间隔,避免固定节奏被识别 time.sleep(random.uniform(2, 5))

关于代理,我多说一句。当 IP 被标记之后,立刻换代理池轮询是对的,但有几条红线要注意:不要用免费代理池里的高匿名代理去访问带账号体系的网站,不要用一个代理短时间内高频请求,不要每次请求都换一个不同地区的代理——一个正常用户不会在 10 秒内从北京跳到上海再跳到广州。代理的轮换策略应该是低频慢换,而不是每次请求都换。另外,采购代理的时候优先选“住宅代理”或者“ISP 代理”,数据中心代理 IP 在风控严格的网站上命中率偏高。

2.4 普通请求的局限:为什么有些 403 你搞不定

到这里,普通请求方案的几条腿就都站上了:完整的请求头、Session 保持 Cookie、重试退避、随机限速、代理轮换。这套组合拳能解决掉相当一部分 403,但我必须说句实话:它存在天花板。

天花板主要有三层:

  • 无法执行 JS。有些网站的反爬逻辑全部写在 JS 里,页面加载时会通过 JS 生成一个动态 Token 拼在请求头或 URL 上。你连 JS 都不执行,自然拿不到这个 Token。
  • 无法伪造浏览器指纹。WebGL 渲染结果、Canvas 指纹、字体列表、屏幕分辨率、时区,这些属性在普通请求里完全不存在。服务器可以要求“客户端必须上报一套完整且合理的浏览器指纹”,普通请求直接出局。
  • 无法模拟复杂交互。点击、滚动、拖拽、悬停、输入,这些行为在普通请求里做不到。如果服务器要求“先滚动到页面底部,数据接口才会被触发”,普通请求依然出局。

遇到这三类情况,你与其跟请求头死磕,不如切换思路。

3. Selenium 方案:用真实浏览器解决“行为识别”类 403

3.1 Selenium 到底做了什么,和普通请求的差别是什么

Selenium 的本质是通过 WebDriver 协议驱动一个真实的浏览器实例。它不是一个“模拟浏览器”,它就是浏览器本身。Chrome 会真实启动,JS 会真实执行,CSS 会真实加载,Cookie 和指纹由 Chromium 内核自动生成。

普通请求和 Selenium 在服务器眼里完全是两种东西:

维度普通请求 (requests)Selenium 浏览器
JS 执行不执行完整执行
浏览器指纹缺失真实 Chromium 指纹
请求上下文单请求直达完整加载流程(HTML→CSS→JS→接口)
速度毫秒级秒级
资源占用极低每个实例数百 MB 内存
维护成本低中等(驱动管理、版本兼容)
适用场景纯接口、无强校验强 JS 校验、复杂交互、数据动态渲染

从这张表能看出一个明显结论:Selenium 不是为了“更快”而存在的,是为了“更像真人”而存在的。

3.2 Selenium 4 环境搭建与最小可用脚本

Selenium 4 相比老版本最大的变化是内置了 Service 管理,不再需要手动设置 executable_path。我用它的时候通常会搭配webdriver-manager,自动下载配对的 Chromedriver,省去手动维护驱动的麻烦。

pip install selenium webdriver-manager

最小可用脚本长这样:

from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from webdriver_manager.chrome import ChromeDriverManager options = Options() # 无头模式,后台运行不弹窗 options.add_argument("--headless=new") options.add_argument("--window-size=1920,1080") options.add_argument("--disable-gpu") options.add_argument("--no-sandbox") # 关键参数:去掉自动化控制标志 options.add_argument("--disable-blink-features=AutomationControlled") service = Service(ChromeDriverManager().install()) driver = webdriver.Chrome(service=service, options=options) try: driver.get("https://data.example.com/") # 显性等待,等目标元素出现在页面中 items = WebDriverWait(driver, 10).until( EC.presence_of_all_elements_located((By.CSS_SELECTOR, ".data-row")) ) for item in items: print(item.text) finally: driver.quit()

这段脚本里有几个点值得解释一下。

--disable-blink-features=AutomationControlled的作用是去掉 Chromium 里一个叫navigator.webdriver的自动标记。正常浏览器这个属性是undefined,而 WebDriver 控制的浏览器里它是true。很多网站的反爬脚本第一件事就是查这个属性,看见true直接判死刑。加这个参数能去掉大部分场景下的自动化标记。

--headless=new是新版无头模式。老的无头模式(--headless)在某些版本里会被特殊处理,产生一些指纹差异。新无头模式已经非常接近有头浏览器。

WebDriverWait是必须养成的习惯。很多新手用time.sleep(3)硬等页面加载,这既慢又不稳。显性等待让程序在目标元素出现的那一刻立即继续,既不浪费时间也不会因为固定时长不够而误杀。

3.3 无头模式与反检测配置:为什么你打开了页面还是 403

用 Selenium 还是被 403,这是最让人崩溃的场景。我遇到过不少次,排查到最后都是“浏览器指纹”问题。

先说一个常见的坑:无头模式本身可能被识别。老版本的--headless有很多特征,比如navigator.plugins为空、navigator.languages不完整、时区和语言不匹配。新版--headless=new改善很大,但也不敢说百分百。

更隐蔽的是指纹一致性问题。你的 Chrome 版本是 120,但系统的时区是东八区,语言是 zh-CN,屏幕分辨率是 1920×1080……如果某些属性之间互相矛盾,比如 Chrome 的Accept-Language显示 en-US,但navigator.language是 zh-CN,就会被风控模型标记为可疑。

有两类开源工具可以解决这个问题:

一类是Stealth.js / selenium-stealth。它会在页面加载前注入一段 JS,把navigator.webdriver、navigator.plugins、navigator.languages、chrome.runtime等数十个自动化特征全部用真实值覆盖。这样相当于给自动化浏览器做了一层“套皮”。

另一类是undetected-chromedriver。它比标准 Selenium 更进一步,直接 patch 了 Chromedriver 的底层通信过程,让浏览器不认为自己是被 WebDriver 控制的。实测下来它在应对商业级风控时的成功率更高。

但我必须给一段提醒:工具只能降低被识别的概率,不能保证 100% 不被识别。更重要的是,用这些工具去绕过自己无权访问的内容是不道德且可能违法的事情。我在这里介绍它们,是为了让你在自动化测试、处理自己有权限的公开数据场景时,理解背后的原理。真正的长期爬虫项目靠的是频率控制、IP 管理和合理的采集节奏,不是硬刚风控。

3.4 Selenium 性能代价与正确使用姿势

Selenium 真正的痛点在性能和稳定性。

性能方面,一个 Chrome 实例启动要一两秒,打开一个复杂页面又要几秒,单个请求动辄 3-8 秒,是普通请求的几十倍。内存占用更是夸张,一个无头 Chrome 常驻内存 300-500MB。你要开 10 个并发实例,基本就是一台小服务器了。

稳定性方面,WebDriver 的版本兼容性是个老难题。Chrome 自动更新到某个新版本,你的 Chromedriver 没跟上,启动就报错。webdriver-manager能解决一部分,但很多企业的内网环境根本访问不了自动下载地址。另外浏览器偶尔会因为页面 JS 报错、内存溢出、网络波动而崩溃,你的爬虫程序必须做好异常兜底。

所以正确的使用姿势是:把 Selenium 当作“攻坚”工具,而不是“常规”工具。先用普通请求跑 80% 的接口,碰到普通请求搞不定的 403,再降级到 Selenium 处理。这也是我要在第四章详细演示的思路。

4. 实操对比:同一个目标,两种方案分别怎么解 403

4.1 场景设定与合规边界

为了把两种方案放在同一起跑线上比较,我们用同一个假设场景:一个公开展示的数据列表页,页面数据通过异步接口加载,接口只有带上合法 Cookie 才能访问,直接裸请求返回 403。

先声明合规边界:这个场景只讨论公开数据的采集,不涉及登录后隐私数据、付费内容、评论区个人信息等敏感点。你在跟进任何目标时,永远要先看对方的 robots.txt 和服务条款,采集频率保持克制——别把别人的服务器打崩,这是爬虫从业者的基本素养。

4.2 普通请求完整示例与效果分析

先写普通请求版本。这个版本的核心是:补齐 Headers、用 Session 先逛首页拿 Cookie、加随机延迟和重试。

import time import random import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/122.0.0.0 Safari/537.36", "Accept": "application/json, text/plain, */*", "Accept-Language": "zh-CN,zh;q=0.9", "Referer": "https://data.example.com/", "Origin": "https://data.example.com", } session = requests.Session() session.headers.update(HEADERS) retry = Retry(total=2, status_forcelist=[403, 500, 502], backoff_factor=0.5) adapter = HTTPAdapter(max_retries=retry) session.mount("https://", adapter) # 第一步:访问首页,拿下种子 Cookie entry = session.get("https://data.example.com/", timeout=10) print("首页状态:", entry.status_code, "Cookie数:", len(session.cookies)) # 第二步:带 Cookie 请求数据接口 data_url = "https://data.example.com/api/list?page=1" resp = session.get(data_url, timeout=10) print("数据接口状态:", resp.status_code) if resp.status_code == 200: data = resp.json() print("拿到数据条数:", len(data.get("items", []))) else: print("响应体前 200 字符:", resp.text[:200])

在大多数情况下,这套配置能解决“裸请求被 403”的问题。它的耗时是多少?两次请求加起来不到 0.5 秒,非常轻量。但如果服务器在后端校验了更复杂的指纹,或者首页必须在浏览器里执行 JS 才能种下真正的有效 Cookie,这个方案就会失败——表现为首页状态是 200,但拿到的 Cookie 是无效的,数据接口继续 403。

4.3 Selenium 完整示例与效果分析

接着是 Selenium 版本。这个版本的核心是:用真实浏览器执行 JS、生成完整指纹、走完页面整个加载流程,然后把数据提取出来。

import time from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from webdriver_manager.chrome import ChromeDriverManager options = Options() options.add_argument("--headless=new") options.add_argument("--window-size=1920,1080") options.add_argument("--disable-blink-features=AutomationControlled") options.add_argument("--no-sandbox") options.add_argument("--disable-dev-shm-usage") options.add_argument("--lang=zh-CN") # 通过 CDP 注入 stealth 脚本,隐藏自动化特征 STEALTH_JS = """ Object.defineProperty(navigator, 'webdriver', {get: () => undefined}); Object.defineProperty(navigator, 'languages', {get: () => ['zh-CN', 'zh']}); Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5]}); """ service = Service(ChromeDriverManager().install()) driver = webdriver.Chrome(service=service, options=options) try: driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {"source": STEALTH_JS}) driver.get("https://data.example.com/") print("页面标题:", driver.title) # 等待表格数据渲染出来 rows = WebDriverWait(driver, 10).until( EC.presence_of_all_elements_located((By.CSS_SELECTOR, "table tbody tr")) ) print("拿到数据行数:", len(rows)) for row in rows[:3]: print(row.text) finally: driver.quit()

这个版本的优点很明显:只要页面能出现在浏览器里,数据基本就能拿。缺点也同样明显:慢,每次都启动一个浏览器;重,内存占用高。而且selenium-stealth这类库只对特定版本的 Chromedriver 有效,版本一升级可能就失效,需要不断维护。

4.4 两种方案的核心数据对比与决策标准

我用一个协作过的测试项目记录过两种方案的对比数据,场景就是上面这种公开列表页:

对比项普通请求(优化后)Selenium(无头)
首次请求成功率60%-85%95% 以上
单次请求耗时0.2s-0.5s3s-8s
内存占用可忽略300MB+
10 万条数据预计耗时2-4 小时1-3 天
维护成本低高
被 IP 封禁的风险高(请求快,容易触发频率)低(请求慢,更接近真人)

这里的决策标准其实很清晰:

  • 目标是纯 JSON 接口,页面本身没有复杂 JS 校验 → 用普通请求,把 Header、Cookie、频率三件事做好就够。
  • 页面需要 JS 执行后才能加载数据,但数据本身不复杂 → 用 Selenium 或者考虑用 Playwright 的 sync API,性能更好。
  • 数据量大、需要长时间稳定采集 → 优先考虑普通请求加代理池,让 Selenium 只做“攻坚”而不是“常态”。
  • 网站风控极强、普通请求怎么调都过不了 → 再上 Selenium 加 stealth,同时严格限速,一次只跑少量并发。

5. 混合方案与进阶思路:把两者优势结合起来

5.1 先用普通请求探接口,再用 Selenium 兜底

真正成熟的项目很少只依赖一种方案。我自己的习惯是设计一套“降级链路”:先用普通请求去探测目标接口,遇到 403 就暂停;然后用 Selenium 打开同一个页面,观察它的真实网络请求长什么样,拿到正常请求所需的 Cookie、Header、参数格式;再回到普通请求里把这些信息补上。有限,因为 Selenium 主要解决了“请求头最缺什么”的问题,而不是“每次都慢”。

伪代码大概是这个逻辑:

# 第一步:尝试普通请求 resp = requests.get(data_url, headers=HEADERS, cookies=session.cookies) if resp.status_code != 403: return parse_json(resp.json()) # 第二步:403了,降级到 Selenium selenium_result = get_cookie_and_params_by_selenium() # 第三步:用 Selenium 拿到的信息重新走普通请求 session.cookies.update(selenium_result["cookies"]) headers = {**HEADERS, **selenium_result["headers"]} resp = requests.get(data_url, headers=headers, cookies=session.cookies)

这套逻辑的核心价值在于:能用普通请求解决的 90% 场景,就不必为一个页面昂贵地启动浏览器。Selenium 在这个链路里更像是一个“侦察兵”,负责把最难拿的参数和 Cookie 取回来,后续的批量请求交给轻量级方案。

5.2 拿 Selenium 的 Cookie 喂给 requests

这是实际项目里最常用的技巧之一。一些网站需要登录或完整加载页面后,才在 Cookie 里种下有效的会话凭证。与其让 Selenium 一页一页地采集,不如让它干点更值钱的事:登录并拿到有效 Cookie,然后退出,后续所有批量请求都用requests带着这批 Cookie 打接口。

Selenium 获取 Cookie 的写法:

# 登录后或页面加载完成后 cookies = driver.get_cookies() # 转换成 requests 可用的格式 cookie_dict = {c["name"]: c["value"] for c in cookies} # 打印出来保存,或直接更新给 Session print(cookie_dict)

拿到之后直接灌入 requests 的 Session:

session = requests.Session() session.cookies.update(cookie_dict) resp = session.get("https://data.example.com/api/list", headers=HEADERS)

这种“浏览器拿凭证、请求库跑量”的组合,是我目前见过的、在保证速度和稳定性之间权衡得最好的方案。代价是 Cookie 有有效期,通常几十分钟到几天不等,所以需要在程序里设计“Cookie 过期自动续期”的机制——比如请求返回 401 或 403 时,重新拉起 Selenium 刷新 Cookie。

5.3 从被动挨打到主动防御:合规的“反反爬”思路

聊了这么多绕过思路,最后必须聊一个更有价值的问题:如何用“自身行为习惯”降低被 403 的概率。

真正稳定的爬虫,靠的不是某个神奇的 Header 或强大的 stealth 脚本,而是让自己在服务器眼里看起来像个“有耐心的正常用户”。我见过很多项目从开始就注定要失败,因为它们的设计思路就是“别人正常浏览需要 10 分钟才能看完的内容,我要在 10 秒内拿完”,这不叫爬虫,这叫攻击。

合规且可持续的做法是:

  • 频率上限:单个 IP 对同一站点的请求频率控制在 1-5 次/秒以下,甚至更低。
  • 时间窗口:只在目标网站的“低峰期”跑大批量任务,比如凌晨 2-5 点对大多数站来说是低峰期。
  • 随机化:请求间隔加随机抖动,避免固定 3 秒、固定 5 秒这种机器人节奏。
  • 数据量规划:设定每天采集上限,而不是“一次全拉完”。宁可跑三天,也别惹火服务器。
  • 会话保活:一批请求之间定期访问页面首页,让会话看起来持续活跃。

把这几件事做好,你甚至会发现很多“反爬”压根不会对你触发——因为你的流量特征早就融入普通用户了。

6. 常见问题与排查技巧实录

6.1 403 问题速查表

我把这几年遇到过的 403 场景做一个速查表,方便你对照排查:

现象可能原因排查方向
裸请求就 403UA 太明显 / Header 缺失补全 Header,用浏览器 UA
换了 UA 还是 403需要 Cookie / 首次访问校验先访问首页拿 Cookie,再访问接口
爬了一会儿开始 403频率过高触发限流加随机延迟、上代理池,降低 QPS
代理下 403数据中心 IP 被标记换住宅代理或 ISP 代理
首页 200,接口 403接口有独立风控检查 Referer / Origin / 接口 Token
Selenium 打开页面后被 403自动化标记被识别加 stealth、去掉 webdriver 标记
Selenium 间歇性 403浏览器指纹不一致检查时区、语言、分辨率是否匹配
请求验证码页面综合风控等级较高降频、换 IP、暂停几小时再继续

6.2 几个我踩过的坑

第一个坑是无头模式下字体缺失。用--headless=new跑某个页面时,页面渲染的 DOM 结构不完整,导致定位不到元素。排查半天发现是服务器根据浏览器能识别的字体列表做了一次语音检测,无头环境字体库不全,被判定为异常。解决办法是装常用字体或者使用有头模式跑关键页面。

第二个坑是URL 拼接编码。有次反复 403,后来发现是请求 URL 里的一个参数包含特殊字符,requests自动编码后的路径和浏览器不一致,服务器判定 URL 签名不合法。排查了很久才发现是分号变成了%3B的问题。这个需要程序员对接口设计的理解足够细。

第三个坑是Session 过期时间比想象中短。一个网站种下的 Cookie 有效期只有 10 分钟。我配置了代理池但每个请求都换新 IP,服务器认为用户在频繁切换身份,直接触发风控。后来改成“一个 IP 保持一段时间再切”,问题就消失了。

第四个坑是Selenium 版本兼容。Chrome 某天自动升级到 124,而机器上的 Chromedriver 还停留在 120,启动直接抛SessionNotCreatedException。这个问题的解决思路就是引入webdriver-manager,让驱动版本和本地浏览器版本自动对齐。

6.3 经验心得:什么项目真的该用 Selenium

聊到这儿,我直接给出我的个人判断标准。

如果一个项目满足以下任意两条,我就不会死磕普通请求,直接上浏览器方案:

  • 目标页面通过 JS 动态渲染数据,接口调用逻辑极其复杂或根本无法从页面源码直接定位;
  • 网站存在“首次访问必须执行一段 JS 生成动态 Token”的机制;
  • 采集的数据量不大,但对成功率和完整度要求很高;
  • 你每天只需要跑一两次,单次跑完就停。

反过来,如果目标是纯 JSON 接口、数据量大、需要长期跑,我依然建议把大部分精力花在普通请求上——把 Header 抄准、把频率控稳、把代理池用好,这套方案的速度和成本是 Selenium 无法比的。

在我实际的项目里,最常用到的其实不是某个单一技术,而是一套“降级链路”的思维:先用最轻量的方案试,不行再上更重的方案,而每一层方案之间通过 Cookie 和参数的传递互相衔接。这套思路让我的爬虫项目很少因为一个 403 而彻底卡死。

最后再分享一个小技巧:写爬虫的时候,建议给每个请求打上日志,记录时间戳、目标 URL、状态码、用了哪个代理、Cookie 是否过期。没有日志的爬虫就像没有仪表盘的飞机,你根本不知道它在哪一步失联的。等你的 403 问题排查积累多了,你会发现大部分“灵异事件”背后其实都有一个非常具体的原因,只差你多看几行日志。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 5:41:56

东莞外贸网站推广避坑指南:5个对比评测维度防黑挂马

东莞外贸网站推广避坑指南:5个对比评测维度防黑挂马 网站被黑挂马不知道怎么办?这是很多东莞外贸老板深夜最恐慌的时刻。后台突然弹出大量无关广告,浏览器直接提示“不安全”,客户询盘瞬间归零,这时候才想起当初建站时没做好安全防护。别慌,这种情况在东莞外贸圈太常见了,尤其是那些用低价模板站、服务器配置拉胯的…

作者头像 李华
网站建设 2026/9/28 5:41:24

智慧团建网站首页对比评测:3套方案实测避开模板陷阱

智慧团建网站首页对比评测:3套方案实测避开模板陷阱 别再被那些花里胡哨的模板网站骗了,真的不够用。上周帮一家国企工会搞 智慧团建网站首页 改版,甲方拿着某宝几百块的模板图跟我说“就这个感觉”,我直接劝退。模板站最大的坑就是看似全能,实则死板,稍微改个字段就崩,更别提SEO权重积累和后期维护成本。今天…

作者头像 李华
网站建设 2026/9/28 5:41:19

网站首页怎么制作过程完整流程

拒绝模板丑站:从零搭建首页的安全防线与制作全流程 别再被那些千篇一律的模板网站坑了,看着别人做的首页光鲜亮丽,自己拖个模板出来却像上个世纪的产物,这种“模板网站太丑不够用”的焦虑,每一个想自己 从零搭建…

作者头像 李华
网站建设 2026/9/28 5:41:13

婚庆网站建设策划案避坑指南:用免费工具省下3万块

婚庆网站建设策划案避坑指南:用免费工具省下3万块 找建站公司最怕什么?怕报价单上数字好看,落地全是坑,最后花高价买了个半成品。很多做婚庆的老板为了省那点服务器钱,结果网站加载慢、手机端排版乱,客户看一眼就走了,这钱花得冤不冤?别急着签合同,手里有 免费工具…

作者头像 李华
网站建设 2026/9/28 5:41:08

5个实战案例揭秘:网站建设需要多少钱小江网页设计

5个实战案例揭秘:网站建设需要多少钱小江网页设计 上周刚帮一个做机械配件的老板解决大麻烦。他之前找的公司,改个首页Banner图,拖了一周还没动静,急得他直拍桌子。这种“改个需求建站公司拖一周”的情况,在行业里太常见了。很多老板问:网站建设需要多少钱小江网页设计到底靠谱吗?今天不扯虚的,直接拆解5个…

作者头像 李华
网站建设 2026/9/28 5:40:57

Linux文件管理命令实战:从基础操作到安全运维的进阶指南

干了这么多年运维,接手过几十台服务器、给同事收拾过无数次“文件去哪了”的烂摊子之后,我越来越确定一个事儿:文件管理命令这东西,真不是“会用几条就够”的。你光会cd、ls、cp、mv、rm,日常凑合用没问题,…

作者头像 李华