Python 接口请求技能文档
1. 功能说明
技能用途
本技能用于通过 Python 的 requests 库发送 HTTP POST 请求,获取二手房房源数据接口的数据,并重点解决接口返回的中文乱码问题。
适用场景
-
需要从 RESTful API 接口获取结构化数据
-
接口返回 JSON 格式数据但存在中文乱码问题
-
需要批量获取和处理房产类数据
-
适用于数据分析、爬虫、数据同步等场景
依赖环境
-
Python 3.6+
-
requests 库 (
pip install requests)
2. Python 实现代码
import requests
import json
def get_second_hand_house_data(page=1, limit=10, city_code="431300"):
"""
获取二手房房源列表数据
Args:
page (int): 页码,默认第1页
limit (int): 每页数据条数,默认10条
city_code (str): 城市编码,默认娄底市 431300
Returns:
dict: 解析后的JSON数据,包含房源列表信息
"""
url = "https://zfapi.jiafudc.com/api/SecondHandSellHouse/GetSecondHandSellHouseList"
# 请求头设置
headers = {
'User-Agent': 'Apifox/1.0.0 (https://apifox.com)',
'Accept': '*/*',
'Host': 'zfapi.jiafudc.com',
'Connection': 'keep-alive',
'Content-Type': 'application/x-www-form-urlencoded'
}
# 请求参数
payload = {
'page': page,
'limit': limit,
'cityCode': city_code,
'sortField': 'm_ALLFloor',
'unknownUserId': '20fcfd01-3b88-bcaa-305b-a590780d30da'
}
try:
# 发送POST请求
response = requests.post(url, headers=headers, data=payload, timeout=10)
# ==================== 中文乱码核心处理 ====================
# 方案1:手动指定编码(推荐,最可靠)
response.encoding = 'utf-8'
# 方案2:自动检测编码(备选,适用于不确定编码的情况)
# response.encoding = response.apparent_encoding
# 方案3:手动解码(终极方案,处理特殊编码)
# content = response.content.decode('utf-8', errors='ignore')
# =========================================================
# 检查请求是否成功
response.raise_for_status()
# 解析JSON数据
result = response.json()
print(f"请求成功,获取到 {len(result.get('data', {}).get('list', []))} 条数据")
return result
except requests.exceptions.RequestException as e:
print(f"请求失败: {str(e)}")
return None
except json.JSONDecodeError as e:
print(f"JSON解析失败: {str(e)}")
print(f"响应内容预览: {response.text[:200]}")
return None
# 示例调用
if __name__ == "__main__":
# 获取第1页,每页5条数据
data = get_second_hand_house_data(page=1, limit=5)
if data:
# 打印返回结果的结构
print("\n=== 返回数据结构 ===")
print(f"状态码: {data.get('code')}")
print(f"消息: {data.get('msg')}")
# 打印房源数据
house_list = data.get('data', {}).get('list', [])
print(f"\n=== 房源列表示例(前2条)===")
for i, house in enumerate(house_list[:2], 1):
print(f"\n--- 房源 {i} ---")
print(f"标题: {house.get('m_Title', 'N/A')}")
print(f"价格: {house.get('m_Price', 'N/A')} 万元")
print(f"面积: {house.get('m_Square', 'N/A')} ㎡")
print(f"户型: {house.get('m_Room', 'N/A')}室{house.get('m_Hall', 'N/A')}厅")
print(f"楼层: {house.get('m_Floor', 'N/A')}/{house.get('m_ALLFloor', 'N/A')}层")
print(f"小区: {house.get('m_Address', 'N/A')}")
3. 中文乱码处理的详细说明和解决方案
3.1 为什么会出现中文乱码?
中文乱码的根本原因是编码和解码使用的字符集不一致。在 HTTP 请求场景中,具体表现为:
|乱码原因|说明|
|---|---|
|编码不匹配|服务器实际使用 UTF-8 编码返回数据,但 requests 库误认为是 ISO-8859-1(Latin-1)编码|
|响应头缺失|服务器返回的 Content\-Type 响应头中没有指定 charset 信息|
|自动检测失败|requests 库的编码自动检测算法(chardet)判断错误|
|特殊编码|部分国内网站使用 GBK/GB2312/GB18030 编码而非 UTF-8|
|BOM 头问题|UTF-8 BOM 头导致解析异常|
典型乱码表现:
-
䏿–‡这样的乱码字符(UTF-8 被当作 Latin-1 解析) -
??????问号(编码不支持中文字符) -
锟斤拷经典乱码(GBK 和 UTF-8 转换错误)
3.2 多种解决方案对比
方案 1:手动指定编码(推荐,最可靠)
response.encoding = 'utf-8'
-
优点:性能最好,直接指定正确编码,无需检测
-
适用场景:明确知道接口使用 UTF-8 编码(绝大多数现代接口)
-
成功率:95% 以上的中文乱码可以通过此方法解决
方案 2:使用 apparent_encoding 自动检测
response.encoding = response.apparent_encoding
-
优点:自动检测,无需人工判断
-
原理:使用 chardet 库分析响应内容的字节特征来判断编码
-
适用场景:不确定接口编码时的兜底方案
-
注意:会增加少量性能开销,大响应体时明显
方案 3:手动解码字节内容(终极方案)
# 方式A:直接解码
content = response.content.decode('utf-8', errors='ignore')
# 方式B:尝试多种编码
encodings = ['utf-8', 'gbk', 'gb2312', 'gb18030']
for enc in encodings:
try:
content = response.content.decode(enc)
break
except UnicodeDecodeError:
continue
-
优点:最灵活,可以处理各种极端情况
-
适用场景:前两种方案都失败时
-
errors='ignore':忽略无法解码的字符,避免程序崩溃
方案 4:针对 GBK 编码的特殊处理
# 部分老旧系统使用 GBK 编码
response.encoding = 'gbk'
# 或者
response.encoding = 'gb18030' # GBK 的超集,支持更多字符
3.3 调试乱码问题的技巧
def debug_encoding(response):
"""调试编码问题的辅助函数"""
print("=== 编码调试信息 ===")
print(f"requests 猜测的编码: {response.encoding}")
print(f"自动检测的编码: {response.apparent_encoding}")
print(f"Content-Type 头: {response.headers.get('Content-Type')}")
print(f"响应字节前50个: {response.content[:50]}")
print(f"当前文本前50字符: {response.text[:50]}")
# 尝试不同编码
for enc in ['utf-8', 'gbk', 'gb2312', 'latin-1']:
try:
decoded = response.content.decode(enc)[:30]
print(f"{enc} 解码结果: {decoded}")
except:
print(f"{enc} 解码失败")
# 使用方法:在请求后调用
debug_encoding(response)
4. 请求参数说明
|参数名|类型|必填|默认值|说明|
|---|---|---|---|---|
|page|int|是|1|页码,从 1 开始计数。用于分页获取数据|
|limit|int|是|10|每页返回的数据条数。建议设置为 10-50 之间,过大可能导致请求超时|
|cityCode|string|是|431300|城市行政区划代码:<br>- 431300 = 娄底市<br>- 可根据需要更换为其他城市代码|
|sortField|string|是|m_ALLFloor|排序字段:<br>- m\_ALLFloor: 按总楼层排序<br>- 其他可选值需参考接口文档|
|unknownUserId|string|是|-|用户标识 ID,用于接口访问鉴权和请求追踪。此为固定值,请勿随意修改|
参数使用建议
-
分页策略:建议 limit 设置为 20-30,循环请求多页数据,避免单次请求过大
-
城市代码:可通过国家统计局官网查询各城市的行政区划代码
-
频率控制:请求频率建议控制在 1 秒 / 次以内,避免触发接口限流
5. 返回结果处理示例
5.1 返回数据结构说明
接口返回标准 JSON 格式,结构如下:
{
"code": 0, // 状态码,0 表示成功
"msg": "success", // 状态消息
"data": {
"total": 1234, // 数据总条数
"page": 1, // 当前页码
"limit": 10, // 每页条数
"list": [ // 房源列表数组
{
"m_ID": "xxx", // 房源ID
"m_Title": "xxx", // 房源标题
"m_Price": 100, // 价格(万元)
"m_Square": 120, // 面积(㎡)
"m_Room": 3, // 室数
"m_Hall": 2, // 厅数
"m_Floor": 15, // 当前楼层
"m_ALLFloor": 32, // 总楼层
"m_Address": "xxx",// 小区地址
"m_Phone": "xxx" // 联系电话
// ... 其他字段
}
]
}
}
5.2 数据处理示例代码
def process_house_data(result):
"""
处理返回的房源数据,提取关键信息并格式化输出
"""
if not result or result.get('code') != 0:
print("数据获取失败或返回错误")
return []
data = result.get('data', {})
house_list = data.get('list', [])
processed_data = []
for house in house_list:
# 提取关键字段,设置默认值避免 KeyError
processed_house = {
'房源ID': house.get('m_ID', ''),
'房源标题': house.get('m_Title', '').strip(),
'总价(万元)': house.get('m_Price', 0),
'面积(㎡)': house.get('m_Square', 0),
'户型': f"{house.get('m_Room', 0)}室{house.get('m_Hall', 0)}厅",
'楼层': f"{house.get('m_Floor', 0)}/{house.get('m_ALLFloor', 0)}层",
'小区地址': house.get('m_Address', ''),
'单价(元/㎡)': round(house.get('m_Price', 0) * 10000 / house.get('m_Square', 1), 2)
if house.get('m_Square', 0) > 0 else 0
}
processed_data.append(processed_house)
return processed_data
def export_to_csv(data, filename='house_data.csv'):
"""
将处理后的数据导出为CSV文件
"""
import csv
if not data:
print("没有数据可导出")
return
with open(filename, 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.DictWriter(f, fieldnames=data[0].keys())
writer.writeheader()
writer.writerows(data)
print(f"数据已导出到 {filename}")
# 综合使用示例
if __name__ == "__main__":
# 获取数据
raw_data = get_second_hand_house_data(page=1, limit=20)
if raw_data:
# 处理数据
processed = process_house_data(raw_data)
# 打印统计信息
print(f"\n=== 数据统计 ===")
total = raw_data.get('data', {}).get('total', 0)
print(f"总房源数: {total} 套")
print(f"本次获取: {len(processed)} 套")
if processed:
avg_price = sum(h['总价(万元)'] for h in processed) / len(processed)
avg_unit_price = sum(h['单价(元/㎡)'] for h in processed) / len(processed)
print(f"平均总价: {avg_price:.1f} 万元")
print(f"平均单价: {avg_unit_price:.0f} 元/㎡")
# 导出CSV
export_to_csv(processed)
5.3 批量获取所有数据
def get_all_house_data(max_pages=10):
"""
循环获取所有页面的数据
"""
all_data = []
for page in range(1, max_pages + 1):
print(f"正在获取第 {page} 页...")
result = get_second_hand_house_data(page=page, limit=20)
if not result or result.get('code') != 0:
print(f"第 {page} 页获取失败,停止获取")
break
house_list = result.get('data', {}).get('list', [])
if not house_list:
print("已获取完所有数据")
break
all_data.extend(house_list)
# 检查是否还有下一页
total = result.get('data', {}).get('total', 0)
if len(all_data) >= total:
break
# 避免请求过快
import time
time.sleep(0.5)
print(f"共获取 {len(all_data)} 条房源数据")
return all_data
6. 常见问题与注意事项
-
请求频率限制:建议添加适当的延时(0.5-1 秒),避免触发服务器反爬机制
-
异常处理:网络请求可能失败,务必添加 try-except 异常捕获
-
参数验证:cityCode 等参数需要验证有效性,避免传入错误值
-
数据清洗:返回数据可能存在空值或异常值,处理时需设置默认值
-
编码问题:如果 UTF-8 不行,尝试 GBK 编码,部分老旧系统可能使用 GBK
(注:文档部分内容可能由 AI 生成)
Scan to join WeChat group