Python二手车爬虫与可视化毕业设计实战指南
发布时间:2026/9/11 23:22:19来源:尧图网络
简介本资源是一套完整落地的本科毕业设计项目面向计算机、数据科学及相关专业学生聚焦二手车市场数据采集与商业分析实战场景。项目基于Python实现全流程从主流平台动态爬取车辆信息到SQLite本地数据库存储再到Pandas清洗、Matplotlib/Seaborn可视化及基础统计分析覆盖数据获取、处理、呈现与解读全链路亦适合作为课程设计或期末大作业直接复用。压缩包共2000个文件主体为1745个Python源码含爬虫主程序、数据清洗模块、可视化脚本及数据库操作逻辑辅以112个头文件、88个说明文本、13个JSON配置及11个PDF文档含设计说明书、答辩PPT与技术报告整体54.99MB结构清晰、模块解耦度高。目前已有656人学习下载提供经导师指导、答辩获97分的高分方案含可运行源码、完整数据库文件与详细文档无需二次调试即可部署验证。1. 为什么用 Python 爬二手车数据做毕业设计反而比“高大上”选题更容易拿高分很多同学一想到毕业设计就默认要搞 AI、深度学习或分布式系统结果卡在环境配置、模型调参、论文复现上最后答辩时连数据来源都说不清。而这个基于 Python 的二手车爬虫与可视化项目恰恰反其道而行之它不追求算法复杂度而是把「真实业务链路」跑通——从目标网站如瓜子、人人车、懂车帝等主流平台稳定抓取结构化车辆信息清洗掉价格乱码、里程异常、年份错位等典型脏数据再用 Matplotlib Seaborn Plotly 构建可交互的多维分析视图最终导出带统计结论的 PDF 报告。97 分答辩成绩背后是导师最看重的三点数据可溯源、流程可复现、结论有业务支撑。它适合两类人一是计算机/信管/大数据专业本科生需要一个完整闭环、无版权风险、本地即可运行的课程设计二是想快速建立数据工程实感的转行者——你不需要自己搭服务器、不用申请 API、不碰反爬对抗黑盒所有请求头、重试逻辑、代理池占位符都已预置解压即 run改两行 URL 就能迁移到其他垂直品类。2. 爬虫模块解析requests BeautifulSoup 为何仍是毕业设计首选组合2.1 为什么不用 Scrapy 或 Selenium——选型背后的教学合理性Scrapy 功能强大但学习曲线陡峭需理解中间件、管道、信号机制对仅需抓取静态列表页详情页的二手车场景属于过度设计Selenium 虽能渲染 JS但启动浏览器开销大、易被检测、CI/CD 部署困难。本项目采用 requests BeautifulSoup 组合核心逻辑集中在crawler/spider.py中共 217 行代码覆盖了从首页翻页到详情页解析的全路径。这种轻量架构让导师能一眼看清数据流向get_list_page()→parse_car_list()→extract_detail_url()→get_detail_page()→parse_car_detail()。更重要的是所有请求均模拟真实用户行为设置User-Agent为 Chrome 最新版本Accept-Language指定中文Referer回填上一页 URL且每请求间隔random.uniform(1.2, 2.8)秒——这既规避了基础频率限制又符合教学项目“可控、可解释”的要求。2.2 关键字段解析逻辑与容错处理二手车数据中价格、里程、上牌时间三类字段极易出错。例如某平台将“¥12.5万”存为span classprice12.5/spani万/i而另一平台用“125000元”纯数字格式。项目在parser/car_parser.py中定义了统一清洗函数def clean_price(text: str) - Optional[float]: 标准化价格字段支持12.5万、125000元、面议等多种格式 if not text or 面议 in text: return None # 提取所有数字和小数点 digits re.findall(r[\d.], text) if not digits: return None num_str .join(digits) try: if 万 in text: return float(num_str) * 10000 else: return float(num_str) except ValueError: return None提示该函数被parse_car_detail()调用时会对price_raw字段执行clean_price(price_raw)。若返回None则数据库对应字段写入NULL而非报错中断。这种“失败静默标记缺失”的策略比强行转换更符合真实数据工程规范。2.3 数据库持久化SQLite 作为毕业设计存储方案的不可替代性项目使用sqlite3模块直连data/cars.db建表语句在db/init_db.py中定义CREATE TABLE IF NOT EXISTS cars ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, price REAL, -- 清洗后价格单位元 mileage REAL, -- 里程单位万公里 reg_date DATE, -- 上牌日期格式YYYY-MM-DD brand TEXT, model TEXT, transmission TEXT, -- 变速箱类型自动/手动/手自一体 fuel_type TEXT, -- 燃料类型汽油/柴油/新能源 source_url TEXT UNIQUE, crawl_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP );注意source_url UNIQUE约束确保同一车辆不会重复入库避免翻页时详情页 URL 重复采集。crawl_time自动记录抓取时间后续做时间维度分析如“近3个月价格走势”时直接可用。下表列出各字段在爬虫中的映射关系及常见异常值处理方式数据库字段爬虫来源 HTML 元素典型异常值示例处理方式pricediv classprice¥15.8万/div“面议”、“暂无报价”、“电联”clean_price()返回Nonemileageli行驶里程span6.2万公里/span/li“未填写”、“1万公里”、“过户2次”正则提取数字非数字返回Nonereg_dateli上牌时间span2019年05月/span/li“2019.5”、“19年5月”、“约2019年”标准化为2019-05-01年份缺失则补当年3. 可视化分析模块从 Matplotlib 基础图到 Plotly 交互式仪表盘3.1 价格-车龄散点图揭示二手车市场核心规律analysis/price_age_analysis.py中的核心绘图代码如下import matplotlib.pyplot as plt import seaborn as sns import pandas as pd # 读取清洗后数据 df pd.read_sql_query(SELECT price, reg_date FROM cars WHERE price IS NOT NULL AND reg_date IS NOT NULL, conn) df[age] pd.to_datetime(today).year - pd.to_datetime(df[reg_date]).dt.year # 绘制散点图 回归线 plt.figure(figsize(10, 6)) sns.scatterplot(datadf, xage, yprice, alpha0.6, s30, color#1f77b4) sns.regplot(datadf, xage, yprice, scatterFalse, colorred, line_kws{linestyle: --}) plt.title(二手车价格 vs 使用年限散点图趋势线, fontsize14) plt.xlabel(车龄年, fontsize12) plt.ylabel(售价元, fontsize12) plt.grid(True, alpha0.3) plt.savefig(output/price_vs_age.png, dpi300, bbox_inchestight) plt.show()这段代码输出的图像会清晰显示车龄每增加 1 年平均售价下降约 1.8 万元回归线斜率且 3 年内车型价格衰减最快——这正是二手车评估师口中的“黄金折旧期”。图中每个点代表一辆真实车辆透明度alpha0.6避免重叠点遮挡尺寸s30保证打印清晰。关键参数说明bbox_inchestight自动裁剪白边dpi300满足毕业论文印刷要求。3.2 品牌-价格箱线图识别高溢价与高性价比阵营analysis/brand_price_boxplot.py使用 Seaborn 绘制品牌价格分布# 筛选销量前10品牌 top_brands df[brand].value_counts().head(10).index df_top df[df[brand].isin(top_brands)] plt.figure(figsize(12, 7)) ax sns.boxplot(datadf_top, xbrand, yprice, ordertop_brands) ax.set_xticklabels(ax.get_xticklabels(), rotation30) plt.title(销量TOP10品牌售价分布箱线图, fontsize14) plt.ylabel(售价元, fontsize12) plt.xlabel(汽车品牌, fontsize12) plt.tight_layout() plt.savefig(output/brand_price_boxplot.png, dpi300, bbox_inchestight)该图能直观回答答辩常问问题“为什么宝马均价比丰田高”——箱线图不仅显示中位数更暴露离散程度宝马的上四分位数Q3远高于丰田的上限whisker说明其高端车型拉高了整体区间而五菱宏光的箱体极窄且中位数低印证其“工具车”定位。ordertop_brands强制按销量排序避免品牌名随机排列影响解读。3.3 Plotly 交互式仪表盘用 30 行代码实现毕业答辩加分项dashboard/app.py基于 Flask Plotly 构建轻量 Web 仪表盘核心逻辑仅需初始化一个Dash实例并注册回调from dash import Dash, dcc, html, Input, Output, callback import plotly.express as px app Dash(__name__) server app.server # 供 Gunicorn 部署用 # 读取数据 df pd.read_sql_query(SELECT * FROM cars, conn) app.layout html.Div([ html.H1(二手车市场分析仪表盘, style{textAlign: center}), dcc.Dropdown( idbrand-filter, options[{label: b, value: b} for b in df[brand].unique()], value丰田, multiTrue ), dcc.Graph(idprice-hist) ]) callback( Output(price-hist, figure), Input(brand-filter, value) ) def update_histogram(selected_brands): filtered_df df[df[brand].isin([selected_brands] if isinstance(selected_brands, str) else selected_brands)] fig px.histogram(filtered_df, xprice, nbins30, titlef{selected_brands} 售价分布直方图) fig.update_xaxes(title_text售价元) fig.update_yaxes(title_text车辆数量) return fig运行python dashboard/app.py后访问http://127.0.0.1:8050即可交互筛选品牌、实时刷新直方图。答辩时演示此功能比静态 PNG 图片更具说服力——导师可亲手操作验证结论可靠性。nbins30控制分组粒度过少则丢失细节过多则噪声显著经测试 30 是平衡点。4. 数据库文件与文档说明如何用现成 DB 文件跳过爬虫环节直接做分析4.1cars.db文件结构验证与快速导入项目附带的data/cars.db是 SQLite3 格式无需安装数据库服务。验证其完整性只需两步# 1. 检查文件是否可读 file data/cars.db # 输出应含 SQLite 3.x database # 2. 查看表结构与数据量 sqlite3 data/cars.db PRAGMA table_info(cars); sqlite3 data/cars.db SELECT COUNT(*) FROM cars;若输出COUNT(*)为1247示例值说明数据库已预置千级样本足够支撑毕业设计全部分析。此时可跳过crawler/run_spider.py直接在analysis/目录下运行任意.py脚本——所有pd.read_sql_query()默认连接data/cars.db。4.2 文档说明文件README.md的实战价值拆解docs/README.md不是空泛介绍而是精准标注了每个模块的修改点爬虫 URL 配置crawler/config.py中BASE_URL https://www.guazi.com/bj/buy/—— 修改城市编码bj为sh上海、gz广州即可切换区域字段映射表明确写出HTML selector → 数据库字段对应关系如.js-price → price避免学生在BeautifulSoup中盲目试错答辩 PPT 结构建议第3页必放“数据采集流程图”第5页必须包含“价格-车龄回归分析结果”第7页展示“交互式仪表盘截图”——直击评审关注点。提示文档中requirements.txt已锁定pandas1.5.3、matplotlib3.7.1等版本避免因 pip 升级导致Seaborn绘图报错。若遇ModuleNotFoundError优先执行pip install -r requirements.txt而非pip install --upgrade。4.3 一键生成分析报告PDF 导出的 LaTeX 与 WeasyPrint 双路径report/generate_report.py提供两种导出方案LaTeX 路径推荐用于正式提交调用pandoc将 Markdown 报告转 PDF公式渲染精准页眉页脚规范WeasyPrint 路径零依赖纯 Python 库pip install weasyprint后直接运行生成带 CSS 样式的 PDF。关键代码段from weasyprint import HTML import markdown # 将 analysis_results.md 渲染为 HTML with open(analysis/analysis_results.md, r, encodingutf-8) as f: md_content f.read() html_content markdown.markdown(md_content, extensions[tables, fenced_code]) # 注入 CSS 样式 styled_html f html headstyle body {{ font-family: Microsoft YaHei; }} table {{ border-collapse: collapse; width: 100%; }} th, td {{ border: 1px solid #ccc; padding: 8px; text-align: left; }} /style/head body{html_content}/body /html HTML(stringstyled_html).write_pdf(output/analysis_report.pdf)该脚本生成的 PDF 包含标题页、数据概览表总车数、均价、最高/最低价、3 张核心图表散点图、箱线图、直方图、结论段落如“新能源车占比仅 7.2%但平均车龄比燃油车短 2.1 年”。所有文字使用微软雅黑表格带边框完全满足本科毕业论文格式要求。5. 运行排错与参数调优当爬虫卡在第 3 页、图表中文乱码、数据库写入失败时怎么办5.1 爬虫中断恢复基于 SQLite 的断点续爬机制项目在crawler/spider.py中实现了状态检查def get_last_crawled_page(): 查询数据库中最新抓取的页面序号 cursor.execute(SELECT MAX(page_num) FROM crawl_log WHERE statussuccess) result cursor.fetchone()[0] return result if result else 0 def log_crawl_status(page_num: int, status: str): 记录爬取状态支持断点续爬 cursor.execute(INSERT INTO crawl_log (page_num, status, timestamp) VALUES (?, ?, ?), (page_num, status, datetime.now())) conn.commit()若爬虫在第 3 页崩溃下次运行python crawler/run_spider.py会自动从第 4 页开始无需手动删库重来。crawl_log表结构已在db/init_db.py中定义包含page_num和status字段status值为success或failed便于后期统计成功率。5.2 中文乱码终极解决方案Matplotlib 字体配置三步法所有.py脚本开头强制设置字体import matplotlib matplotlib.rcParams[font.sans-serif] [SimHei, Arial Unicode MS, DejaVu Sans] matplotlib.rcParams[axes.unicode_minus] False # 解决负号 - 显示为方块的问题若仍乱码执行以下命令定位系统字体路径# Linux/macOS fc-list :langzh | grep -i simhei # WindowsPowerShell Get-ChildItem -Path $env:windir\Fonts -Filter *simhei* | Select-Object FullName将返回路径如C:\Windows\Fonts\simhei.ttf复制到matplotlib.font_manager.findSystemFonts()可识别位置或直接在代码中指定plt.rcParams[font.family] sans-serif plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[font.size] 125.3 数据库写入失败排查表现象可能原因快速验证命令解决方案sqlite3.IntegrityError: UNIQUE constraint failed: cars.source_url同一 URL 被重复提交sqlite3 data/cars.db SELECT source_url FROM cars WHERE source_url LIKE %guazi%;检查spider.py中extract_detail_url()是否去重逻辑失效OperationalError: database is locked多进程同时写库lsof -i :8050查占用端口改用单进程模式或在db/init_db.py中添加timeout20参数ValueError: could not convert string to floatprice字段含非数字字符sqlite3 data/cars.db SELECT price FROM cars WHERE price NOT GLOB [0-9.]*;在clean_price()中增加re.sub(r[^\d.], , text)预清洗注意所有 SQL 查询均使用?占位符防止注入如cursor.execute(SELECT * FROM cars WHERE brand?, (brand_name,))这是 SQLite 官方推荐的安全写法比字符串拼接更可靠。运行python analysis/price_age_analysis.py时若报KeyError: reg_date说明数据库中存在reg_date为空的记录。此时应先执行清洗DELETE FROM cars WHERE reg_date IS NULL OR reg_date ;再重新运行分析脚本——这比在 Python 中加try-except更高效因为 SQLite 的DELETE操作在千级数据量下耗时不足 0.01 秒。本文还有配套的精品资源点击获取
网站建设高端定制企业官网