从零构建:途虎养车数据爬虫实战指南
本文将以途虎养车平台为例,搭建一个高效、稳定的爬虫系统。内容涵盖从需求分析、技术选型到反爬策略应对的全流程,并提供可复用的代码示例与架构设计思路。
前言
最近,由于需要了解汽车滤芯更换套餐的市场行情,我决定通过爬虫技术从途虎养车平台采集相关数据。选择途虎养车作为目标平台主要有两个原因:
- 行业地位:途虎养车是国内领先的汽车后市场服务平台,拥有丰富的商品和服务数据,具有较高的参考价值。
- 技术可行性:与其他完全依赖APP的汽车服务平台不同,途虎仍然维护了网页端服务,使得数据抓取成为可能。
在调研过程中,我发现途虎的网页端仍然可用:途虎养车网页版,这为后续的爬虫开发提供了便利。
链接抓取
为了分析途虎养车的数据请求,我们可以使用浏览器的开发者工具进行抓包。首先在Chrome或Firefox中打开途虎养车网页端(途虎养车手机版),按下F12
键调出开发者工具,切换到Network面板(网络)并确保选中All选项。

在正式抓取数据前,我们需要先完成几个关键操作步骤。首先登录途虎养车账号,在个人中心选择对应的车辆信息,然后进入"保养"服务页面。待页面完全加载后,系统会展示各类保养套餐信息。此时开发者工具已经记录了所有网络请求,我们需要特别关注那些返回JSON格式数据的API接口,这些往往包含我们需要的核心信息。
对于初学者来说,可以通过逐个查看接口的响应内容来定位目标数据。以当前页面为例,在Network面板中筛选请求后,可以清楚地看到返回的JSON数据中包含了我们所需的保养套餐详情,包括项目名称、价格、维护项目以及使用产品等关键字段。这个发现过程为后续编写爬虫代码提供了明确的数据接口定位。

