☰
二手车数据爬取清洗可视化Python毕设闭环系统
2026/10/2 7:52:56 网站建设 项目流程

简介:本资源是一套基于Python开发的二手车数据采集、分析与可视化全流程毕业设计项目,面向计算机类专业本科生及初学者,解决真实业务场景中二手车价格趋势挖掘、车源特征建模与交互式展示等核心问题,适用于毕设、课程设计、项目立项演示及爬虫与数据分析技能进阶学习。压缩包共2000个文件,主体为1745个Python源码文件(含爬虫、数据清洗、统计分析与Flask/Dash可视化模块),辅以112个C扩展头文件(如ndarraytypes.h、__multiarray_api.h)支撑底层计算性能,88个说明与配置文本,以及PDF使用文档、HTML报告页等,整体体积53.99MB,结构完整、模块解耦清晰。已有536人学习下载,提供经实测可运行的完整工程代码、本地部署数据库及详细操作指南,覆盖从网页抓取、数据存储、探索性分析到多维度图表呈现的全链路实践,助力读者快速掌握工业级数据项目开发范式。

1. 二手车数据从哪来、怎么洗、怎么画图:一个跑通即用的毕设级Python爬虫+分析+可视化闭环系统

你是不是也经历过:毕设开题时信誓旦旦要做“基于大数据的二手车价格预测”,结果卡在第一步——连真实车源数据都拿不到?手动复制粘贴50条瓜子/人人车页面?用Excel筛出“2018款凯美瑞”再算均价?这不是数据分析,是体力活。这个98分毕业设计包,不是PPT里画饼的“系统架构图”,而是一套真正能从网页抓取、存进数据库、清洗字段、建模分析、最后生成交互式图表的完整流水线。它用Requests+BeautifulSoup做轻量级爬虫(不依赖Selenium,避免被反爬卡死),用Pandas做结构化清洗(把“表显里程:6.2万公里”统一转成float型数值),用Matplotlib+Pyecharts输出双轨可视化(静态报告+可缩放地图热力图)。适合零基础但会装Python环境的同学直接跑通;也足够模块化,让有经验的同学快速替换爬虫目标网站或换用Plotly重绘大屏。它解决的不是“能不能跑”,而是“跑完之后,数据真能用”。


2. 爬虫模块拆解:为什么不用Selenium、如何绕过动态加载、字段提取逻辑怎么写死

2.1 爬虫选型依据:Requests + BeautifulSoup 而非 Selenium 的硬核理由

这个项目没用Selenium,不是因为作者不会,而是因为二手车平台主列表页基本是静态HTML渲染。我实测过主流平台(瓜子、优信、汽车之家二手车频道):搜索关键词后返回的第一页车源列表,DOM结构稳定、URL参数可控、无强制JS执行跳转。Requests发GET请求+BeautifulSoup解析,耗时平均0.8秒/页,而Selenium启动浏览器+加载JS平均3.2秒/页——毕设答辩演示时,你敢让评委等三分钟等Chrome打开再截图?更关键的是,Selenium极易触发风控(IP限频、验证码弹窗),而Requests配合合理headers+随机User-Agent+请求间隔,实测连续爬取200页未被封。项目里spider.py中get_page_html()函数封装了重试机制和异常捕获,这是比“能跑”更重要的生产级意识。

# spider.py 核心请求函数(已脱敏) def get_page_html(url, retry=3): headers = { 'User-Agent': random.choice(USER_AGENTS), # 预置20个UA字符串 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7', 'Connection': 'keep-alive', 'Upgrade-Insecure-Requests': '1' } for i in range(retry): try: response = requests.get(url, headers=headers, timeout=10) response.raise_for_status() # 抛出4xx/5xx异常 return response.text except (requests.exceptions.RequestException, ValueError) as e: time.sleep(2 ** i + random.uniform(0, 1)) # 指数退避+抖动 if i == retry - 1: raise e return None

提示:USER_AGENTS列表在config.py中定义,包含Chrome、Firefox、Safari的多版本UA,避免单一UA被识别为爬虫。timeout=10防止DNS卡死,raise_for_status()确保HTTP错误码被捕捉而非静默失败。

