【BUG已解决】Python Requests 爬虫返回 521 状态码的解决方案

📅 2026/7/3 11:33:28
【BUG已解决】Python Requests 爬虫返回 521 状态码的解决方案
【BUG已解决】Python Requests 爬虫返回 521 状态码的解决方案前言本文主要介绍了 Python 使用 requests 库进行网络爬虫时遇到 HTTP 521 状态码的完整排查过程和解决方案希望能对使用 Python 进行数据采集开发的同学们有所帮助。521 状态码不是标准 HTTP 状态码而是 CDN 厂商尤其是 Cloudflare自定义的错误码代表源站不可用本文将深入分析其成因并提供多种应对策略。1. 问题描述1.1 完整报错信息import requests response requests.get(https://example.com/api/data) print(response.status_code) # 521 print(response.text)输出521 html headtitle521: Web server is down/title/head body h1Error 521/h1 pWeb server is down/p pRay ID: 8a3f2b1c9d8e7f6a • 2026-07-02 10:00:00 UTC/p /body /html或在爬虫脚本中触发异常处理Traceback (most recent call last): File spider.py, line 20, in module data response.json() requests.exceptions.JSONDecodeError: Expecting value: line 1 column 1 (char 0) # 因为返回的是 521 错误页面的 HTML不是预期的 JSON1.2 具体现象用 requests 请求某个网站返回状态码是 521 而不是预期的 200用浏览器直接访问该网站是正常的能看到内容同样的爬虫代码某些时间段能正常运行某些时间段全部返回 521换了不同的 IP如用代理后问题消失或者依然存在请求头User-Agent看起来正常但仍然被拒绝1.3 521 状态码的特殊性522/521/520 等 5xx 状态码属于Cloudflare 自定义扩展状态码不是 IANA 官方注册的标准状态码状态码Cloudflare 含义520源站返回了未知错误521源站服务器已关闭Web server is down522连接超时523源站不可达524源站响应超时2. 原因分析2.1 CDN 架构下的请求流程爬虫(requests) ↓ 请求 Cloudflare CDN 边缘节点全球分布式代理 ↓ 转发请求给真实的源站服务器 源站服务器 ← 如果这里出问题就返回521 ↓ 源站正常时返回真实内容 Cloudflare CDN ↓ 转发响应 爬虫(requests) 收到响应521 意味着Cloudflare 边缘节点自己是正常的但它转发请求给源站服务器时源站没有响应或拒绝了连接。2.2 五大触发原因#原因说明占比1Cloudflare 反爬虫策略触发识别到你的请求是爬虫故意伪造521拒绝40%2源站服务器真的宕机/维护源站临时不可用20%3缺少必要的请求头User-Agent/Referer/Cookie 缺失被拦截20%4请求频率过高触发限流短时间内请求次数过多15%5IP 被列入黑名单之前的爬虫行为导致IP被封5%2.3 判断是反爬虫还是真实源站故障import requests import time url https://example.com/api/data # 用不同的请求头测试 headers_browser { 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/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9, } response1 requests.get(url, headersheaders_browser) print(f带浏览器头请求: {response1.status_code}) response2 requests.get(url) # 不带任何自定义头 print(f不带头请求: {response2.status_code}) # 如果带浏览器头能成功说明是被识别为爬虫拦截不是源站真的宕机3. 解决方案3.1 方案一伪造完整的浏览器请求头最常用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/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Accept-Encoding: gzip, deflate, br, Connection: keep-alive, Referer: https://example.com/, Sec-Fetch-Dest: document, Sec-Fetch-Mode: navigate, Sec-Fetch-Site: same-origin, Upgrade-Insecure-Requests: 1, } response requests.get(https://example.com/api/data, headersheaders) print(response.status_code)3.2 方案二使用 Session 保持会话与 Cookieimport requests session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, }) # 先访问首页获取 Cookie session.get(https://example.com/) # 再访问目标接口会自动带上之前获取的 Cookie response session.get(https://example.com/api/data) print(response.status_code)3.3 方案三控制请求频率添加随机延迟import requests import time import random urls [https://example.com/api/data?page str(i) for i in range(1, 20)] for url in urls: response requests.get(url, headersheaders) print(f{url}: {response.status_code}) # 随机延迟 1-3 秒模拟人类浏览行为 time.sleep(random.uniform(1, 3))3.4 方案四使用代理IP池轮换import requests proxies_pool [ {http: http://proxy1:port, https: http://proxy1:port}, {http: http://proxy2:port, https: http://proxy2:port}, # 更多代理... ] import random for url in urls: proxy random.choice(proxies_pool) try: response requests.get(url, headersheaders, proxiesproxy, timeout10) print(f{url}: {response.status_code}) except requests.exceptions.RequestException as e: print(f代理请求失败: {e})3.5 方案五使用 cloudscraper 库绕过 Cloudflare 防护cloudscraper是专门为绕过 Cloudflare 反爬虫机制设计的库pip install cloudscraperimport cloudscraper scraper cloudscraper.create_scraper() response scraper.get(https://example.com/api/data) print(response.status_code) print(response.text)3.6 方案六使用 Selenium/Playwright 模拟真实浏览器如果 521 是因为 Cloudflare 的 JavaScript 挑战验证需要执行JS才能通过纯 requests 无法处理需要用真实浏览器from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch(headlessTrue) page browser.new_page() page.goto(https://example.com/api/data) content page.content() print(content) browser.close()3.7 方案七重试机制 异常处理健壮性提升import requests import time from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry def create_session_with_retry(): session requests.Session() retry_strategy Retry( total3, # 最多重试3次 backoff_factor2, # 每次重试间隔递增2s, 4s, 8s status_forcelist[521, 522, 523, 524, 429, 500, 502, 503], # 这些状态码触发重试 ) adapter HTTPAdapter(max_retriesretry_strategy) session.mount(https://, adapter) session.mount(http://, adapter) return session session create_session_with_retry() response session.get(https://example.com/api/data, headersheaders) print(response.status_code)3.8 方案八检测是否是源站真实故障排除法import requests def check_site_status(url): try: response requests.get(url, timeout10) if response.status_code 521: # 尝试用不同方式访问判断是反爬还是真故障 time.sleep(5) retry_response requests.get(url, headersheaders, timeout10) if retry_response.status_code 200: print(判定为反爬虫拦截添加头后恢复正常) else: print(可能是源站真实故障建议稍后重试) return response.status_code except requests.exceptions.RequestException as e: print(f请求异常: {e}) return None check_site_status(https://example.com/api/data)4. 完整的健壮爬虫模板import requests import time import random from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry class RobustSpider: def __init__(self): 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/120.0.0.0 Safari/537.36, Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9, }) retry_strategy Retry(total3, backoff_factor2, status_forcelist[521, 522, 429, 500, 502, 503]) self.session.mount(https://, HTTPAdapter(max_retriesretry_strategy)) def get(self, url, **kwargs): time.sleep(random.uniform(0.5, 2)) # 随机延迟 return self.session.get(url, timeout15, **kwargs) spider RobustSpider() response spider.get(https://example.com/api/data) print(response.status_code)5. 总结处理 521 状态码的排查优先级先确认是否是反爬虫拦截→ 加浏览器头测试添加完整请求头→ User-Agent Referer Accept系列控制请求频率→ 添加随机延迟使用 Session 维持会话→ 处理需要Cookie的场景重度反爬网站→ cloudscraper 或 Playwright加入重试机制→ 提升脚本健壮性应对临时性5216. 常见问题 FAQ6.1 521 与 403 Forbidden 的区别状态码触发层含义403源站服务器服务器明确拒绝了你的请求权限问题521CDN边缘节点CDN认为源站不可用可能是伪装拒绝如果同时遇到两种状态码交替出现说明目标网站有多层防护机制需要综合运用本文提到的多种方案。6.2 如何判断目标网站使用的是 Cloudflareimport requests response requests.head(https://example.com) print(response.headers.get(Server)) # 如果输出 cloudflare说明确实是Cloudflare CDN print(response.headers.get(CF-RAY)) # 存在这个头也说明走了Cloudflare6.3 使用 requests-html 自动处理JS渲染pip install requests-htmlfrom requests_html import HTMLSession session HTMLSession() r session.get(https://example.com/api/data) r.html.render() # 执行JS渲染 print(r.html.html)注意requests-html 底层依赖 pyppeteer在部分环境下配置较复杂如果遇到问题优先考虑方案六Playwright。6.4 批量爬取时如何优雅地处理部分URL的521import requests import time failed_urls [] def crawl_with_fallback(urls): results {} for url in urls: try: response requests.get(url, headersheaders, timeout10) if response.status_code 521: failed_urls.append(url) continue results[url] response.text except requests.exceptions.RequestException: failed_urls.append(url) return results # 第一轮爬取 results crawl_with_fallback(url_list) # 对失败的URL等待一段时间后重试 if failed_urls: print(f有 {len(failed_urls)} 个URL失败等待60秒后重试) time.sleep(60) retry_results crawl_with_fallback(failed_urls) results.update(retry_results)6.5 长期反爬对抗的架构建议对于需要长期、大规模采集的场景单纯依赖请求头伪造往往不够稳定建议考虑代理IP池服务如青果、快代理等商业代理分布式爬虫架构多节点分摊请求降低单IP频率官方API优先很多网站提供官方开放API优先使用比爬虫更稳定合规需要提醒的是爬虫开发请遵守目标网站的 robots.txt 规则和相关法律法规不要进行高频率、大规模的恶意爬取。合理控制请求频率不仅是技术上的最佳实践也是对目标网站服务器资源的基本尊重。