Python爬虫JSON解析错误排查与解决方案
1. 问题现象与背景分析最近在调试一个Python爬虫项目时遇到了一个典型的JSON解析错误。当向某电商平台API发送POST请求时服务器返回了JSON parse error: Unrecognized token pageNo的错误信息。更奇怪的是当请求参数中包含中文字符时错误会变成解析错误。这种情况在使用Python requests库进行接口调试时并不少见特别是处理包含中文参数和分页参数的场景。这个问题看似简单但实际上涉及HTTP请求、字符编码、JSON序列化等多个技术环节的协同工作。作为一名长期与各种API打交道的开发者我发现这类问题往往源于以下几个常见原因请求头(Headers)中Content-Type设置不当JSON数据格式不符合规范中文字符编码处理不当参数序列化方式错误服务端与客户端对JSON标准的理解差异2. 问题根因深度解析2.1 JSON解析错误的本质当看到Unrecognized token pageNo这样的错误时首先需要理解JSON解析器的工作机制。JSON解析器在读取数据时会按照严格的语法规则进行词法分析。当遇到不符合JSON规范的字符时就会抛出这类无法识别的标记错误。在我们的案例中错误指向了pageNo这个字段名。这表明解析器在期望看到JSON规范中的特定符号如冒号、引号、花括号等时却遇到了看似随意的字符串。这种情况通常意味着发送的数据根本不是JSON格式JSON字符串格式不正确如缺少引号编码问题导致特殊字符被破坏2.2 中文字符带来的额外问题当中文字符出现在请求中时问题变得更加复杂。常见的编码问题包括双重编码中文字符被多次URL编码编码不一致请求头声明的编码与实际编码不符字节顺序标记(BOM)某些编辑器添加的BOM头干扰解析转义字符处理不当如将已转义的字符再次转义3. 完整解决方案与实操步骤3.1 正确的POST请求实现方式以下是经过实战验证的Python requests库POST请求标准写法import requests import json url https://api.example.com/products headers { Content-Type: application/json; charsetutf-8, User-Agent: Mozilla/5.0 } data { pageNo: 1, pageSize: 20, keyword: 手机 } try: response requests.post( url, datajson.dumps(data, ensure_asciiFalse).encode(utf-8), headersheaders ) response.raise_for_status() result response.json() print(result) except requests.exceptions.RequestException as e: print(f请求失败: {e}) except json.JSONDecodeError as e: print(fJSON解析失败: {e})关键点说明明确设置Content-Type为application/json并指定utf-8编码使用json.dumps进行序列化ensure_asciiFalse保留中文显式编码为utf-8字节流完整的错误处理机制3.2 参数序列化的常见陷阱很多开发者会犯以下错误# 错误示例1直接传递字典 requests.post(url, jsondata) # 有时能工作但不稳定 # 错误示例2手动拼接JSON字符串 requests.post(url, datastr(data)) # 完全错误的做法 # 错误示例3使用默认ascii编码 requests.post(url, datajson.dumps(data)) # 中文会被转义这些做法的问题在于直接传递字典依赖requests内部实现行为不一致手动拼接无法保证JSON格式正确性默认ascii编码会破坏中文字符3.3 高级场景处理对于更复杂的API请求可能需要处理嵌套JSONdata { pagination: { pageNo: 1, pageSize: 20 }, filter: { category: 电子产品, priceRange: [1000, 5000] } }添加认证信息headers { Content-Type: application/json, Authorization: Bearer your_token_here }处理二进制数据files { image: (photo.jpg, open(photo.jpg, rb), image/jpeg), data: (data.json, json.dumps(data), application/json) } response requests.post(url, filesfiles)4. 调试技巧与问题排查4.1 请求日志记录在开发过程中记录完整的请求和响应信息至关重要import logging import http.client # 启用requests的调试日志 http.client.HTTPConnection.debuglevel 1 logging.basicConfig() logging.getLogger().setLevel(logging.DEBUG) requests_log logging.getLogger(requests.packages.urllib3) requests_log.setLevel(logging.DEBUG) requests_log.propagate True4.2 使用中间工具验证在确定是客户端还是服务端问题前可以使用以下工具验证Postman手动构造请求测试APIcurl命令curl -X POST \ -H Content-Type: application/json \ -d {pageNo:1,keyword:测试} \ https://api.example.com/endpoint在线JSON验证器验证生成的JSON是否合法4.3 常见错误对照表错误现象可能原因解决方案Unrecognized token xxx数据不是合法JSON格式检查是否使用json.dumps()JSON parse error编码问题或格式错误设置ensure_asciiFalse和utf-8编码400 Bad Request请求头或参数不正确检查Content-Type和请求体格式中文变乱码编码不一致统一使用utf-8编码响应不是JSON服务端错误检查响应头Content-Type5. 性能优化与最佳实践5.1 连接池配置高频调用API时合理配置连接池可以显著提升性能session requests.Session() adapter requests.adapters.HTTPAdapter( pool_connections10, pool_maxsize50, max_retries3 ) session.mount(http://, adapter) session.mount(https://, adapter)5.2 超时与重试机制生产环境必须设置合理的超时和重试策略from urllib3.util.retry import Retry from requests.adapters import HTTPAdapter retry_strategy Retry( total3, backoff_factor1, status_forcelist[500, 502, 503, 504] ) adapter HTTPAdapter(max_retriesretry_strategy) session requests.Session() session.mount(https://, adapter) try: response session.post( url, jsondata, timeout(3.05, 27) # 连接超时和读取超时 ) except requests.exceptions.Timeout: print(请求超时)5.3 异步请求处理对于高并发场景可以考虑使用aiohttp实现异步请求import aiohttp import asyncio async def fetch(session, url, data): async with session.post(url, jsondata) as response: return await response.json() async def main(): async with aiohttp.ClientSession() as session: tasks [ fetch(session, url, data) for data in batch_data ] results await asyncio.gather(*tasks) print(results) asyncio.run(main())6. 安全注意事项6.1 敏感信息处理永远不要在代码中硬编码敏感信息# 错误做法 headers { Authorization: Bearer hardcoded_token } # 正确做法 import os from dotenv import load_dotenv load_dotenv() headers { Authorization: fBearer {os.getenv(API_TOKEN)} }6.2 输入验证与清理处理用户提供的参数时必须进行验证def validate_input(data): if not isinstance(data.get(pageNo), int) or data[pageNo] 1: raise ValueError(pageNo必须是正整数) if len(data.get(keyword, )) 100: raise ValueError(关键词过长) # 更多验证规则... try: validate_input(request_data) except ValueError as e: print(f参数错误: {e})6.3 HTTPS与证书验证生产环境必须启用HTTPS和证书验证# 启用证书验证默认 requests.get(https://api.example.com) # 仅在测试环境禁用验证不推荐 requests.get(https://api.example.com, verifyFalse)在实际项目中遇到JSON解析问题时我的经验是先确保请求构造正确再检查网络传输过程最后验证服务端实现。90%的问题都出在客户端请求构造阶段特别是编码和格式问题。