Python爬虫遇到403 Forbidden或IP被封怎么办

许南星
许南星
发布时间:2026-11-03| 5人浏览过
原创

针对Python爬虫运行中遭遇的403 Forbidden与IP封禁问题,提供从故障现象定性排查、请求标头仿真、并发频控调优、高匿代理池轮换到TLS指纹伪装的完整工程化解决方案。

适用环境:Python 3.8+ / requests / httpx / curl_cffi / Playwright 主题定位:HTTP 状态码排查与网络爬虫工程化防护应对
Python 爬虫在运行过程中遭遇 403 Forbidden 或连接拒绝,核心原因通常集中在两个不同层面:一是客户端请求特征未通过标头身份校验(如缺少真实的 User-Agent、防盗链 Referer 或动态 Cookie),二是客户端的抓取频率触犯了服务端的安全阈值,导致 IP 级别限流或拉黑。解决该问题的标准路径不是盲目更换工具,而是按照“定性诊断排查 ➔ 补齐会话标头 ➔ 调节并发频控 ➔ 接入高匿代理池 ➔ 穿透底层指纹与动态校验”的五步梯度逐步施治。
Python爬虫遇到403 Forbidden或IP被封怎么办
图示:Python 爬虫 403 Forbidden 与 IP 拦截排查处置总览

一、先定性:区分 403 Forbidden 与真正的 IP 封禁

在遇到爬虫报错时,许多开发者常将“403 状态码”与“IP 被封”混为一谈。事实上,HTTP 403 Forbidden 仅表示“服务器理解了本次请求,但拒绝执行该请求”。这既可能是接口鉴权拦截,也可能是防盗链机制,并不必然意味着你的 IP 地址已被封锁。如果未经排查就盲目付费采购代理 IP,往往既浪费成本,又无法解决本质问题。

排查定位的核心手段是“对照测试法”。在爬虫报错的第一时间,请保持当前网络环境不变(即使用相同的本地宽带或服务器出口 IP),使用常规桌面浏览器打开无痕模式(隐身窗口)访问相同的目标 URL。

  • 情况 A(浏览器可正常打开,仅 Python 脚本返回 403):这说明你的本地 IP 处于安全放行状态,服务端仅仅是通过分析请求头参数识别出了爬虫身份。此时只需针对请求头、Cookie 或特定会话参数进行补齐伪装即可恢复。
  • 情况 B(浏览器同样返回 403、429、或页面持续转圈直到连接超时):这表明服务端的 WAF(Web Application Firewall)防火墙或网关已对你当前的公网 IP 实施了临时或永久封禁。此时即使修改请求头也无法生效,必须依靠代理 IP 轮换或等待冷却解封。
Python爬虫遇到403 Forbidden或IP被封怎么办
图 1:403 Forbidden 与 IP 封禁排查决策树

二、标头层修复:消除 80% 的初级 403 拦截

当确认本地浏览器可以正常访问,而 Python 脚本被 403 阻断时,问题几乎全部出在 HTTP 请求标头的差异上。官方 Python requests 库在发起请求时,默认附带的标头极度简陋,甚至直接声明 User-Agent: python-requests/2.x.x。对任何部署了基础反爬策略的网站而言,这是最直观的拦截特征。

Python爬虫遇到403 Forbidden或IP被封怎么办
图 2:Python 裸请求与真实浏览器请求头特征对照

1. 规范构造现代浏览器 User-Agent

不要随意手写一个简短过时的 UA 字符串。现代浏览器的 UA 结构具有严格的命名规则与版本依赖关系。推荐使用近期主流 Chrome 版本的稳定标识,并搭配规范的操作系统信息:

Python 请求头规范伪装示例
import requests

headers = {
    "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",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
    "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
    "Accept-Encoding": "gzip, deflate, br",
    "Sec-Ch-Ua": '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"',
    "Sec-Ch-Ua-Mobile": "?0",
    "Sec-Ch-Ua-Platform": '"Windows"',
    "Sec-Fetch-Dest": "document",
    "Sec-Fetch-Mode": "navigate",
    "Sec-Fetch-Site": "none",
    "Sec-Fetch-User": "?1",
    "Upgrade-Insecure-Requests": "1"
}

2. 补齐关键防盗链标头:Referer 与 Origin

许多数据列表页或 API 接口虽然不强制登录,但严格校验请求来源。如果直接在爬虫中请求 /api/data/list,服务端检测到 Referer 为空,就会判定为非站内合法跳转,立刻触发 403。解决办法是:将 Referer 设置为对应的前置详情页或频道导航页 URL。

3. 使用 Session 维持 Cookie 状态流转

很多网站在首页首次加载时,会通过 Set-Cookie 在客户端注入临时会话凭据(如会话 ID 或 CSRF Token)。如果每次都使用单独的 requests.get() 发起无状态调用,就会因为缺少前置 Cookie 而被后续页面阻断。必须使用 requests.Session() 对象维持完整的连接池与 Cookie 容器:

