Python HTTP请求入门:Requests库核心原理与实战指南

发布时间:2026/6/23 15:26:35

Python HTTP请求入门:Requests库核心原理与实战指南 1. 为什么 Requests 是 Python 开发者绕不开的第一道 HTTP 门槛Requests 库不是 Python 标准库但它在绝大多数 Python 项目中出现的频率几乎和print()一样高。我带过几十个从零开始学 Python 的新人无论是想写个自动查课表的小脚本、调用天气 API 做个桌面提醒还是给公司内部系统写个数据同步工具只要涉及“跟另一个网站或服务说句话”95% 的人第一反应都是pip install requests。这不是偶然而是它精准踩中了开发者最真实的痛点——HTTP 协议本身太复杂而业务逻辑又必须简单。HTTP 协议里藏着一堆你不想碰但又绕不开的东西状态码要手动判断、重定向要自己处理、Cookie 要手动存取、HTTPS 证书验证要配置、超时没设好程序就卡死、中文参数要编码、JSON 数据要序列化反序列化……标准库里的urllib确实能干所有事但它的 API 设计就像一本没有目录的古籍——功能全在但你要翻三遍文档才能拼出一个能用的 GET 请求。Requests 把这一切封装成一句requests.get(https://api.example.com/data)返回的就是你想要的数据对象错误是异常成功是结果连 JSON 解析都给你内置好了.json()方法。这不是偷懒是把重复劳动从每个项目里抽出来集中优化一次让所有人受益。它解决的从来不是“能不能发请求”这个技术问题而是“要不要为发个请求写二十行胶水代码”这个工程效率问题。尤其对零基础入门者Requests 是第一个让你感受到“Python 真的能干活”的库——输入几行代码立刻看到网页内容、API 返回的 JSON、甚至下载一张图片。这种即时正向反馈比讲一百遍 TCP 三次握手都管用。我见过太多人卡在urllib.error.HTTPError: HTTP Error 403: Forbidden上一整天只因为没加 User-Agent也见过有人在socket.timeout异常里反复挣扎却不知道 Requests 里一个timeout10就能搞定。Requests 不是魔法它是把 HTTP 的毛刺全部磨平后交到你手里的那把趁手的螺丝刀。2. Requests 的核心设计哲学与底层逻辑拆解2.1 “人类友好”不是口号而是每一行 API 的设计选择Requests 的作者 Kenneth Reitz 在 2011 年发布它时明确提出了一个目标“Make HTTP for Humans”。这句话常被误解为“简化操作”其实更深层的意思是让 API 的行为符合人的直觉而不是协议规范的字面意思。我们来对比两个真实场景场景一GET 请求带参数urllib的写法是from urllib.parse import urlencode from urllib.request import urlopen params {q: python, page: 1} url https://api.example.com/search? urlencode(params) response urlopen(url)这里你得先记住urlencode在哪个模块再拼 URL稍有不慎就漏了?或者参数没编码中文直接变乱码。Requests 的写法是import requests params {q: python, page: 1} response requests.get(https://api.example.com/search, paramsparams)params参数名直白字典结构自然Requests 内部自动拼接、自动编码。这不是省了几行代码而是消除了“URL 构造”这个独立认知环节。场景二POST 提交 JSON 数据urllib需要手动设置 headers、序列化 body、处理字节流import json from urllib.request import Request, urlopen data json.dumps({name: Alice}).encode(utf-8) req Request(https://api.example.com/users, datadata) req.add_header(Content-Type, application/json) response urlopen(req)Requests 只需import requests data {name: Alice} response requests.post(https://api.example.com/users, jsondata)json参数名直接告诉你“这是 JSON 数据”Requests 自动序列化、自动加 header、自动处理编码。你不需要知道application/json是什么 MIME 类型只需要知道“我要发个字典过去”。这种设计背后是 Requests 对 HTTP 协议的“语义分层”它把协议细节如 header 字段、状态码含义、重定向逻辑封装在底层把业务意图“我要查搜索结果”、“我要创建用户”暴露为顶层 API。开发者思考的是“我要做什么”而不是“HTTP 协议要求我怎么做”。2.2 它不是凭空造轮子而是站在 urllib3 和 chardet 的坚实肩膀上Requests 本身不处理网络连接、不解析字符编码、不管理连接池——它是个精巧的“调度中心”。它的核心依赖只有两个urllib3和chardet。urllib3是 Requests 的网络引擎负责真正的 socket 连接、SSL/TLS 握手、HTTP/1.1 协议解析、连接复用keep-alive、重试机制。Requests 把所有网络层面的复杂性都委托给它并通过自己的 API 层进行约束和简化。比如timeout参数Requests 会把它拆成connect_timeout和read_timeout分别传给 urllib3max_redirects参数则控制 urllib3 的重定向次数。chardet是字符编码探测库。当你用response.text获取响应体时Requests 不会盲目用 UTF-8 解码。它先调用chardet.detect()分析响应字节流猜测最可能的编码如gbk,latin-1,utf-8-sig再用该编码解码。这解决了中文网页抓取中最经典的乱码问题——你不用再猜response.content.decode(gbk)还是decode(utf-8).text就是它认为最靠谱的结果。这种“组合优于自研”的架构让 Requests 既轻量源码仅约 3000 行又极其稳定。urllib3经历了十年以上生产环境锤炼处理429 Too Many Requests、502 Bad Gateway、连接中断等边界情况的能力远超手写代码。Requests 只需专注做好“人机接口”把底层的健壮性红利完整传递给使用者。2.3 Session 对象从“一次请求”到“一段会话”的思维跃迁初学者常误以为requests.get()和requests.post()是孤立的函数调用。实际上Requests 的灵魂在于Session对象。它代表的不是一个技术概念而是一种现实世界中的交互模式你登录一个网站后续所有操作都在同一个“会话”中进行服务器靠 Cookie 记住你是谁。import requests # 错误示范每次请求都新建连接不共享 Cookie requests.post(https://api.example.com/login, data{user: a, pwd: b}) requests.get(https://api.example.com/profile) # 401 Unauthorized没带登录态 # 正确示范用 Session 管理状态 session requests.Session() session.post(https://api.example.com/login, data{user: a, pwd: b}) response session.get(https://api.example.com/profile) # 成功Cookie 自动携带Session内部维护了一个CookieJar自动存储服务器 Set-Cookie 头发送的 Cookie并在后续请求中自动附加。它还默认启用连接池复用 TCP 连接大幅提升高频请求的性能。更重要的是它支持预置默认参数session requests.Session() session.headers.update({User-Agent: MyApp/1.0}) # 所有请求自动带 UA session.auth (username, password) # 所有请求自动加 Basic Auth session.timeout 15 # 所有请求默认超时 15 秒这不再是“发请求”而是“开启一段与服务端的对话”。对 API 调用者而言Session是管理认证、限流、调试日志的统一入口。很多企业级 SDK如 GitHub、GitLab 的 Python 客户端底层就是封装了一个 Requests Session再在其上构建领域特定的方法。3. 从安装到实战零基础也能跑通的完整链路3.1 安装与环境确认避开那些“看似正常”的坑Requests 的安装看似简单pip install requests。但实际落地时90% 的新手卡点不在 Requests 本身而在环境配置。我整理了三个最常被忽略的细节确认 pip 指向正确的 Python 版本很多人装了多个 Python系统自带、pyenv、Anacondapip命令可能对应旧版本。执行python -m pip --version输出应为pip x.x.x from /path/to/your/python/lib/site-packages/pip (python x.x)。如果路径指向/usr/bin/python或版本过低21.0说明 pip 没关联到你的目标 Python。此时应使用python -m pip install requests强制用当前python解释器的 pip。PyCharm 用户的专属陷阱PyCharm 创建新项目时默认使用“New environment using Virtualenv”但有时会静默失败导致解释器实际指向系统 Python。检查方法打开File Settings Project Python Interpreter点击右上角号搜索requests如果显示Package not found或安装后仍报ModuleNotFoundError说明解释器路径错了。点击齿轮图标 Add... 选择Existing environment手动指定你python -c import sys; print(sys.executable)输出的路径。国内网络下的镜像加速非代理pip install requests卡住大概率是访问pypi.org慢而非网络问题。解决方案是换国内镜像源这与任何网络代理无关纯属 pip 配置优化# 临时使用清华源推荐最稳 pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple/ # 永久配置Linux/macOS mkdir -p ~/.pip echo [global]\nindex-url https://pypi.tuna.tsinghua.edu.cn/simple/ ~/.pip/pip.conf # 永久配置Windows # 在 %APPDATA%\pip\pip.ini 文件中写入 # [global] # index-url https://pypi.tuna.tsinghua.edu.cn/simple/镜像源只是 pypi.org 的缓存副本所有包完全一致安全可靠且速度提升 5-10 倍。这是国内开发者的标准配置不是“特殊手段”。提示安装完成后务必验证是否真能用。不要只看pip list里有requests要运行 Python 交互式环境import requests print(requests.__version__) # 应输出 2.31.x 或更高 r requests.get(https://httpbin.org/get) print(r.status_code) # 应输出 2003.2 第一个真正有用的脚本获取实时天气并解析 JSON别再写requests.get(https://httpbin.org/get)了。我们直接做一个能解决实际问题的小工具调用免费的 Open-Meteo 天气 API获取你所在城市的当前温度。这个例子覆盖了 Requests 最核心的 5 个能力GET 请求、URL 参数、JSON 解析、错误处理、状态码判断。import requests import json def get_weather(city: str) - dict: 获取指定城市的当前天气使用 Open-Meteo 免费 API :param city: 城市名称如 Beijing :return: 包含温度、天气描述的字典 # Step 1: 构建请求 URL —— 使用 params 参数Requests 自动编码 base_url https://api.open-meteo.com/v1/forecast params { latitude: 39.9042, # 北京纬度 longitude: 116.4074, # 北京经度 current: temperature_2m,weather_code, timezone: Asia/Shanghai } try: # Step 2: 发起请求设置合理超时避免卡死 response requests.get(base_url, paramsparams, timeout10) # Step 3: 检查 HTTP 状态码 —— 4xx/5xx 不等于程序崩溃而是业务异常 if response.status_code ! 200: raise Exception(fAPI 请求失败状态码: {response.status_code}, 响应: {response.text[:100]}) # Step 4: 解析 JSON 响应 —— .json() 方法自动处理编码和解析 data response.json() # Step 5: 提取关键字段添加业务逻辑 current data.get(current, {}) temp current.get(temperature_2m, N/A) weather_code current.get(weather_code, 0) # 将数字天气码转为中文描述简化版 weather_desc { 0: 晴天, 1: 晴间多云, 2: 局部多云, 3: 多云, 45: 雾, 48: 冻雾, 51: 毛毛雨, 53: 小雨, 55: 细雨, 61: 小雨, 63: 中雨, 65: 大雨, 71: 小雪, 73: 中雪, 75: 大雪, 80: 小雨, 81: 中雨, 82: 大雨, 85: 小雪, 86: 大雪, 95: 雷暴, 96: 雷暴伴小雨, 99: 雷暴伴冰雹 }.get(weather_code, 未知天气) return { city: city, temperature: f{temp}°C, weather: weather_desc, timestamp: current.get(time, N/A) } except requests.exceptions.Timeout: raise Exception(请求超时请检查网络或稍后重试) except requests.exceptions.ConnectionError: raise Exception(无法连接到天气服务器请检查网络) except requests.exceptions.JSONDecodeError: raise Exception(API 返回非 JSON 数据可能是服务异常) except Exception as e: raise Exception(f获取天气失败: {str(e)}) # 实际调用 if __name__ __main__: try: result get_weather(Beijing) print(f【{result[city]}】当前天气{result[weather]}温度 {result[temperature]}) print(f数据更新时间{result[timestamp]}) except Exception as e: print(f❌ 错误{e})这段代码的价值远超“能跑通”本身params参数的实践意义它教会你如何把动态查询条件经纬度、需要的字段安全地注入 URL避免字符串拼接的风险。timeout10的必要性没有超时的网络请求是生产环境的定时炸弹。10 秒是经验阈值——多数 API 在 3 秒内响应10 秒足够覆盖网络抖动。状态码检查的业务思维response.status_code ! 200不是“报错”而是“业务流程的一个分支”。404 是城市不存在429 是调用太频繁502 是对方服务挂了——每种状态都需要不同的用户提示。.json()的健壮性它内部已处理了Content-Type检查、BOM 头、编码探测比手动json.loads(response.content.decode(utf-8))稳定十倍。异常分类处理Timeout、ConnectionError、JSONDecodeError是 Requests 定义的特定异常类捕获它们能精准定位问题根源而不是笼统的except Exception。运行它你会第一次看到终端输出真实的天气信息。这种“我写的代码真的在和世界对话”的感觉是驱动你继续深入的最强燃料。3.3 处理高频场景登录、上传、流式下载的实操要点当 Requests 从“玩具”走向“工具”你会遇到三类高频需求模拟登录、上传文件、下载大文件。它们的实现方式直接体现了你对 HTTP 协议和 Requests 机制的理解深度。▶ 模拟登录Session 表单提交的黄金组合以登录一个典型的 Web 表单为例如 Flask 搭建的简易后台import requests # 1. 创建 Session管理整个登录会话 session requests.Session() # 2. 先 GET 登录页获取隐藏字段如 CSRF token login_page session.get(https://example.com/login) # 假设页面中有一个 input typehidden namecsrf_token valueabc123 # 这里用 BeautifulSoup 解析需 pip install beautifulsoup4但 Requests 本身不依赖它 # 实际项目中若 API 文档明确提供 token 获取接口则直接调用 # 3. POST 登录表单数据 login_data { username: admin, password: 123456, # csrf_token: abc123 # 若需 token此处填入 } response session.post(https://example.com/login, datalogin_data) # 4. 检查登录是否成功看重定向或响应内容 if response.status_code 200 and 欢迎回来 in response.text: print(✅ 登录成功) # 后续所有请求都用这个 session自动携带 Cookie profile session.get(https://example.com/profile) print(profile.json()) else: print(❌ 登录失败状态码:, response.status_code)关键点解析Session是必须的否则登录态无法延续。data参数用于表单提交application/x-www-form-urlencodedjson用于 API 接口application/json二者不能混用。真实网站常有 CSRF、验证码等防护此时 Requests 需配合BeautifulSoup或lxml解析 HTML或调用专门的验证码识别服务。Requests 本身只负责“发请求”业务逻辑由你组装。▶ 上传文件files参数的正确用法上传文件不是把路径字符串塞进去而是构造一个符合multipart/form-data编码规则的请求体。Requests 的files参数帮你全自动完成import requests # 方式1上传本地文件最常用 with open(report.pdf, rb) as f: files {file: (report.pdf, f, application/pdf)} # 参数说明(文件名, 文件对象, MIME类型) response requests.post( https://api.example.com/upload, filesfiles, # 若需额外字段用 data 参数 data{description: 月度报告} ) # 方式2上传内存中的字节流如 PIL 生成的图片 from io import BytesIO from PIL import Image img Image.new(RGB, (100, 100), colorred) img_bytes BytesIO() img.save(img_bytes, formatPNG) img_bytes.seek(0) # 重置指针到开头 files {image: (logo.png, img_bytes, image/png)} response requests.post(https://api.example.com/upload, filesfiles)避坑指南files的值必须是元组格式为(filename, file_object, content_type)。content_type可省略Requests 会根据文件扩展名猜测但显式指定更可靠。文件对象必须是二进制模式打开rb否则上传会损坏。files和data可同时存在Requests 会自动合并为 multipart 请求体。▶ 流式下载大文件避免内存爆炸用requests.get(url).content下载 1GB 文件你的 Python 进程会瞬间吃光几 GB 内存。正确做法是流式stream下载边下边存import requests def download_file(url: str, filename: str): 流式下载大文件 try: # streamTrue 关键告诉 Requests 不要一次性读取全部响应体 with requests.get(url, streamTrue, timeout30) as response: response.raise_for_status() # 自动检查 4xx/5xx # 获取文件总大小若服务器返回 Content-Length total_size int(response.headers.get(content-length, 0)) # 以二进制模式写入文件 with open(filename, wb) as f: # 每次读取 8192 字节8KB可调大提升速度 for chunk in response.iter_content(chunk_size8192): if chunk: # 过滤掉 keep-alive 的空块 f.write(chunk) print(f✅ 下载完成{filename} ({total_size/1024/1024:.1f} MB)) except requests.exceptions.RequestException as e: print(f❌ 下载失败{e}) # 使用示例 download_file(https://example.com/large-dataset.zip, dataset.zip)原理与优势streamTrue让 Requests 返回一个未读取的响应对象response.iter_content()按需拉取数据块。内存占用恒定约 8KB与文件大小无关。response.raise_for_status()在流式模式下依然有效能捕获初始的 HTTP 错误。chunk_size是性能调优点太小如 1024增加 I/O 次数太大如 1MB可能阻塞太久。8KB 是通用平衡值。4. 直面真实世界的错误429、502、连接中断的排查与应对Requests 再强大也无法消除网络世界的不确定性。exceeded retry limit, last status: 429 too many requests、unexpected status 502 bad gateway、Connection aborted这些错误不是代码 bug而是分布式系统协作的常态。学会与它们共处是进阶的关键。4.1 429 Too Many Requests不是限制而是服务端的善意提醒429状态码意味着你触发了服务端的速率限制Rate Limiting。它不是拒绝服务而是说“请慢一点我需要喘口气”。常见于免费 API如 GitHub、OpenWeatherMap或爬虫场景。错误现场还原# 错误写法无节制循环请求 for i in range(100): r requests.get(fhttps://api.example.com/data/{i}) time.sleep(0.1) # 以为 0.1 秒就够了 # 结果第 20 次左右开始返回 429根本原因分析服务端限制通常是“每分钟 60 次”而非“每秒 10 次”。0.1 秒间隔1 分钟就是 600 次远超限额。time.sleep(0.1)无法保证精确实际间隔可能更短。Requests 默认不重试 429它被视为客户端错误所以你收到 429 后若不处理后续请求全失败。专业级解决方案阅读 API 文档明确限额规则查找X-RateLimit-Limit、X-RateLimit-Remaining、X-RateLimit-Reset这些响应头。例如response requests.get(https://api.github.com/rate_limit) print(response.headers[X-RateLimit-Limit]) # 总限额 print(response.headers[X-RateLimit-Remaining]) # 剩余次数 print(response.headers[X-RateLimit-Reset]) # 重置时间戳Unix 时间实现智能等待Exponential Backoff不是简单sleep(1)而是按失败次数指数增长等待时间import time import random def robust_get(url: str, max_retries3): for attempt in range(max_retries 1): try: response requests.get(url, timeout10) if response.status_code 429: # 从响应头获取重试时间或计算指数退避 retry_after int(response.headers.get(Retry-After, 1)) * (2 ** attempt) jitter random.uniform(0, 0.1) # 加入随机抖动避免集体重试 sleep_time retry_after jitter print(f⚠️ 429等待 {sleep_time:.2f} 秒后重试...) time.sleep(sleep_time) continue response.raise_for_status() return response except requests.exceptions.RequestException as e: if attempt max_retries: raise e time.sleep(1) # 其他错误也等 1 秒再试 return None使用 requests.adapters.HTTPAdapter 配置重试策略推荐这是 Requests 官方支持的重试机制可针对特定状态码定制from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry # 创建一个重试策略对 429 状态码最多重试 3 次每次间隔 1, 2, 4 秒 retry_strategy Retry( total3, status_forcelist[429], # 明确指定哪些状态码触发重试 backoff_factor1, # 退避因子1-1,2,4 秒 allowed_methods[HEAD, GET, OPTIONS, POST] # 允许重试的 HTTP 方法 ) adapter HTTPAdapter(max_retriesretry_strategy) session requests.Session() session.mount(http://, adapter) session.mount(https://, adapter) # 现在 session.get() 会自动处理 429 response session.get(https://api.example.com/data)注意重试不是万能的。对POST请求重试可能导致重复提交如下单此时必须用幂等设计如带唯一 ID 的请求。4.2 502 Bad Gateway当你的请求撞上了别人的故障502 Bad Gateway意味着你成功连接到了网关如 Nginx但网关无法从上游服务如 Python Flask 应用获得有效响应。它 100% 是服务端问题你的代码通常无需修改但需要优雅降级。典型场景与排查步骤场景如何确认应对措施上游服务崩溃curl -v https://upstream-service返回空响应或 connection refused在代码中捕获502返回友好的“服务暂时不可用”提示记录日志通知运维网关超时Nginx 日志中出现upstream timed out联系服务方调大proxy_read_timeout或优化你的请求如减少数据量DNS 解析失败nslookup upstream-service失败检查 DNS 配置或在代码中加入 DNS 缓存如dnspython代码层面的防御性编程def safe_api_call(url: str): try: response requests.get(url, timeout15) # 延长超时给网关更多时间 if response.status_code 502: # 记录详细日志包含时间、URL、headers logger.warning(f502 Bad Gateway for {url}. Headers: {response.headers}) # 返回降级数据或抛出自定义异常 raise ServiceUnavailableError(上游服务暂不可用请稍后再试) response.raise_for_status() return response.json() except requests.exceptions.Timeout: logger.error(fRequest timeout for {url}) raise TimeoutError(请求超时请检查网络或稍后重试) except requests.exceptions.ConnectionError: logger.error(fConnection failed for {url}) raise ConnectionError(无法连接到服务器请检查网络) except requests.exceptions.HTTPError as e: # 其他 HTTP 错误统一处理 logger.error(fHTTP error {e.response.status_code} for {url}) raise e4.3 连接中断与 SSL 错误本地环境的隐形杀手Connection aborted、SSLError: certificate verify failed这类错误99% 出现在本地开发环境尤其是 Windows 或老旧系统上。根因与修复SSL 证书验证失败系统 CA 证书库过期或 Python 使用了内置的旧证书。修复升级certifi包Requests 依赖它提供证书pip install --upgrade certifi # 或指定最新证书路径 import requests requests.utils.DEFAULT_CA_BUNDLE_PATH /path/to/certifi/cacert.pemConnection aborted / Connection reset防火墙、杀毒软件拦截了 Python 进程的网络连接或代理设置污染了环境变量。排查# 检查是否有代理环境变量它们会干扰 Requests echo $HTTP_PROXY $HTTPS_PROXY $http_proxy $https_proxy # 若有临时取消 unset HTTP_PROXY HTTPS_PROXY http_proxy https_proxy # 再运行 Python 脚本Docker 环境下的get https://registry-1.docker.io/v2/: net/http这是 Docker daemon 的网络问题与 Requests 无关。解决方案是配置 Docker 的 registry mirror国内镜像源或检查宿主机网络。5. Requests 的能力边界与替代方案什么时候该放手Requests 是伟大的但它不是银弹。当你的需求超出其设计范畴时强行使用只会让代码越来越脆弱。以下是三个明确的“该换工具”的信号5.1 你需要处理 WebSocket 实时通信Requests 基于 HTTP/1.1而 WebSocket 是独立的 TCP 协议。requests.get(ws://...)会直接报错。此时必须用专用库websocket-client轻量级适合简单收发消息。from websocket import create_connection ws create_connection(wss://echo.websocket.org) ws.send(Hello, World) result ws.recv() ws.close()websockets异步现代 Python 项目的首选支持 asyncio。import asyncio import websockets async def hello(): async with websockets.connect(wss://echo.websocket.org) as websocket: await websocket.send(Hello world!) response await websocket.recv() print(response) asyncio.run(hello())判断标准如果你的需求是“服务器主动推送消息给我”如聊天室、股票行情而不是“我问它答”那就不是 HTTP 请求Requests 无能为力。5.2 你需要高并发、低延迟的海量请求Requests 是同步阻塞的。一个requests.get()执行时整个线程就卡住了。100 个请求串行执行耗时是单个请求的 100 倍。这时你需要aiohttp基于 asyncio 的异步 HTTP 客户端性能提升 10-100 倍。import asyncio import aiohttp async def fetch(session, url): async with session.get(url) as response: return await response.text() async def main(): urls [https://httpbin.org/delay/1] * 100 async with aiohttp.ClientSession() as session: tasks [fetch(session, url) for url in urls] results await asyncio.gather(*tasks) print(len(results)) asyncio.run(main())httpxRequests 的精神继承者同时支持同步和异步API 高度兼容。import httpx # 同步用法和 Requests 几乎一样 response httpx.get(https://httpbin.org/get) # 异步用法 import asyncio async def async_example(): async with httpx.AsyncClient() as client: response await client.get(https://httpbin.org/get)判断标准如果你的脚本需要在 1 秒内发出 100 请求如监控、压测、爬虫同步 Requests 是瓶颈必须转向异步。5.3 你需要模拟真实浏览器行为JS 渲染、指纹、反爬Requests 只是一个 HTTP 客户端它不会执行 JavaScript不会渲染 HTML无法处理document.cookie动态生成、Canvas 指纹、WebGL 指纹等前端反爬手段。当你遇到页面内容是 JS 动态加载的Network 面板里 XHR/Fetch 请求返回空 HTML登录需要滑动验证码、点选验证码请求头中有 Sec-Ch-Ua

相关新闻