保姆级教程:用Python+Requests搞定携程汽车票数据爬取(附完整代码与常见报错解决)

张开发
2026/4/18 5:14:06 15 分钟阅读

分享文章

保姆级教程:用Python+Requests搞定携程汽车票数据爬取(附完整代码与常见报错解决)
Python爬虫实战高效获取汽车票数据的完整解决方案最近在帮朋友处理一个需求时遇到了一个典型的爬虫场景需要批量查询多个城市之间的汽车票班次信息。这个任务看似简单但实际操作中会遇到各种技术挑战特别是对于刚接触爬虫开发的朋友来说。本文将分享一个完整的解决方案从环境搭建到代码实现再到常见问题的排查手把手带你完成这个实用项目。1. 环境准备与基础配置在开始编写爬虫之前我们需要确保开发环境配置正确。这里推荐使用Python 3.8版本因为它提供了良好的兼容性和稳定性。以下是需要安装的核心库pip install requests pandas openpyxlrequests用于发送HTTP请求pandas处理Excel文件和数据结构openpyxl支持最新Excel格式的读写创建一个新的项目目录建议结构如下/bus_ticket_crawler ├── config.py # 存放配置信息 ├── utils.py # 工具函数 ├── main.py # 主程序 └── cities.xlsx # 城市列表数据在cities.xlsx中我们需要准备城市列表数据。表格结构建议如下始发地目的地A目的地B目的地C北京上海广州2. 网络请求分析与关键参数破解分析目标网站的请求流程是爬虫开发中最关键的环节。通过浏览器开发者工具F12我们可以观察到以下几个重要信息请求URL通常包含基础路径和动态参数请求头特别是User-Agent和Cookie请求体POST请求中传递的参数在汽车票查询场景中我们发现两个关键参数需要特别处理_fxpcqlniredt静态用户标识通常与Cookie中的GUID字段一致x-traceID动态生成的追踪ID由三部分组成用户标识当前时间戳毫秒级7位随机数以下是生成这些参数的Python实现import random import time def generate_trace_id(guid): timestamp str(int(time.time() * 1000)) random_num str(random.randint(0, 9999999)).zfill(7) return f{guid}-{timestamp}-{random_num}3. 核心爬虫代码实现现在我们来构建爬虫的核心逻辑。整个过程可以分为以下几个步骤读取Excel文件获取城市列表遍历所有城市组合为每个组合构建请求参数发送请求并解析响应将结果写回Excel文件以下是完整的代码实现import pandas as pd import requests import json import time import random from urllib.parse import urlencode class BusTicketCrawler: def __init__(self, excel_path): self.excel_path excel_path self.base_url https://m.ctrip.com/restapi/soa2/13906/json/busListV2 self.headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Content-Type: application/json } self.guid 替换为你的GUID # 从Cookie中获取 def get_city_list(self): df pd.read_excel(self.excel_path) return df[始发地].dropna().unique().tolist() def generate_params(self, from_city, to_city): trace_id generate_trace_id(self.guid) params { _fxpcqlniredt: self.guid, x-traceID: trace_id } data { fromCity: from_city, toCity: to_city, fromDate: time.strftime(%Y-%m-%d) } return params, data def parse_response(self, response): try: result json.loads(response.text) bus_list result.get(data, {}).get(bus, {}).get(data, {}).get(busList, []) return len(bus_list) except Exception as e: print(f解析响应出错: {e}) return error def update_excel(self, from_city, to_city, count): df pd.read_excel(self.excel_path) df.loc[df[始发地] from_city, to_city] count df.to_excel(self.excel_path, indexFalse) def run(self): cities self.get_city_list() for from_city in cities: for to_city in cities: if from_city to_city: continue params, data self.generate_params(from_city, to_city) url f{self.base_url}?{urlencode(params)} try: response requests.post(url, headersself.headers, jsondata) count self.parse_response(response) self.update_excel(from_city, to_city, count) print(f成功处理 {from_city} - {to_city}: {count}班次) # 随机延迟防止被封 time.sleep(random.uniform(1, 3)) except Exception as e: print(f处理 {from_city} - {to_city} 时出错: {e}) self.update_excel(from_city, to_city, error)4. 常见问题与解决方案在实际开发过程中我们可能会遇到各种问题。以下是几个典型问题及其解决方案4.1 InvalidHeader错误错误现象requests.exceptions.InvalidHeader: Invalid leading whitespace in header name原因分析 HTTP头部字段包含非法字符或格式不正确特别是从浏览器直接复制的头部可能包含隐藏字符。解决方案# 错误的头部示例 headers { User-Agent: Mozilla/5.0, # 前面有空格 Accept-Encoding: gzip, deflate, } # 正确的做法是确保键名没有多余空格 headers { User-Agent: Mozilla/5.0, Accept-Encoding: gzip, deflate, }4.2 KeyError: data错误现象 在尝试访问响应数据时出现KeyError提示data字段不存在。可能原因请求参数不正确导致返回的数据结构不符合预期服务器返回了错误响应使用了params而不是json传递POST数据解决方案# 错误的请求方式 response requests.post(url, headersheaders, paramsdata) # 正确的请求方式 response requests.post(url, headersheaders, jsondata) # 更健壮的解析方式 result response.json() bus_list result.get(data, {}).get(bus, {}).get(data, {}).get(busList, [])4.3 反爬机制应对网站可能会采取各种反爬措施我们可以通过以下方法提高爬虫的稳定性请求间隔在请求之间添加随机延迟time.sleep(random.uniform(1, 3))User-Agent轮换使用不同的User-Agentuser_agents [ Mozilla/5.0 (Windows NT 10.0; Win64; x64), Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ] headers[User-Agent] random.choice(user_agents)代理IP使用代理池避免IP被封proxies { http: http://your_proxy:port, https: http://your_proxy:port } response requests.post(url, headersheaders, jsondata, proxiesproxies)5. 高级技巧与优化建议当基本功能实现后我们可以考虑以下优化措施5.1 异步请求加速使用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(headersheaders) as session: tasks [] # 创建所有请求任务 for from_city, to_city in city_pairs: params, data generate_params(from_city, to_city) url f{base_url}?{urlencode(params)} tasks.append(fetch(session, url, data)) # 限制并发量 results await asyncio.gather(*tasks, return_exceptionsTrue) # 处理结果...5.2 数据持久化优化对于大规模数据采集可以考虑使用数据库替代Excelimport sqlite3 def init_db(): conn sqlite3.connect(bus_tickets.db) c conn.cursor() c.execute(CREATE TABLE IF NOT EXISTS tickets (from_city TEXT, to_city TEXT, count INTEGER, date TEXT)) conn.commit() return conn def save_to_db(conn, from_city, to_city, count): c conn.cursor() c.execute(INSERT INTO tickets VALUES (?, ?, ?, ?), (from_city, to_city, count, time.strftime(%Y-%m-%d))) conn.commit()5.3 日志记录与错误处理完善的日志系统可以帮助我们更好地监控爬虫运行状态import logging logging.basicConfig( levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s, handlers[ logging.FileHandler(crawler.log), logging.StreamHandler() ] ) try: # 爬虫代码 except Exception as e: logging.error(f爬取失败: {e}, exc_infoTrue)6. 项目扩展思路这个基础爬虫可以进一步扩展为更强大的工具定时任务使用APScheduler实现每日自动更新from apscheduler.schedulers.blocking import BlockingScheduler scheduler BlockingScheduler() scheduler.scheduled_job(cron, hour3) # 每天凌晨3点运行 def scheduled_job(): crawler BusTicketCrawler(cities.xlsx) crawler.run() scheduler.start()可视化展示使用matplotlib或pyecharts生成热力图import matplotlib.pyplot as plt import seaborn as sns def visualize(data): plt.figure(figsize(12, 8)) sns.heatmap(data, annotTrue, fmtd) plt.title(城市间汽车班次热力图) plt.show()价格监控扩展爬虫功能记录历史价格变化这个项目虽然针对的是特定网站的汽车票数据采集但其中涉及的技术和方法可以应用于许多类似的爬虫场景。关键在于理解网站的工作原理构建合理的请求并处理好各种边界情况和异常。

更多文章