使用 Session 维持上下文状态
session = requests.Session()
session.headers.update(headers)

# 先访问入口页面,自动获取前置 Cookie
welcome_resp = session.get("https://example.com/portal", timeout=10)

# 再带着获取到的 Cookie 访问目标数据接口,补齐 Referer
session.headers["Referer"] = "https://example.com/portal"
data_resp = session.get("https://example.com/api/data/items", timeout=10)

if data_resp.status_code == 200:
    print("标头与会话补齐成功,数据已正常获取")
else:
    print(f"请求失败,状态码: {data_resp.status_code}")

三、频控调优:消除高并发触发的限流与封锁

如果爬虫在最初几十次请求表现完全正常,但几分钟后突然连续报错 429(Too Many Requests)或 403,这属于典型的频控型封禁。服务端的流控组件(如 Nginx limit_req、Redis 滑动窗口计数器)捕捉到了非人力的请求节奏。

高频抓取的误区提醒
固定周期的延时(例如每次循环严格 time.sleep(1))同样具备极强的机械特征。现代防御系统不仅统计单秒请求量,还会通过时序方差(Variance)判断请求是否具有生物学的不确定性。

1. 引入随机时延(Random Jitter)

在每次请求间隔中引入正态分布或区间随机浮动,模拟真实用户的浏览阅读停顿:

随机退避延迟实现
import time
import random

def polite_sleep(base_seconds: float = 1.5, jitter_range: float = 1.0):
    """产生带有随机抖动的暂停时间,避免固定的请求时序特征"""
    actual_delay = base_seconds + random.uniform(0.1, jitter_range)
    time.sleep(actual_delay)

2. 建立带指数退避(Exponential Backoff)的重试机制

当检测到 429、503 或偶发 403 时,切忌立即发起下一次重试,这会导致服务端惩罚计时器不断重置。应当成倍延长等待时间:

指数退避自动重试装饰器/函数
def fetch_with_backoff(session, url, max_retries=4):
    delay = 2.0
    for attempt in range(1, max_retries + 1):
        try:
            resp = session.get(url, timeout=8)
            if resp.status_code == 200:
                return resp
            elif resp.status_code in (403, 429):
                print(f"[警告] 遭遇拦截 (状态码: {resp.status_code}),开始第 {attempt} 次指数退避,等待 {delay:.1f} 秒...")
                time.sleep(delay)
                delay *= 2  # 等待时间翻倍:2s, 4s, 8s, 16s
            else:
                resp.raise_for_status()
        except requests.exceptions.RequestException as err:
            print(f"[异常] 网络传输失败: {err},等待重试...")
            time.sleep(delay)
            delay *= 2
    return None

四、网络层治理:高可用代理 IP 池的构建与自愈

当排查确认目标网站确实对出口 IP 施加了严格封锁时,代理 IP 池是唯一从网络层绕过限制的工程解法。但在选型与接入过程中,有三个关键准则必须明确:

代理类型 HTTP 头 REMOTE_ADDR HTTP_X_FORWARDED_FOR 防封可用性结论
透明代理 (Transparent) 代理服务器 IP 包含真实客户端 IP 完全无效,服务端可直接识破原 IP
普通匿名代理 (Anonymous) 代理服务器 IP 包含代理自身或伪造值 一般,服务端仍能识别出使用了代理
高匿代理 (Elite Proxy) 代理服务器 IP 完全不透传任何代理标识 唯一推荐,服务端视同普通访问者
Python爬虫遇到403 Forbidden或IP被封怎么办
图 3:高可用代理池轮换与故障自愈流转架构

生产级代理轮换与失效剔除示例

以下代码演示了如何在 Python 中将代理池接入 Session,当某节点返回 403 或超时失效时,自动上报剔除并切换下一可用节点:

生产级代理池轮换客户端
import requests
from typing import List, Optional

class ResilientProxyClient:
    def __init__(self, proxy_list: List[str]):
        self.proxy_pool = list(proxy_list)
        self.session = requests.Session()
        self.session.headers.update({
            "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"
        })

    def _get_next_proxy(self) -> Optional[str]:
        return self.proxy_pool[0] if self.proxy_pool else None

    def _mark_proxy_failed(self, failed_proxy: str):
        if failed_proxy in self.proxy_pool:
            self.proxy_pool.remove(failed_proxy)
            print(f"[代理中心] 节点 {failed_proxy} 被拦截或超时,已从活跃池移出,当前剩余: {len(self.proxy_pool)}")

    def execute_request(self, target_url: str, max_attempts: int = 5) -> Optional[requests.Response]:
        for attempt in range(max_attempts):
            current_proxy = self._get_next_proxy()
            if not current_proxy:
                print("[错误] 可用代理池已耗尽,请补充代理源")
                return None

            proxy_dict = {
                "http": current_proxy,
                "https": current_proxy
            }

            try:
                # 严格限制单次代理连接超时为 5 秒,避免卡死
                resp = self.session.get(target_url, proxies=proxy_dict, timeout=5)
                
                # 如果依然被目标站点返回 403,说明该代理已被目标 WAF 拉黑
                if resp.status_code == 403:
                    print(f"[拦截] 代理 {current_proxy} 返回 403,触发轮换")
                    self._mark_proxy_failed(current_proxy)
                    continue
                
                if resp.status_code == 200:
                    # 轮转队列尾部,实现均衡使用
                    self.proxy_pool.append(self.proxy_pool.pop(0))
                    return resp

            except (requests.exceptions.ProxyError, requests.exceptions.Timeout, requests.exceptions.ConnectionError):
                self._mark_proxy_failed(current_proxy)

        return None

