Python requests.get详解:data参数与headers配置实战指南 刚开始接触Python网络请求的人基本都会遇到同一个困惑明明接口文档写得清清楚楚自己的requests.get却怎么也对不上。要么是GET参数没传对要么是headers少了关键字段还有更隐蔽的——data到底该怎么放。最近帮一个小伙伴排查接口联调问题前前后后折腾了快一个下午最后发现就是对requests.get里data和headers的理解有偏差。这篇文章就把这两个参数从头到尾捋一遍适合刚学requests的新手也适合写过一段时间但没深究细节的老朋友看完可以直接对照自己的代码去改。先说结论requests.get支持data参数但HTTP语义上GET请求的body是个微妙话题。headers则是每个请求几乎都少不了的标配只是很多人不知道应该带哪些字段、什么时候带。下面逐步拆开讲。1. 先搞清楚GET请求里的data到底是什么角色1.1 三个容易混淆的参数params、data、jsonrequests.get最常用的三个传参方式是params、data、json很多人以为它们可以互换实际完全不同。参数数据最终位置编码方式典型场景paramsURL查询字符串自动urlencode后拼接到?后面GET普通查询参数data请求体bodydict时按表单编码str时原样发送POST表单提交或特殊GET bodyjson请求体body自动序列化为JSON字符串接口要求application/json时用最直白的话理解params是挂在网址问号后面的参数肉眼可以直接在地址栏看到data和json是放在请求包里“肚子”里的数据地址栏看不到。requests.get的完整签名是requests.get(url, paramsNone, **kwargs)这里的**kwargs会透传给底层请求方法所以data、headers、json、timeout、verify这些都可以直接写在get里。很多人一直误以为get只能传params其实requests库早就把口子留开了。数据序列化这一点很关键。传dict给data时requests会按照application/x-www-form-urlencoded格式编码最终body长这样keywordpythonpage1size20传str给data时requests原样发送不帮你做任何处理。所以如果要发送JSON字符串你可以用json.dumps()手动序列化也可以直接使用json参数它会自动完成序列化并设置对应的Content-Type。1.2 到底什么时候需要给GET加data回到标题中的“get请求添加data”现实中你确实会遇到需要这么干的时候。最常见的是某些接口天生就设计成“GET JSON body”。尤其是一些搜索类、查询类接口查询条件复杂如果全部拼到URL里一串几十个参数既不美观又容易触发URL长度限制。于是服务端干脆把查询条件放进body里请求方法仍然用GET保持只读语义。这种情况下requests.get就必须带上data或json去请求。第二种场景是接口文档明确要求把某些字段放在body中。常见于一些内部系统或旧系统遗留下来的约定文档这么写你就得顺着来。但这里必须泼一盆冷水虽然HTTP协议没有禁止GET携带body很多网络中间件代理、网关、CDN对GET body并不友好。我自己实测过某些代理服务器会把GET请求的body直接丢弃服务端最终拿到的就是一个没有任何body的GET请求。这是这种做法的最大隐患不是代码层面能解决的需要先确认服务器和链路设备支不支持。另外GET本身的语义是幂等查询设计良好的接口不会因为GET请求body的存在而产生数据变更。如果你请求的接口服务端会写入数据哪怕请求方法写的GET也要提高警惕这往往是接口设计有问题的信号。2. headers的实用价值与推荐配置2.1 常见headers字段逐一说清楚headers是HTTP请求的“身份证通行证”服务端靠它判断你是谁、你想要什么、你能不能进来。日常开发中涉及到的字段主要就下面这些我按使用频率排个序字段作用典型值User-Agent标识客户端类型和版本很多服务端会拒绝无UA或异常UA的请求Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36Accept告诉服务端你愿意接收什么响应格式application/jsonAccept-Language声明语言偏好多语言站点会据此返回不同内容zh-CN,zh;q0.9,en;q0.8Content-Type声明请求体的媒体类型有body时必填application/json; charsetutf-8Authorization携带认证凭证常见Bearer Token或Basic AuthBearer eyJhbGciOi...Referer请求来源页面部分服务端会校验https://example.com/Cookie会话凭证登录态通常靠它维持sessionidabc123X-Requested-With标识是否为异步请求有些框架的CSRF校验会检查XMLHttpRequestUser-Agent绝对是我踩坑最多的地方。很多接口服务端对库默认的UA比如python-requests/2.31.0直接拒绝访问返回403或者一片空白。换上浏览器UA之后同一套参数立刻通了。所以如果你发现请求被403拦掉第一反应应该是检查UA有没有被识别为非常规客户端。Authorization要注意格式。Bearer Token是Bearer 空格token的写法Basic Auth是Basic base64(user:pass)。中间的空格和大小写经常被人忽略而服务端解析时往往严格区分少一个空格都可能认证失败。2.2 实战中怎么配置headers最稳最稳的做法不是自己手写一份headers而是先抓包看真实浏览器发出的请求长什么样。用浏览器开发者工具打开Network面板任意访问一个目标站点点开一个请求把Request Headers里关键字段拷贝出来作为自己代码里的初始配置。桌面端Chrome的UA模板可以直接用Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36移动端写爬虫或适配移动版接口时常用Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1headers里有两个细节值得单独提醒。第一header的value尽量不要放中文或其他非ASCII字符。HTTP头部本设计为ASCII环境虽然规范允许扩展但很多服务端解析非ASCII头时会直接崩掉或乱码。如果你确实要在header里传会话信息、自定义标识先用URL编码或Base64处理再塞进去。第二headers字段名大小写不敏感user-agent和User-Agent对服务端效果一样但代码里建议用标准驼峰写法一是可读性好二是避免某些严格框架的比对逻辑出幺蛾子。还有一个反直觉的点requests对headers的处理并不是简单的“后来的覆盖前面的”。如果你用Session维护了一套默认headers请求级传入的headers是按字段名做合并的不是整体替换。也就是说会话级已有的Accept不会被请求级的headers清掉除非请求级也显式传入同名字段。这个机制用好了很省事用不好就是隐患容易以为“传了headers就会覆盖”结果服务端看到的是新旧字段混搭的请求头。3. 实操requests.get中data和headers的正确写法3.1 基础示例params与headers配合这是最常规的requests.get写法查询参数走URL认证和客户端标识走headers。我用一个虚构的天气查询接口当示例import requests url https://api.example-weather.com/v1/weather 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: application/json, Authorization: Bearer DEMO_TOKEN_HERE, } params { city: beijing, unit: metric, } resp requests.get(url, paramsparams, headersheaders, timeout10) print(HTTP状态码:, resp.status_code) print(最终请求URL:, resp.url) print(响应内容类型:, resp.headers.get(Content-Type)) print(JSON数据:, resp.json())注意resp.url这个字段它可以用来确认params是否真的拼接到了URL上。如果最终URL里没有你预期的参数说明params写法有问题或者有个别参数被requests过滤掉了。params中的键值会自动做URL编码你不需要手动处理空格、中文这些字符。一个容易被忽略的细节如果params里有某个键的值为Nonerequests不会把它拼到URL里。你可以利用这个特性做条件筛选而不是在外部先拼好一串带空值的查询串。3.2 进阶示例GET请求带databody当接口明确要求GET请求在body中传JSON数据时前半部分代码会变成下面这样import json import requests url https://api.example-search.com/v1/search headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Accept: application/json, Content-Type: application/json; charsetutf-8, } data { keyword: python requests, filters: { category: documentation, lang: zh }, page: 1, size: 20, } resp requests.get(url, headersheaders, datajson.dumps(data), timeout10)这里我用了json.dumps(data)手动序列化因为data参数传dict时requests会按表单格式编码而服务端按JSON解析时肯定失败。这是一个非常隐蔽的坑明明请求发出去了服务端也收到了但解析body时就报错。问题出在编码格式和Content-Type不匹配上。更省事的写法是直接用json参数resp requests.get(url, headersheaders, jsondata, timeout10)json参数会替你完成两件事自动json.dumps()序列化并自动设置Content-Type: application/json。不过如果你在headers里已经手动指定了Content-Typerequests不会用json参数覆盖已有的值。所以想自己完全控制Content-Type就用第一种写法想省心用第二种。3.3 响应处理与状态判断请求发出去了响应怎么处理也有门道。最稳的判断流程是这样try: resp requests.get(url, headersheaders, paramsparams, timeout10) resp.raise_for_status() except requests.exceptions.HTTPError as e: print(HTTP错误:, e) print(响应内容:, resp.text) else: try: result resp.json() print(result) except requests.exceptions.JSONDecodeError: print(响应不是JSON原文如下) print(resp.text)raise_for_status()会在状态码为4xx或5xx时主动抛出异常相当于把错误判断提前了。很多初学者不写这行拿到响应后直接resp.json()服务端返回一个404页面时就会出现JSON解析异常报错信息完全误导你让你以为是数据格式问题而不是状态码问题。中文乱码的问题也经常出在这一步。requests会根据响应头里的charset推断编码但如果服务端没有明确声明字符集requests默认按ISO-8859-1处理中文全部变成乱码。遇到这种情况先手动指定编码再重新解析文本resp.encoding utf-8 print(resp.text)如果要更精确可以先用resp.content拿到原始bytes再根据实际情况解码raw_bytes resp.content text raw_bytes.decode(utf-8, errorsreplace)3.4 Session复用headers的高效做法同一个应用里多次请求同一个域名时反复构造headers既浪费又啰嗦。requests.Session就是为解决这个问题存在的。import requests with requests.Session() as session: session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Accept: application/json, }) # 会话级别已带headers直接传业务参数 r1 session.get(https://api.example.com/v1/user, params{id: 1}, timeout10) # 需要覆盖某个header时请求级单独传 r2 session.get( https://api.example.com/v1/orders, params{status: pending}, headers{Accept: application/xml}, timeout10, )Session还有一个隐藏优势底层TCP连接会被复用。并发请求量大的情况下用Session比每次新建裸连接快很多效率提升非常明显。实际操作中我会把headers初始化放在Session创建之后立即执行所有请求统一走这个Session代码清爽且性能更好。需要注意的一点是Session的Cookie也是自动维持的。如果你用Session先登录再请求业务接口登录态会自动保留不用手动把Cookie每次塞进headers省掉很多麻烦。4. 常见问题与排查技巧实录4.1 服务端收不到data症状代码里写了data参数抓包却发现请求没有body或者服务端反馈“body为空”。可能的原因分几种。第一有些网络链路设备比如代理、网关出于规范兼容性考虑会主动丢弃GET请求的body。这个属于环境问题代码无法解决只能改造请求方式或确认链路配置。第二requests在处理某些重定向时会把body丢掉尤其遇到301/302跳转默认行为是不保留原body。第三参数名传错把data写到了params里数据自然跑到URL上。排查时最直接的一招不真正发请求先把Request对象准备好打印看它最终长什么样。import requests req requests.Request(GET, url, headersheaders, datajson.dumps(data)) prepared req.prepare() print(最终URL:, prepared.url) print(请求头:, prepared.headers) print(请求体:, prepared.body)prepare()之后PreparedRequest已经把headers、body、url都组装好了。如果这个阶段body就是空的说明data参数根本没传进Request里如果body有内容但线上服务端仍收不到基本可以怀疑是链路设备或重定向导致。4.2 中文乱码与编码问题响应乱码的根源几乎都在编码推断上。我之前排查过一起问题服务端返回的JSON头部明明写着charsetgbk但实际内容却是UTF-8编码的requests按照gbk去解码中文全部变成“锟斤拷”。这个情况下唯一正确的做法是拿到原始字节后手动按正确的编码解。请求方向的乱码一般出现在URL或body中。params里的中文requests会自动做URL编码比如“北京”会变成一堆百分号开头的字符串这没问题。body里的中文如果走json.dumps默认情况下会被转成\uXXXX形式这是合法的JSON服务端用JSON解析器能正确还原。如果真要看懂内容用json.dumps(data, ensure_asciiFalse)可以让中文保持原样但发送给服务端时两者等价。4.3 超时、SSL证书、重定向问题这三个问题排在requests日常报错的前三名基本每个写接口的人都会遇到。超时建议区分连接超时和读取超时用元组传值requests.get(url, timeout(3.05, 5), headersheaders)3.05秒是建立连接的最长等待时间5秒是拿到响应的最长等待时间。只写一个数字意味着两个都用它。不设timeout的请求在网络异常时可能一直挂在那里既不报错也不返回排查起来极其痛苦。SSL证书错误常见于连公司内网服务、自己搭的测试环境或者代理抓包时。临时环境可以关闭验证import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) resp requests.get(url, headersheaders, verifyFalse, timeout10)但生产环境强烈不建议这么干。正确做法是拿到服务端的CA证书文件用verify/path/to/ca.pem指定证书路径。重定向方面默认情况下requests会自动跟随跳转最终拿到的响应是跳转后的结果。如果想知道是否发生过跳转看resp.history列表里面是历史响应对象。有些场景需要自己控制跳转比如要捕获302后的Location再做二次处理这时用allow_redirectsFalse然后手动判断resp requests.get(url, headersheaders, allow_redirectsFalse, timeout10) if resp.status_code in (301, 302): next_url resp.headers.get(Location) # 自行决定是否继续请求4.4 排查思路速查表把平时最容易遇到的几个问题整理成表方便遇到问题直接对号入座症状可能原因排查方向响应401/403headers缺认证信息或UA被识别为非浏览器检查Authorization格式、UA是否完整响应400/422body格式与Content-Type不匹配确认是JSON还是表单用json参数还是data参数响应为空GET body被网关/代理丢弃改用params传参或换POST中文乱码编码推断错误resp.encoding utf-8或手动解码bytes请求卡死没设置timeout使用timeout(3, 5)SSL证书报错证书链不受信任开发环境verifyFalse生产指定CA证书ISE或异常行为请求头字段重复或格式错误prepare后打印headers逐字段检查最后再分享一个我自己的习惯动作接口联调前先把prepared打印出来看一遍。这个小步骤能省下后面大量抓包的功夫因为请求实际长什么样在发出前就完全暴露了。requests.get的data和headers这两个参数说复杂也就是上面这些内容但真正用好的关键在于你对每个参数在HTTP层的真实作用有准确判断。选对工具写对格式踩坑自然就少了。