我们可以通过以下方式获取目标链接:在开发者工具的Network面板中,找到包含所需数据的请求后,直接双击该请求行,浏览器会自动在新标签页中打开该请求的响应内容。此时地址栏显示的URL就是我们需要的目标API链接,这个链接将作为后续爬虫程序直接请求的数据接口。
链接分析
我们获得了以下链接:
https://maint-api.tuhu.cn/apinew/GetBaoYangAppPackages?channel=kH5&activityId=&city=上海市&province=上海市&lngBegin=119.80062593300364&latBegin=33.14440913557059&vehicle={"CarId":"ba12c20d-9256-4117-bade-47d47b68e822","PaiLiang":"电动","OnRoadTime":"","VehicleId":"VE-TSLY","tireSize":"","Properties":[],"Nian":"2025","Distance":0,"Tid":"160821"}&baoYangTypes=&isDefaultExpand=true&userId=0bdf60a3-ff01-459d-8621-de160851eed8&productIds=
我们需要深入解析这个API链接的结构,以确定哪些参数是必须的、哪些是可选的,从而构建出可批量爬取的请求链接。通过拆解这个URL,我们可以清晰地看到途虎养车API的参数设计逻辑:
核心参数分类说明
- 基础定位参数
city/province
:省市信息(需URL编码)lngBegin/latBegin
:经纬度坐标(支持6位小数)- 示例:
city=上海
→city=%E4%B8%8A%E6%B5%B7
- 车辆身份参数
{ "CarId": "ba12c20d-...", // 车辆唯一标识 "VehicleId": "VE-TSLY", // 车型编码 "PaiLiang": "电动", // 动力类型 "Nian": "2025" // 年款 }
- 用户会话参数
userId
:用户唯一标识(32位UUID格式)channel
:渠道标识(kH5表示H5页面)
- 业务筛选参数
baoYangTypes
:保养类型过滤productIds
:指定商品ID查询
首先观察基础URL部分:https://maint-api.tuhu.cn/apinew/GetBaoYangAppPackages
,这是所有请求的入口端点。紧随其后的问号表示开始查询参数,这些参数使用标准的key=value格式,以&符号分隔。在这些参数中,有些是必填的核心参数,有些则是可选的辅助参数。
必填参数包括:
- 地理位置参数:
city
和province
需要填写具体的省市名称,建议使用URL编码格式 - 车辆标识参数:
vehicle
是一个JSON字符串,必须包含有效的CarId
等车辆信息 - 用户标识:
userId
虽然是UUID格式,但在未登录状态下可以使用默认值
可选参数包括:
- 坐标参数:
lngBegin
和latBegin
可以留空或使用默认值 - 筛选参数:
baoYangTypes
和productIds
可以留空获取全部结果 - 活动参数:
activityId
通常可以留空
特别需要注意的是channel=kH5
这个参数,它标识请求来源,保持这个值可以避免一些反爬检测。在实际批量爬取时,我们主要需要动态替换的参数是city
、province
和vehicle
中的车辆信息,其他参数可以保持固定值。通过这种参数分析,我们就可以设计出可批量请求的URL模板,只需替换关键参数即可获取不同地区、不同车型的保养套餐数据。
必要参数获取
这一步将使用Python去获得目标参数,并解决途虎平台"最多只能添加5辆车"的限制问题。我们需要做以下几步:
- 获取途虎养车的品牌、车型、排量等基础数据
- 绕过5辆车限制实现大批量数据爬取
- 构建有效的API请求
- 处理和存储爬取结果 爬取流程如下:
环境准备
安装必要库
pip install requests
核心代码解析
配置参数
# 基本配置
USER_ID = "0bdf60a3-ff01-459d-8621-de160851eed8" # 用户ID
CITY = "上海市" # 默认城市
PROVINCE = "上海市" # 默认省份
# 请求控制
RETRY_COUNT = 3 # 请求重试次数
DELAY_BETWEEN_REQUESTS = 3 # 请求间隔时间(秒)
请求头设置
headers = {
"User-Agent": "Mozilla/5.0...",
"Content-Type": "application/json",
"Authorization": "Bearer 204576661ec744519fcd3c2714a850ad",
# 其他必要headers...
}
请求头在爬虫中的作用至关重要,它就像是网络请求的身份证和通行证,决定了服务器是否会接受并响应你的请求。在途虎养车这样的平台爬取数据时,请求头不仅需要包含基本的身份标识信息,还需要模拟真实用户的行为特征,以避免被识别为自动化程序而遭到拦截。一个典型的请求头会包含User-Agent来伪装成普通浏览器,携带Authorization和Cookie来维持登录状态,设置Content-Type来声明数据格式,还可能包含一些平台特定的验证字段如blackbox或TongDun-TokenId等反爬机制相关的信息。这些头部信息共同构成了一个完整的请求身份,缺少任何一个关键字段都可能导致请求失败。
车辆管理功能
获取当前车辆列表
def get_car_list() -> List[Dict]:
"""获取当前账户下的车辆列表"""
url = "https://cl-gateway.tuhu.cn/cl-user-info-site/myCar/getCarListByUserId"
payload = {"userId": USER_ID}
response = requests.post(url, headers=headers, json=payload)
if response.json().get("code") == 10000:
return response.json().get("data", [])
return []
- 这里的url也需要通过上面的
F12
开发者模式的网络请求中获取,即调用哪个api才能获取汽车列表。我们在汽车列表页面对网络请求进行抓取就会获得以上的api,通过发送我们的userID
即可获得到所有的汽车品牌。
删除指定车辆
因为途虎只允许未认证的情况下添加5辆汽车,限制了我们爬取全部汽车的信息,这个时候我们就需要在获得一辆汽车的信息后删除这辆车,反复执行添加删除车辆,直到获得所有车的信息。
提示
这里的url是途虎的车辆删除端口,通过F12
抓取,发送carID
和userID
就可以删除你的汽车。
def delete_car(car_id: str) -> bool:
"""删除指定车辆"""
url = "https://cl-gateway.tuhu.cn/cl-user-info-site/myCar/removeCar"
payload = {"carId": car_id, "userId": USER_ID}
response = requests.post(url, headers=headers, json=payload)
return response.json().get("code") == 10000
添加新车
通过添加新车,我们可以获得汽车的carId
,我们需要给url发送payload
中的信息就可以获取:
def add_vehicle(model_code: str, year: int, tid: int) -> Optional[str]:
"""添加新车并返回carId"""
url = "https://cl-gateway.tuhu.cn/cl-user-info-site/myCar/addCar"
payload = {
"modelCode": model_code,
"productionYear": year,
"tid": tid,
"source": "tuhu_wap"
}
response = requests.post(url, headers=headers, json=payload)
if response.json().get("code") == 10000:
return response.json().get("data", {}).get("carId")
return None
数据爬取功能
构建养护查询链接
def build_maintenance_link(vehicle_json: str) -> str:
"""构建养护查询完整链接"""
base_url = "https://maint-api.tuhu.cn/apinew/GetAppFirstPageExternalData"
params = {
"channel": "kH5",
"city": CITY,
"province": PROVINCE,
"vehicle": requests.utils.quote(vehicle_json)
}
return f"{base_url}?{urllib.parse.urlencode(params)}"
执行数据爬取
def crawl_maintenance(link: str) -> Optional[Dict]:
"""爬取养护数据"""
try:
response = requests.get(link, headers=headers, timeout=15)
return response.json().get("Categories", [])
except Exception as e:
print(f"爬取失败: {str(e)}")
return None
5辆车限制解决方案
解决方案设计
核心实现代码
def process_vehicle(model_info: Dict) -> Optional[Dict]:
"""处理单个车型的爬取流程"""
# 检查并管理车辆
while True:
current_vehicles = get_car_list()
if len(current_vehicles) < 5:
break
# 删除最早添加的车辆
oldest = sorted(current_vehicles, key=lambda x: x["addTime"])[0]
if not delete_car(oldest["carId"]):
return None
time.sleep(2)
# 添加新车
car_id = add_vehicle(
model_info["model_code"],
model_info["year"],
model_info["tid"]
)
if not car_id:
return None
# 构建并爬取链接
vehicle_json = build_vehicle_json(car_id, model_info)
link = build_maintenance_link(vehicle_json)
data = crawl_maintenance(link)
# 立即删除车辆
delete_car(car_id)
return {
"model_info": model_info,
"data": data,
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
完整工作流程
主函数实现
def main():
# 1. 获取所有品牌
brands = get_brands()
# 2. 遍历品牌获取车型
for brand in brands[:2]: # 测试时限制品牌数量
models = get_models(brand["name"])
# 3. 处理每个车型
for model in models[:3]: # 测试时限制车型数量
displacements = get_displacements(model["code"])
for disp in displacements[:2]: # 测试时限制排量数量
years = get_product_years(model["code"], disp)
for year in years[:1]: # 测试时限制年份数量
# 4. 获取tid并处理车辆
vehicle_info = get_vehicle_tid(model["code"], disp, year)
if vehicle_info:
result = process_vehicle({
"brand": brand["name"],
"model_name": model["displayName"],
"model_code": model["code"],
"displacement": disp,
"year": year,
"tid": vehicle_info["tid"]
})
# 5. 保存结果
if result:
save_result(result)
time.sleep(DELAY_BETWEEN_REQUESTS)
运行示例
python main.py
输出示例:
开始处理品牌: 大众
获取到车型: 高尔夫 (code: VW-GOLF)
处理排量: 1.4T
处理年份: 2022
成功添加车辆: VW123
爬取数据完成
删除车辆: VW123
保存结果成功
...
最终信息爬取
在数据功能爬取阶段,我们成功爬取了想要的参数,参数结果示例如下所示:
"奥迪": [
{
"model_name": "奥迪 A3",
"model_code": "VE-AADA3AD",
"factory": "一汽大众奥迪",
"displacement": "1.4T(35TFSI)",
"production_year": 2025,
"tid": "157761",
"car_id": "3B1BD673-BEE9-45CC-8B57-036275D53EFA",
"vehicle_json": "{\"CarId\": \"3B1BD673-BEE9-45CC-8B57-036275D53EFA\", \"PaiLiang\": \"1.4T(35TFSI)\", \"OnRoadTime\": \"\", \"VehicleId\": \"VE-AADA3AD\", \"tireSize\": \"\", \"Properties\": [], \"Nian\": \"2025\", \"Distance\": 0, \"Tid\": \"157761\"}",
"crawl_time": "2025-08-05 22:41:51",
"status": "完整",
"car_details": null
}
]
其实已经结束了,我们只需要把CarId
,model_code
,PaiLiang
必须信息填入之前的链接就可以获得套餐了,读取json文件填入就像,批量获取链接里面的内容,这里不多赘述,但是要控制爬的时间,一般随机1-3秒即可,实践证明太快会导致途虎封ip。