1. 问题现象与背景解析
最近在调试一个Python爬虫项目时,遇到了一个典型的JSON解析错误。当向某电商平台API发送POST请求获取分页数据时,服务器返回了以下报错信息:
JSON parse error: Unrecognized token 'pageNo': was expecting发送请求,有汉字时解析错误这个错误表面看起来是JSON解析问题,但实际涉及字符编码、请求头设置、参数格式化等多个技术环节。作为经常处理API对接的开发者,这类问题其实有很典型的解决模式。下面我就结合具体案例,拆解这个问题的完整排查思路和解决方案。
2. 错误原因深度分析
2.1 原始请求代码示例
先看触发错误的原始代码片段:
import requests url = "https://api.example.com/products" params = { "pageNo": 1, "pageSize": 20, "keyword": "手机" } response = requests.post(url, data=params) print(response.json())2.2 关键错误点诊断
- Content-Type缺失:未显式设置请求头,默认使用
application/x-www-form-urlencoded - 参数编码问题:中文字符
"手机"未做URL编码处理 - 数据格式错配:服务器期望接收JSON格式请求体,但实际发送的是表单数据
重要提示:现代API开发中,90%的JSON解析错误其实都是由于请求头或数据格式不匹配导致的,而非真正的JSON语法错误。
3. 完整解决方案
3.1 标准修复方案
以下是经过验证的可靠实现方式:
import requests import json url = "https://api.example.com/products" headers = { "Content-Type": "application/json; charset=UTF-8" } params = { "pageNo": 1, "pageSize": 20, "keyword": "手机" } response = requests.post( url, data=json.dumps(params, ensure_ascii=False), headers=headers ) print(response.json())3.2 关键改进点说明
- 显式设置Content-Type:明确告知服务器发送的是JSON格式数据
- 使用json.dumps转换:将字典转为标准JSON字符串
- ensure_ascii=False:保留中文字符原样输出,避免自动转义
- 统一字符编码:指定UTF-8编码避免乱码
4. 高级调试技巧
4.1 请求日志捕获
在复杂场景下,建议使用以下方式捕获完整请求详情:
import logging # 启用requests库的调试日志 logging.basicConfig(level=logging.DEBUG) requests_log = logging.getLogger("requests.packages.urllib3") requests_log.setLevel(logging.DEBUG) requests_log.propagate = True4.2 使用curl命令验证
有时用命令行工具验证能快速定位问题:
curl -X POST \ -H "Content-Type: application/json" \ -d '{"pageNo":1,"pageSize":20,"keyword":"手机"}' \ https://api.example.com/products5. 常见问题排查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| JSON parse error | 1. 未设置Content-Type 2. 数据未转为JSON字符串 | 1. 添加正确请求头 2. 使用json.dumps() |
| 中文乱码 | 字符编码不一致 | 1. 确保UTF-8编码 2. 设置ensure_ascii=False |
| 参数缺失 | 数据格式错误 | 检查使用data还是json参数 |
| 400 Bad Request | 请求体格式错误 | 用工具捕获原始请求对比 |
6. 最佳实践建议
- 始终显式设置Content-Type:即使是简单请求也建议完整配置headers
- 使用requests的json参数:对于简单场景可以直接用
requests.post(url, json=params) - 统一编码规范:项目内所有API调用保持一致的编码处理方式
- 添加异常处理:对响应状态码和JSON解析增加try-catch块
我在实际项目中总结出一个通用封装方法:
def safe_api_call(url, method="POST", params=None): try: resp = requests.request( method, url, json=params, headers={"Content-Type": "application/json"}, timeout=10 ) resp.raise_for_status() return resp.json() except requests.exceptions.RequestException as e: print(f"API请求失败: {str(e)}") return None这个方案已经在我们团队的生产环境中稳定运行超过2年,处理了各种复杂的API对接场景。记住,好的请求处理应该像隐形的基础设施一样可靠,让业务代码不需要关心网络通信的细节。