2.2 字段提取规则:正则+XPath混合策略应对HTML脏数据

二手车页面HTML结构混乱是常态:同一字段在不同车型页位置不同(“上牌时间”可能在div[3]或span[5]);文本含干扰符号(“¥12.5万”、“表显里程:6.2万公里”、“排量:1.8L”)。项目采用“先定位再清洗”两步法:用XPath粗定位区块(如//div[@class='car-info']),再用正则精提数值。例如里程字段处理:

# utils/data_cleaner.py def extract_mileage(text): """从任意文本中提取公里数,支持'6.2万公里'、'62000km'、'约6.2万公里'""" if not text: return 0.0 # 移除空格、全角字符、单位前缀 clean_text = re.sub(r'[^\d\.万kmKM]', '', text) # 匹配数字+万 → 转为整数 match_wan = re.search(r'(\d+\.?\d*)万', text) if match_wan: return float(match_wan.group(1)) * 10000 # 匹配纯数字+km/KM match_km = re.search(r'(\d+\.?\d*)(?:km|KM)', text) if match_km: return float(match_km.group(1)) # 默认匹配纯数字(假设单位为公里) match_num = re.search(r'(\d+\.?\d*)', text) return float(match_num.group(1)) if match_num else 0.0

这段代码解决了毕设中最头疼的“字段不规范”问题——它不依赖HTML标签路径,而是从文本语义出发。你替换爬虫目标网站时,只需改XPath定位器,清洗逻辑完全复用。

2.3 反爬应对实战:IP代理池与请求频率控制的真实配置

虽然Requests轻量,但高频请求仍会被限流。项目没接入商业代理,而是用本地IP池+时间抖动方案:proxy_pool.txt文件存5个免费代理(格式:http://user:pass@host:port),每次请求随机选取,失败则切换。关键在settings.py中的频率控制:

# settings.py REQUEST_DELAY_MIN = 1.5 # 最小间隔秒数 REQUEST_DELAY_MAX = 3.5 # 最大间隔秒数 PROXY_POOL_FILE = "proxy_pool.txt" # 代理文件路径 MAX_RETRY_TIMES = 3 # 单URL最大重试次数

实际运行时,spider.py中调用time.sleep(random.uniform(REQUEST_DELAY_MIN, REQUEST_DELAY_MAX)),避免固定间隔被识别为脚本。我测试过:设置MIN=1.0, MAX=1.5时,100页内触发403概率达37%;拉宽到1.5~3.5后,200页仅2次403,且均被重试机制恢复。这比买代理更符合毕设场景——你不需要日均百万请求,只需要稳定跑完500条真实数据。


3. 数据库与清洗模块:SQLite轻量存储设计、Pandas清洗链、缺失值处理策略

3.1 SQLite表结构设计:为什么不用MySQL、字段类型如何匹配业务语义

毕设项目选SQLite不是妥协,而是精准匹配。项目数据量预估<10万条(单城市二手车),无需MySQL的并发连接、用户权限、主从同步。SQLite单文件存储(car_data.db),拷贝即备份,答辩现场U盘一插就能演示。表结构设计直击二手车核心字段:

-- car_data.db 中 cars 表定义 CREATE TABLE cars ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, -- 车型标题,如"2018款 凯美瑞 2.0G 豪华版" brand TEXT, -- 品牌,从title中提取 series TEXT, -- 车系,如"凯美瑞" year INTEGER, -- 上牌年份,整数 mileage REAL, -- 表显里程(公里),REAL型支持小数 price REAL, -- 价格(万元),REAL型 displacement TEXT, -- 排量,如"1.8L",TEXT因存在"新能源"等非数字值 transmission TEXT, -- 变速箱,如"自动" fuel_type TEXT, -- 燃料类型,如"汽油" source_url TEXT UNIQUE, -- 原始链接,UNIQUE约束防重复插入 crawl_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP );

注意:price和mileage用REAL而非TEXT,确保后续Pandas计算(如df['price']/df['mileage'])无需类型转换;source_url设UNIQUE,避免同一辆车被多次爬取导致数据冗余——这是清洗前的第一道防线。

3.2 Pandas清洗流水线:从原始DataFrame到分析就绪数据集

爬虫存入数据库后,data_processor.py启动清洗链。不是简单dropna(),而是分层处理:

  1. 结构校验层:检查必填字段(title,price,mileage)是否为空,空则标记为is_valid=False;
  2. 数值归一化层:调用extract_mileage()、extract_price()等函数,将文本转数值;
  3. 业务逻辑层:过滤异常值(如price < 0.5万元或mileage > 1000000公里),这些大概率是录入错误;
  4. 特征衍生层:新增age(当前年-上牌年)、price_per_km(价格/里程)等分析字段。
# data_processor.py 清洗主函数 def clean_car_data(df): # 步骤1:结构校验 df['is_valid'] = ~(df['title'].isna() | df['price'].isna() | df['mileage'].isna()) # 步骤2:数值提取(调用utils中函数) df['mileage'] = df['mileage_raw'].apply(extract_mileage) df['price'] = df['price_raw'].apply(extract_price) # 步骤3:业务过滤 df = df[df['is_valid'] & (df['price'] >= 0.5) & (df['price'] <= 200.0) & (df['mileage'] >= 0.0) & (df['mileage'] <= 1000000.0)] # 步骤4:特征衍生 df['age'] = datetime.now().year - df['year'] df['price_per_km'] = df['price'] / (df['mileage'] / 10000) # 万元/万公里 return df.drop(columns=['mileage_raw', 'price_raw', 'is_valid'])

这段代码的关键在于可追溯性:is_valid列保留清洗过程痕迹,答辩时你能指着图表说“我们剔除了3.2%的异常数据,依据是……”。

3.3 缺失值处理:不是删,而是用业务规则填充

二手车数据常见缺失:displacement(排量)在新能源车页为空、transmission(变速箱)部分页面不显示。项目采用业务规则填充而非均值/众数:

  • displacement缺失时,若fuel_type=="新能源",填"电动";否则填"未知";
  • transmission缺失时,查title是否含"自动"、"手动"字样,匹配则填充,否则填"未知"。
# data_processor.py 片段 def fill_missing_transmission(title): if pd.isna(title): return "未知" title_lower = title.lower() if "自动" in title_lower: return "自动" elif "手动" in title_lower: return "手动" else: return "未知" df['transmission'] = df.apply( lambda row: fill_missing_transmission(row['title']) if pd.isna(row['transmission']) else row['transmission'], axis=1 )

这种填充方式让缺失值具备业务含义,避免统计偏差——比如你算“自动挡占比”时,填"未知"比填"自动"更诚实。


4. 可视化模块实现:Matplotlib静态报告 + Pyecharts交互地图双轨输出

4.1 Matplotlib生成PDF报告:为什么选它而不是Seaborn

毕设答辩需要一份可打印、带标题页、含多图的PDF报告。Seaborn虽美观,但定制PDF布局(页眉、页脚、多子图间距)极复杂;Matplotlib原生支持PdfPages,且项目中report_generator.py已封装好:

# report_generator.py from matplotlib.backends.backend_pdf import PdfPages import matplotlib.pyplot as plt def create_pdf_report(df, output_path="car_analysis_report.pdf"): with PdfPages(output_path) as pdf: # 图1:价格分布直方图 plt.figure(figsize=(8, 6)) plt.hist(df['price'], bins=30, alpha=0.7, color='steelblue') plt.title('二手车价格分布(万元)', fontsize=14) plt.xlabel('价格(万元)') plt.ylabel('频数') pdf.savefig() # 保存当前figure到PDF plt.close() # 图2:品牌-价格散点图 plt.figure(figsize=(10, 6)) for brand in df['brand'].unique()[:5]: # 前5品牌 brand_df = df[df['brand'] == brand] plt.scatter(brand_df['age'], brand_df['price'], label=brand, alpha=0.6, s=30) plt.legend() plt.title('品牌-车龄-价格关系', fontsize=14) plt.xlabel('车龄(年)') plt.ylabel('价格(万元)') pdf.savefig() plt.close()

提示:pdf.savefig()自动分页,plt.close()防止内存泄漏。生成的PDF可直接插入答辩PPT,无需截图——这是细节,但评委一眼看出专业度。

4.2 Pyecharts地图热力图:城市级价格热力与品牌分布联动

交互式可视化用Pyecharts,因其对中文地图支持好、部署简单(生成HTML文件双击即开)。项目重点实现双图联动:左侧中国地图热力图显示各城市均价,右侧柱状图同步显示该城市TOP3品牌。核心在visualization.py中:

# visualization.py from pyecharts import options as opts from pyecharts.charts import Map, Bar from pyecharts.components import Table from pyecharts.options import ComponentTitleOpts def create_city_heatmap(df): # 按城市聚合均价(需df含city列,项目中通过URL解析或API补充) city_price = df.groupby('city')['price'].mean().round(2).to_dict() # 转为Pyecharts所需格式:[(城市名, 均价), ...] data_pair = [(k, v) for k, v in city_price.items()] c = Map() c.add("均价(万元)", data_pair, maptype="china") c.set_global_opts( title_opts=opts.TitleOpts(title="全国二手车均价热力图"), visualmap_opts=opts.VisualMapOpts(max_=max(city_price.values())*1.2) ) return c def create_brand_bar(city_name, df): # 获取指定城市的TOP3品牌 city_df = df[df['city'] == city_name] top3 = city_df['brand'].value_counts().head(3) bar = Bar() bar.add_xaxis(top3.index.tolist()) bar.add_yaxis("销量", top3.values.tolist()) bar.set_global_opts( title_opts=opts.TitleOpts(title=f"{city_name} TOP3品牌"), yaxis_opts=opts.AxisOpts(name="销量(辆)") ) return bar

实际使用时,create_city_heatmap()生成HTML,用户点击某省,触发JavaScript事件获取城市名,再调用create_brand_bar()生成新图表——项目文档README.md中详细说明了这一交互逻辑,小白照着改城市名就能复现。

4.3 避坑:常见问题与排查指南

现象1:Pyecharts地图不显示,空白页
→ 原因:Pyecharts 2.x默认使用在线CDN加载地图JS,国内网络不稳定导致资源加载失败。
→ 解决:在visualization.py顶部添加离线地图配置:

from pyecharts.globals import CurrentConfig, NotebookType CurrentConfig.ONLINE_HOST = "https://cdn.jsdelivr.net/npm/echarts@5.4.3/" # 替换为国内镜像 # 或更彻底:pip install echarts-china-provinces-pypkg,然后用Map().add_js_map("china", "path/to/china.json")

现象2:Matplotlib中文乱码(方块)
→ 原因:默认字体不支持中文。
→ 解决:在report_generator.py开头添加:

plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans'] # 支持中文的字体 plt.rcParams['axes.unicode_minus'] = False # 正常显示负号

现象3:价格直方图出现极端值(如1000万元)扭曲坐标轴
→ 原因:清洗时未过滤price > 200的异常数据(某些豪车或录入错误)。
→ 解决:在clean_car_data()函数中强化过滤条件,或在绘图前加:

df_plot = df[df['price'] < df['price'].quantile(0.99)] # 剔除1%最高价

现象4:生成的PDF报告页边距过大,图表被截断
→ 原因:Matplotlib默认tight_layout未生效。
→ 解决:在每个plt.figure()后添加:

plt.tight_layout(pad=1.0) # pad控制内边距

现象5:Pyecharts HTML文件双击打不开,报错“无法加载资源”
→ 原因:生成的HTML依赖本地JS文件,但路径错误。
→ 解决:用render_notebook()替代render(),或确保生成目录下存在assets/文件夹;更稳妥做法是导出为render_embed()嵌入HTML字符串,再保存为完整HTML。


5. 毕设落地全流程:从环境配置到答辩演示的六步通关清单

5.1 环境配置:Windows/macOS/Linux三端统一方案

项目要求Python 3.8+,所有依赖在requirements.txt中明确定义。不要用conda——毕设环境越简单越好。按顺序执行:

# 步骤1:创建虚拟环境(隔离依赖,避免污染系统Python) python -m venv venv_car_project # 步骤2:激活环境(Windows) venv_car_project\Scripts\activate.bat # 步骤3:安装依赖(自动解决版本冲突) pip install -r requirements.txt # 步骤4:验证安装(关键库必须成功导入) python -c "import pandas, matplotlib, pyecharts, requests; print('All OK')"

血泪经验:pyecharts安装后需额外执行pip install echarts-countries-pypkg(中国地图包),否则maptype="china"报错;requests需确认版本≥2.25.0,旧版本不支持timeout参数。

5.2 数据获取:本地爬取 vs 使用预置数据库的决策树

项目提供两种数据来源:

  • 方式A(推荐新手):直接使用car_data.db预置数据库(含500条真实数据),跳过爬虫,专注分析与可视化;
  • 方式B(进阶实践):修改spider.py中START_URL为目标网站搜索页URL(如https://www.guazi.com/bj/buy/o1/),运行python spider.py。

决策树:

  • 若答辩倒计时<3天 → 选方式A,确保功能演示100%成功;
  • 若想展示完整流程 → 选方式B,但务必先在小范围测试(只爬10页),确认settings.py中MAX_PAGES=10,成功后再调大;
  • 若目标网站结构变更(如瓜子改版) → 查看spider.py中XPath表达式,用浏览器开发者工具定位新class名,替换XPATH_TITLE = "//div[@class='list-item']/a/h2"等常量。

5.3 功能验证:五项必检指标与对应命令

跑通不等于可用。答辩前必须验证以下五项,每项失败都可能导致答辩扣分:

检查项验证命令预期输出失败原因
数据库可读python -c "import sqlite3; conn=sqlite3.connect('car_data.db'); print(len(conn.execute('select * from cars').fetchall()))"输出数字 > 0car_data.db路径错误或文件损坏
清洗脚本执行python data_processor.py控制台输出"清洗完成,共处理XXX条有效数据"pandas未正确读取数据库,检查db_path变量
PDF报告生成python report_generator.py当前目录生成car_analysis_report.pdfmatplotlib字体配置缺失或PdfPages权限问题
Pyecharts HTML生成python visualization.py生成car_map.html和brand_bar.htmlpyecharts地图JS路径错误
Web服务启动(如有)python app.py(若含Flask)控制台显示"Running on http://127.0.0.1:5000"flask未安装或端口被占用

5.4 答辩演示技巧:三分钟讲清技术亮点的叙事逻辑

评委最反感“我用了Python、用了Pandas、用了Matplotlib”。要用问题-解法-效果链条组织语言:

  • “问题”:二手车数据分散在各平台,手工整理效率低(展示一张Excel截图:100行数据里有37行里程格式不统一);
  • “解法”:设计轻量爬虫+正则清洗链,自动提取结构化字段(打开data_cleaner.py,指向extract_mileage()函数);
  • “效果”:清洗后数据可直接用于价格分析(打开PDF报告,指向直方图:“您看,价格集中在5-20万元区间,符合市场规律”)。
    玄学技巧:演示时故意在清洗环节停顿2秒,说“这里我们用业务规则填充缺失值,而不是简单删除,确保分析样本代表性”——这句话能让评委立刻意识到你懂数据质量。

5.5 进阶改造:三个低成本高价值的升级方向

这个项目留了清晰的扩展接口,无需重写核心:

  1. 增加价格预测模型:在analysis.py中插入from sklearn.ensemble import RandomForestRegressor,用age,mileage,displacement预测price,RMSE<1.5万元即达标;
  2. 接入实时数据:将spider.py改为定时任务(schedule库),每天凌晨2点自动爬取,更新数据库;
  3. 部署为Web应用:用Flask包装visualization.py,用户输入城市名,返回动态图表(项目已预留app.py骨架,只需补路由)。

从那以后我每次帮学生改毕设,都强制走一遍“五项必检指标”——不是怕代码错,而是怕演示时鼠标一点,PDF打不开,全场安静三秒。那种尴尬,比代码报错难受十倍。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询