# 初始化测试
initial_proxies = [
    "http://user:pass@198.51.100.12:8080",
    "http://user:pass@198.51.100.15:8080",
    "http://user:pass@203.0.113.88:8080"
]
crawler = ResilientProxyClient(initial_proxies)
# result = crawler.execute_request("https://example.com/api/product")

五、高级对抗:解决 TLS/JA3 指纹与动态行为防护

在当今大型企业与主流防护体系(如 Cloudflare、Akamai、Imperva、AWS WAF)下,你可能会发现一个诡异现象:已经完整复制了浏览器的全部 Headers,也使用了干净的住宅代理,但 Python 脚本依然百分之百返回 403。

这是因为现代安全网关的防护层已经深入到了传输层安全协议(TLS)握手过程。Python requests 底层基于 OpenSSL,其在 Client Hello 阶段声明的密码套件(Cipher Suites)、扩展顺序(Extensions)与椭圆曲线(Elliptic Curves)会计算出一个唯一的 JA3 / JA4 指纹。这个指纹与 Google Chrome 或 Apple Safari 存在明显的二进制差异。服务端甚至无需解析 HTTP 报文内容,在 TCP 握手刚结束时便判定为非浏览器环境并直接下发 403。

1. 使用 curl_cffi 模拟真实浏览器 TLS 指纹(首选高效方案)

curl_cffi 是目前解决此类高级 403 阻断的高性能库。它通过底层注入技术完整模拟了 Chrome 等浏览器的 TLS 握手特征与 HTTP/2 帧协商逻辑,运行速度与 requests 无异,开销极低:

curl_cffi 伪装浏览器指纹示例
# 环境准备: pip install curl_cffi
from curl_cffi import requests

# 指定 impersonate="chrome120",底层将完全模拟 Chrome 120 的 JA3/JA4 与 HTTP/2 行为
response = requests.get(
    "https://nowsecure.nl",  # 典型 Cloudflare 防护站点
    impersonate="chrome120",
    timeout=10
)

print(f"响应状态码: {response.status_code}")
if response.status_code == 200:
    print("成功绕过 TLS 指纹校验,获得正常页面内容")

2. 使用 Playwright 自动化处理复杂的 JavaScript 验证码与 5 秒盾

如果目标站点不仅校验 TLS,还会在前端动态执行复杂的 JavaScript 算力挑战(例如 Cloudflare Turnstile 或滑动拼图),纯 HTTP 请求库将无法完成客户端动态计算。此时必须使用现代化无头浏览器方案。

Playwright 结合 stealth 抹除无头痕迹
# 环境准备: pip install playwright && playwright install chromium
import asyncio
from playwright.async_api import async_playwright

async def run_stealth_browser():
    async with async_playwright() as p:
        # 启动真实浏览器内核,关闭自动化扩展特征标识
        browser = await p.chromium.launch(
            headless=True,
            args=[
                "--disable-blink-features=AutomationControlled",
                "--no-sandbox"
            ]
        )
        context = await browser.new_context(
            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",
            viewport={"width": 1920, "height": 1080},
            locale="zh-CN"
        )
        
        # 注入脚本,抹除 navigator.webdriver 属性
        await context.add_init_script("""
            Object.defineProperty(navigator, 'webdriver', {
                get: () => undefined
            });
        """)
        
        page = await context.new_page()
        await page.goto("https://example.com/protected-page", wait_until="networkidle")
        
        content = await page.content()
        print(f"成功获取页面,源码长度: {len(content)}")
        await browser.close()

# asyncio.run(run_stealth_browser())

六、工程总结:四层防御解法矩阵

面对 Python 爬虫的 403 Forbidden 与封禁问题,解决思路应遵从“轻重结合、逐步推进”的工程逻辑。下表总结了四个核心防御层级的对应表现与最优技术选型:

Python爬虫遇到403 Forbidden或IP被封怎么办
图 4:爬虫反拦截四层防御深度与工程解决方案总结矩阵
合规与优雅抓取倡议
网络爬虫应当始终遵守合规与安全边界。在设计爬取架构时,优先检查并尊重目标站点的 robots.txt,将请求速率控制在目标服务器承受范围之内。当遭遇封锁时,优先优化请求效率与数据增量策略,避免对公共网络服务造成拒绝服务式的高负荷冲击。

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。