Python Requests库GET与POST请求实战:从基础到工程化异常处理

📅 2026/8/15 12:30:58
Python Requests库GET与POST请求实战:从基础到工程化异常处理
1. 项目概述从“能跑”到“跑得稳”的网络请求实战在Python的世界里requests库几乎是每个开发者与外部世界对话的“标准普通话”。无论是抓取一张网页、调用一个API接口还是提交一份表单数据GET和POST请求都是最基础、最核心的操作。网上随手一搜你就能找到无数个“三行代码发送请求”的示例。但真正在项目中你会发现事情远没那么简单为什么我的请求突然超时了返回的乱码怎么处理这个422错误到底是什么意思服务器返回的JSON结构千变万化如何优雅又稳健地提取我需要的数据这篇文章不会止步于给你一个“能跑起来”的代码片段。我将结合十多年爬虫和API对接的实战经验带你深入requests库的GET和POST请求不仅告诉你“怎么做”更重点剖析“为什么这么做”以及“如何应对各种意外”。我们会从最基础的请求构建开始一路深入到超时重试、异常处理、响应解析等工程化细节并提供一个可直接复用的、健壮的返回值获取模板。无论你是刚入门的新手还是遇到过“玄学”网络问题的开发者相信都能在这里找到答案。2. 核心工具解析为什么是Requests在深入代码之前我们有必要先理解手中的工具。Python内置的urllib模块也能完成网络请求但它的API设计相对底层和繁琐。requests库的出现以其“人类友好”的哲学迅速成为事实上的标准。2.1 Requests库的核心优势它的优势不仅仅在于语法简洁。首先它自动处理了连接池和持久化连接HTTP Keep-Alive这意味着在多次请求同一主机时无需重复建立昂贵的TCP连接显著提升了性能。其次它提供了高度可读的响应对象将状态码、响应头、响应体、编码等信息封装成清晰的属性访问起来非常直观。最重要的是它拥有极其完善的异常处理体系如ConnectionError,Timeout,HTTPError等让我们能精准地捕获和处理各种网络和服务器问题而不是面对一个笼统的崩溃。2.2 安装与基础准备安装requests非常简单使用pip即可pip install requests在开始编写任何请求代码之前一个好的习惯是进行基础的导入和会话管理。虽然你可以直接使用requests.get()这样的顶级函数但对于需要保持Cookie、配置统一代理或头信息的复杂场景使用Session对象是更专业的选择。import requests import json import time from typing import Optional, Any, Dict # 创建一个会话对象用于保持跨请求的某些参数如cookies, headers session requests.Session() # 设置一个通用的用户代理模拟浏览器访问这是最基本的反反爬策略之一 session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 })注意直接使用requests.get()时每次请求都是独立的。而Session对象会在内部维护一个连接池并自动处理Cookies在需要连续与同一个网站交互时如登录后访问多个页面效率和便利性都更高。3. GET请求深度解析不仅仅是获取数据GET请求通常用于从服务器获取检索数据参数会以查询字符串Query String的形式附加在URL之后。这是最常用的请求方法但其中细节不少。3.1 基础GET请求与参数传递一个最简单的GET请求如下response requests.get(https://httpbin.org/get) print(response.status_code) # 打印状态码如200 print(response.text) # 打印响应文本内容但在实际应用中我们几乎总是需要传递参数。requests提供了非常方便的params参数来自动完成URL编码和拼接。# 定义查询参数 params { key1: value1, key2: [value2_a, value2_b], # 可以传递列表生成 key2value2_akey2value2_b key3: 特殊 字符 # requests会自动进行URL编码 } response requests.get(https://httpbin.org/get, paramsparams) print(response.url) # 查看最终请求的URL参数已被正确编码和添加3.2 处理响应文本、JSON与编码获取响应后如何解析内容是关键。response.text返回的是解码后的字符串。requests会尝试根据HTTP头部的Content-Type来推断编码如果推断错误常见于中文网站就会产生乱码。此时我们可以手动指定编码。response requests.get(https://some-chinese-site.com) # 如果出现乱码尝试手动设置编码常见的有 gbk, gb2312, utf-8 response.encoding gbk content response.text对于现代API返回的通常是JSON格式。直接使用response.json()方法可以将其解析为Python字典或列表。但这里有一个巨大的坑如果响应内容不是合法的JSON或者为空.json()方法会直接抛出json.decoder.JSONDecodeError异常导致程序崩溃。实操心得永远不要直接调用response.json()而不做异常处理。一个健壮的做法是def safe_json_parse(response): try: return response.json() except json.decoder.JSONDecodeError: print(fJSON解析失败。响应状态码{response.status_code} 响应文本前100字符{response.text[:100]}) return None # 或者返回一个空字典 {} 根据你的业务逻辑决定 data safe_json_parse(response) if data: # 安全地处理数据 pass3.3 高级特性超时、重试与请求头定制网络请求充满不确定性超时是最常见的问题之一。不设置超时你的程序可能会永远挂起。# 为请求设置超时连接超时和读取超时 try: # timeout参数可以是一个浮点数总超时或一个(连接超时 读取超时)的元组 response requests.get(https://httpbin.org/delay/5, timeout(3.05, 10)) # 连接3.05秒读取10秒 except requests.exceptions.Timeout: print(请求超时) # 这里可以加入重试逻辑对于重要的请求实现重试机制是提高鲁棒性的关键。我们可以结合time模块和循环或者使用更强大的第三方库如tenacity。def robust_get(url, paramsNone, max_retries3): for i in range(max_retries): try: resp session.get(url, paramsparams, timeout5) resp.raise_for_status() # 如果状态码不是200-399抛出HTTPError异常 return resp except (requests.exceptions.Timeout, requests.exceptions.ConnectionError) as e: print(f第{i1}次请求失败原因{e}) if i max_retries - 1: wait_time 2 ** i # 指数退避策略 print(f等待{wait_time}秒后重试...) time.sleep(wait_time) else: print(重试次数用尽请求失败。) raise # 重新抛出异常 except requests.exceptions.HTTPError as e: # 对于HTTP错误如404 500通常重试无意义直接抛出 print(fHTTP错误{e}) raise # 使用示例 response robust_get(https://api.example.com/data, params{page: 1})此外定制请求头Headers是应对反爬机制和满足API要求的必备技能。除了User-Agent常见的还有Accept告知服务器客户端期望的数据类型、Authorization承载Token、Content-Type在POST中指定发送数据的类型等。4. POST请求实战数据提交的艺术POST请求通常用于向服务器提交创建/更新数据其数据载荷Payload放在请求体中而非URL中。根据提交数据格式的不同主要有以下几种方式。4.1 提交表单数据application/x-www-form-urlencoded这是网页表单默认的提交格式数据格式与GET的查询字符串类似但放在请求体内。form_data { username: test_user, password: secret_pass # 注意实际中密码绝不应该明文传输 } response requests.post(https://httpbin.org/post, dataform_data) # requests会自动将字典转换为这种格式并设置Content-Type为 application/x-www-form-urlencoded4.2 提交JSON数据application/json这是现代RESTful API最常用的交互格式。json_payload { title: My Post, body: This is the content., userId: 1 } # 关键使用 json 参数requests会自动序列化字典为JSON字符串并设置正确的Content-Type response requests.post(https://jsonplaceholder.typicode.com/posts, jsonjson_payload) print(response.status_code) print(response.json()) # 查看服务器返回的创建结果一个常见误区有些人会手动将字典json.dumps()成字符串然后用data参数传递同时手动设置headers{Content-Type: application/json}。这样做虽然可以但使用json参数更简洁、更不易出错。4.3 提交文件multipart/form-data用于上传文件比如图片、文档等。file_path /path/to/your/image.jpg with open(file_path, rb) as f: # 必须以二进制模式打开 files {file: (file_path, f, image/jpeg)} # 元组格式(文件名 文件对象 MIME类型) # 也可以简写为 {file: f} requests会推断文件名和类型 response requests.post(https://httpbin.org/post, filesfiles)4.4 处理复杂的响应与状态码POST请求后服务器可能返回各种状态码。201 Created表示资源创建成功通常会在响应体的Location头或返回的JSON中给出新资源的URL。400 Bad Request表示客户端请求有误如参数缺失、格式错误。422 Unprocessable Entity你在热词中看到的是HTTP扩展状态码常用于表示请求格式正确但语义错误如验证失败。处理这些响应时除了检查status_code更要学会解析响应体中的错误信息。response requests.post(https://api.example.com/items, jsonpayload) if response.status_code 201: new_item response.json() print(f创建成功ID为{new_item[id]}) elif response.status_code 400: error_detail response.json() print(f请求错误{error_detail.get(message, Unknown error)}) # 可能包含更详细的字段错误信息 if errors in error_detail: for field, msg in error_detail[errors].items(): print(f - {field}: {msg}) elif response.status_code 422: # 处理验证错误 pass else: response.raise_for_status() # 对于其他非成功状态码直接抛出异常5. 返回值获取与处理的工程化方案获取返回值不仅仅是response.text那么简单。一个健壮的返回值处理模块需要考虑解析、验证、转换和异常处理的全流程。5.1 构建一个通用的响应处理器我们可以设计一个函数它封装了发送请求、处理异常、解析响应、验证数据的全部逻辑。def make_request(method, url, sessionNone, **kwargs): 通用的请求发送与响应处理函数。 参数: method: 请求方法GET 或 POST url: 请求URL session: 可选的requests.Session对象用于保持会话 **kwargs: 传递给requests.request的其他参数如params, data, json, headers, timeout 返回: 一个字典包含: - success: bool, 请求是否成功网络层面和业务层面 - status_code: int, HTTP状态码 - data: Any, 解析后的数据如JSON对象失败时为None - error: str, 错误信息成功时为None - response: requests.Response对象原始响应供高级调试使用 req_session session or requests.Session() result { success: False, status_code: None, data: None, error: None, response: None } try: resp req_session.request(method.upper(), url, **kwargs) result[status_code] resp.status_code result[response] resp # 首先检查HTTP状态码是否表示成功 resp.raise_for_status() # 尝试解析响应体 content_type resp.headers.get(Content-Type, ).lower() if application/json in content_type: result[data] resp.json() elif text/ in content_type: # 处理文本响应注意编码 resp.encoding resp.apparent_encoding # 使用requests推断的编码 result[data] resp.text else: # 二进制或其他类型返回content result[data] resp.content result[success] True except requests.exceptions.Timeout as e: result[error] f请求超时: {e} except requests.exceptions.ConnectionError as e: result[error] f网络连接错误: {e} except requests.exceptions.HTTPError as e: # HTTP错误4xx, 5xx result[error] fHTTP错误 ({resp.status_code}): {e} # 尝试从错误响应中提取更多信息 try: error_body resp.json() result[error] f | 服务器返回: {error_body} except: result[error] f | 响应文本: {resp.text[:200]} except json.decoder.JSONDecodeError as e: result[error] f响应JSON解析失败: {e}。原始文本: {resp.text[:200]} except Exception as e: result[error] f未知错误: {type(e).__name__}: {e} return result # 使用示例 # GET请求 get_result make_request(GET, https://api.github.com/users/octocat, timeout5) if get_result[success]: user_data get_result[data] print(f用户: {user_data.get(login)}) else: print(f请求失败: {get_result[error]}) # POST请求 post_result make_request(POST, https://httpbin.org/post, json{test: data})这个处理器提供了清晰的成功/失败状态分离集中了错误处理逻辑并保留了原始响应对象供深度调试。5.2 数据提取与验证拿到解析后的数据通常是字典或列表后直接通过键名访问如data[key]是危险的因为键可能不存在。推荐使用.get()方法并提供默认值。# 不安全的访问 # user_name response_data[user][name] # 如果‘user’或‘name’缺失会抛出KeyError # 安全的访问 user_data response_data.get(user, {}) # 如果‘user’不存在返回空字典 user_name user_data.get(name, Unknown) # 如果‘name’不存在返回‘Unknown’对于复杂的API响应可以使用更强大的数据验证库如pydantic它能在解析数据的同时进行类型验证和转换非常适合生产环境。from pydantic import BaseModel, HttpUrl from typing import List, Optional class GitHubUser(BaseModel): login: str id: int avatar_url: Optional[HttpUrl] None public_repos: int 0 # 假设api_response是requests返回并解析好的字典 try: validated_user GitHubUser(**api_response) print(f验证通过的用户: {validated_user.login}, ID: {validated_user.id}) except Exception as e: print(f数据验证失败: {e})6. 常见问题排查与实战避坑指南在实际开发中你会遇到各种各样的问题。下面是一些高频问题的排查思路和解决方案。6.1 连接超时与读取超时现象requests.exceptions.ConnectTimeout或requests.exceptions.ReadTimeout。排查检查网络ping或curl一下目标地址看是否可达。检查代理如果你在代理环境下确保requests正确配置了代理proxies参数。调整超时时间适当增加timeout值特别是对于慢速API或大文件下载。服务器问题可能是目标服务器负载过高或防火墙限制。可以尝试换个时间或联系服务方。6.2 SSL证书验证错误现象requests.exceptions.SSLError。解决临时方案不推荐用于生产在请求中添加verifyFalse参数。警告这会禁用SSL验证存在中间人攻击风险仅用于测试内部或可信环境。response requests.get(https://example.com, verifyFalse)正确方案如果是因为自签名证书可以将服务器的证书文件.crt或.pem路径传给verify参数。response requests.get(https://internal-server.com, verify/path/to/server.crt)6.3 响应内容乱码现象response.text显示为乱码。解决检查response.encoding属性看requests推断的编码是什么。查看响应头Content-Type中的charset信息如Content-Type: text/html; charsetgb2312。手动设置正确的编码response.encoding gbk。如果以上都不行可以使用response.content获取原始字节然后用chardet库检测编码。import chardet raw_data response.content detected_encoding chardet.detect(raw_data)[encoding] text raw_data.decode(detected_encoding, errorsignore)6.4 遭遇反爬机制如触发风控现象返回403 Forbidden、429 Too Many Requests或返回一个包含“拒绝访问”、“安全风控”等字样的页面正如你在热词中看到的。应对策略降低请求频率在请求间加入随机延时time.sleep(random.uniform(1, 3))。完善请求头模拟真实浏览器设置合理的User-Agent、Accept、Accept-Language、Referer等。使用会话使用requests.Session()保持Cookies模拟登录状态。处理动态内容对于JavaScript渲染的页面requests无法获取动态加载的内容需要考虑使用Selenium或Playwright等浏览器自动化工具。尊重robots.txt检查目标网站的robots.txt文件遵守其爬取规则。考虑使用代理IP池对于大规模爬取轮换IP地址是必要手段。6.5 处理重定向现象请求的URL被自动跳转到另一个URL。控制requests默认会跟随重定向最多30次。你可以通过allow_redirectsFalse参数禁用或通过response.history查看重定向历史链。response requests.get(http://github.com, allow_redirectsTrue) # 默认 print(f最终URL: {response.url}) print(f重定向历史: {[r.status_code for r in response.history]})网络请求是连接程序的桥梁其稳定性直接关系到整个应用的健壮性。从简单的数据获取到复杂的API交互理解并妥善处理每一个环节的细节是开发者从不成熟走向专业的关键一步。希望这篇结合了大量实战经验的指南能成为你工具箱里一件趁手的兵器。