Python二手车爬虫数据分析可视化系统毕业设计实战指南

发布时间:2026/10/29 22:10:07
Python二手车爬虫数据分析可视化系统毕业设计实战指南 简介本资源为基于Python的二手车爬虫数据分析可视化系统毕业设计完整项目包面向计算机、软件工程、人工智能、通信工程等专业的在校学生与教师可用于毕业设计、课程设计、作业提交或项目初期立项演示。项目已通过测试运行个人评分98分适合具备一定Python基础、希望快速搭建数据采集与分析可视化流程的学习者参考或二次开发。压缩包共约2000个文件以1745个py源码文件为核心辅以112个h头文件、88个txt说明、15个html页面、13个json配置、11个pdf文档及少量js、css、c文件整体约53.99MB涵盖爬虫、数据处理、可视化与依赖库等模块。目前已有536人学习下载。读者可获得完整源码、数据库与使用文档理解二手车数据从抓取、清洗到图表展示的实现思路并在此基础上修改功能或迁移到其他数据场景。1. 二手车数据从哪来一个毕业设计选题的落地拆解二手车价格预测、车况分析、市场行情可视化这三个词放在一起基本就是「基于 Python 的二手车爬虫数据分析可视化系统」这个毕业设计选题的全部野心。我见过太多同学卡在第一步数据从哪来要么手动编几十条假数据糊弄过去要么爬了两天发现字段对不上、价格全是「面议」。这个选题真正值钱的地方不是画几张 ECharts 图而是把「采集 → 清洗 → 入库 → 分析 → 可视化」这条链路完整跑通并且每一步都有可复现的代码和可解释的参数。它适合两类人一是需要交出一个能演示、能答辩、代码能讲清楚的毕业设计二是想用一个小而完整的项目练手 Python 爬虫和数据分析的入门者。下面我按自己带学生做这类项目的实际路径把每个环节拆开讲。2. 采集层用 requests parsel 抓二手车列表页2.1 目标站点结构与请求参数拆解二手车平台如懂车帝二手车、瓜子二手车、二手车之家的列表页通常采用「URL 参数 分页」的结构。以常见的列表页为例核心参数一般包括城市编码、价格区间、车龄区间、分页页码。不要一上来就写代码先用浏览器开发者工具F12的 Network 面板筛选 XHR 或 Doc 请求找到返回 HTML 或 JSON 的那一条把它的 Request URL 和 Query String Parameters 完整复制下来。这一步决定了你后面能不能稳定翻页。我一般会先手动改几个参数值观察返回内容的变化把页码从 1 改成 2看列表是否更新把价格区间改成 5-10 万看结果是否收窄。确认参数生效后再把这些参数整理成一个字典作为请求的 params。注意很多平台对分页有上限比如只返回前 50 页超过后要么返回空要么跳回第一页这个边界要在代码里做判断。2.2 最小可运行爬虫代码与字段映射下面这段代码是我常用的最小骨架用 requests 发请求parsel 做解析。字段映射部分需要你根据实际页面结构调整 XPath 或 CSS 选择器。import requests from parsel import Selector import time import random # 请求头至少带 User-Agent有 Referer 更好 HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Referer: https://www.example.com/, } # 列表页基础 URL 和参数 BASE_URL https://www.example.com/usedcar/list PARAMS { city: 110100, # 城市编码北京为例 price: 5-10, # 价格区间单位万元 page: 1, # 页码从 1 开始 } def fetch_page(page): 抓取单页列表返回 HTML 文本 params PARAMS.copy() params[page] page try: resp requests.get(BASE_URL, headersHEADERS, paramsparams, timeout10) resp.raise_for_status() resp.encoding resp.apparent_encoding # 防止中文乱码 return resp.text except requests.RequestException as e: print(f第 {page} 页请求失败: {e}) return None def parse_list(html): 解析列表页提取车辆卡片信息 sel Selector(texthtml) items [] # 以下选择器需根据实际页面调整 for card in sel.css(.car-card): item { title: card.css(.car-title::text).get(default).strip(), price: card.css(.car-price::text).get(default).strip(), reg_date: card.css(.car-reg-date::text).get(default).strip(), mileage: card.css(.car-mileage::text).get(default).strip(), city: card.css(.car-city::text).get(default).strip(), detail_url: card.css(a::attr(href)).get(default), } items.append(item) return items if __name__ __main__: for p in range(1, 6): # 先抓 5 页测试 html fetch_page(p) if not html: continue rows parse_list(html) print(f第 {p} 页抓到 {len(rows)} 条) for r in rows: print(r) time.sleep(random.uniform(1.5, 3.5)) # 随机延时降低被封风险逻辑说明fetch_page负责发请求并处理编码parse_list用 CSS 选择器提取字段。参数方面timeout10防止卡死apparent_encoding能自动识别 GBK 或 UTF-8避免中文变问号。time.sleep的随机区间建议 1.5 到 3.5 秒太短容易触发反爬太长效率低。字段映射时如果页面是 JSON 接口直接把resp.json()拿到的字典按层级取值即可比解析 HTML 更稳。2.3 翻页策略与增量抓取翻页不要用while True无限循环而是先确定最大页数。常见做法是在列表页底部找到「下一页」按钮如果它的 class 里包含disabled说明已到最后一页。或者观察返回的车辆数量如果某页返回 0 条就停止。增量抓取则是在数据库里对detail_url或车辆 ID 建唯一索引每次插入前先查重避免重复跑时数据翻倍。我一般会把已抓取的页码记录到一个本地文件或 Redis 里下次启动时从断点继续。对于毕业设计数据量控制在 2000 到 5000 条就足够做分析和可视化了没必要追求几十万条反而增加清洗难度。3. 清洗与入库把「面议」「准新车」变成可计算字段3.1 字段标准化价格、里程、车龄的转换规则爬下来的原始数据里价格可能是「8.68万」「面议」「新车指导价 12.99 万」里程可能是「3.2万公里」「2.8万公里」车龄需要从「2019年3月」推算。这些字段不处理后面做不了任何统计。我通常写一个清洗函数用正则提取数字再统一单位。import re from datetime import datetime def clean_price(raw): 把价格字符串转成万元浮点数面议返回 None if not raw or 面议 in raw: return None # 匹配 8.68万、8.68 万、8.68万元 m re.search(r(\d\.?\d*), raw) if m: return float(m.group(1)) return None def clean_mileage(raw): 把里程转成万公里浮点数 if not raw: return None m re.search(r(\d\.?\d*), raw) if m: return float(m.group(1)) return None def clean_reg_date(raw): 把注册日期转成车龄年当前年份减去注册年份 if not raw: return None m re.search(r(\d{4}), raw) if m: year int(m.group(1)) return datetime.now().year - year return None逻辑说明clean_price用正则抓第一个数字兼容「8.68万」和「8.68 万元」clean_mileage同理clean_reg_date只取年份算车龄忽略月份对毕业设计足够。参数上如果价格单位是「元」而不是「万」需要在正则后除以 10000。清洗后的数据建议先存成 CSV 看一眼确认没有异常值比如价格 0.1 万、车龄 50 年再入库。3.2 用 SQLite 建表与批量插入毕业设计用 SQLite 最省事不需要装 MySQL 服务一个文件就能带走。建表时把清洗后的字段设为 REAL 或 INTEGER原始字段保留 TEXT 以便追溯。import sqlite3 def init_db(db_pathusedcar.db): conn sqlite3.connect(db_path) cur conn.cursor() cur.execute( CREATE TABLE IF NOT EXISTS car ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, price REAL, mileage REAL, car_age INTEGER, city TEXT, detail_url TEXT UNIQUE, raw_price TEXT, raw_mileage TEXT, raw_reg_date TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ) conn.commit() return conn def insert_cars(conn, rows): 批量插入detail_url 重复则忽略 cur conn.cursor() for r in rows: cur.execute( INSERT OR IGNORE INTO car (title, price, mileage, car_age, city, detail_url, raw_price, raw_mileage, raw_reg_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) , ( r.get(title), clean_price(r.get(price)), clean_mileage(r.get(mileage)), clean_reg_date(r.get(reg_date)), r.get(city), r.get(detail_url), r.get(price), r.get(mileage), r.get(reg_date) )) conn.commit()逻辑说明detail_url UNIQUE配合INSERT OR IGNORE实现去重重复抓取不会产生脏数据。raw_开头的字段保留原始文本方便答辩时展示清洗前后对比。批量插入时不要每条都 commit攒 100 条提交一次速度会快很多。3.3 缺失值与异常值处理清洗后先跑几条 SQL 看看分布SELECT COUNT(*), AVG(price), MIN(price), MAX(price) FROM car;。如果 MIN(price) 是 0.1MAX 是 999基本可以判定是异常值。常见处理方式价格低于 0.5 万或高于 200 万的直接标记为异常分析时排除里程为 0 的可能是新车保留但单独标注车龄超过 30 年的可能是经典车样本极少可以剔除。缺失值方面价格缺失占比超过 30% 时这个字段就不适合做核心分析只能作为辅助。4. 分析层用 pandas 做价格分布与影响因素4.1 读库到 DataFrame 与基础统计pandas 读 SQLite 只需要一行然后就能做分组、透视、相关性分析。毕业设计里最常做的三张图价格分布直方图、不同城市均价柱状图、车龄与价格散点图。import pandas as pd import sqlite3 conn sqlite3.connect(usedcar.db) df pd.read_sql(SELECT * FROM car WHERE price IS NOT NULL, conn) # 基础统计 print(df[price].describe()) print(df.groupby(city)[price].mean().sort_values(ascendingFalse).head(10)) # 车龄分箱 df[age_bin] pd.cut(df[car_age], bins[0, 3, 6, 10, 20], labels[3年内, 3-6年, 6-10年, 10年以上]) print(df.groupby(age_bin)[price].agg([count, mean]))逻辑说明WHERE price IS NOT NULL排除面议车避免均值失真。groupby后接agg可以同时看数量和均价判断某个车龄段的样本是否足够。如果某个城市只有 3 条数据它的均价没有代表性可视化时应该过滤掉样本量小于 30 的城市。4.2 价格影响因素的相关性分析用df[[price, mileage, car_age]].corr()看皮尔逊相关系数。通常车龄和价格负相关车越老越便宜里程和价格也负相关但两者之间可能高度共线。毕业设计里不需要做复杂的回归模型用散点图加趋势线就能说明问题。如果要做线性回归用sklearn.linear_model.LinearRegression拟合price ~ mileage car_age看 R² 和系数符号是否符合常识。注意相关性不等于因果答辩时如果老师问「为什么里程影响价格」要能说出「里程高意味着磨损大、剩余寿命短」这类业务解释而不是只念系数。5. 可视化与避坑ECharts 大屏和五个血泪教训5.1 用 Flask ECharts 搭一个可交互的展示页后端用 Flask 提供 JSON 接口前端用 ECharts 渲染。最小结构一个app.py一个templates/index.html。接口返回聚合后的数据比如各城市均价、价格区间分布。from flask import Flask, jsonify, render_template import sqlite3 import pandas as pd app Flask(__name__) def query_df(sql): conn sqlite3.connect(usedcar.db) df pd.read_sql(sql, conn) conn.close() return df app.route(/) def index(): return render_template(index.html) app.route(/api/city_price) def city_price(): df query_df( SELECT city, AVG(price) AS avg_price, COUNT(*) AS cnt FROM car WHERE price IS NOT NULL GROUP BY city HAVING cnt 30 ORDER BY avg_price DESC LIMIT 15 ) return jsonify(df.to_dict(orientrecords)) if __name__ __main__: app.run(debugTrue)前端在index.html里用fetch(/api/city_price)拿数据传给 ECharts 的setOption。图表类型建议柱状图看城市均价饼图看价格区间占比折线图看车龄与均价关系。不要堆太多图三到四张能讲清楚就行。5.2 避坑五个真实翻车记录现象一爬了 200 条后全部返回同一页内容。原因分页参数名写错或者平台用 POST 请求而你在用 GET。解决回到 Network 面板确认请求方法和参数名用requests.post重试。现象二价格字段全是「面议」清洗后 DataFrame 为空。原因目标城市或价格区间筛选太窄或者列表页默认按「最新发布」排序前几页都是面议车。解决放宽价格区间或换一个筛选条件如「个人车源」先保证有足够多的有效价格样本。现象三中文乱码标题变成「测试」。原因resp.encoding没设置requests 默认用 ISO-8859-1。解决加resp.encoding resp.apparent_encoding或者手动指定resp.encoding utf-8。现象四SQLite 插入速度极慢几千条要跑几分钟。原因每条INSERT后都commit。解决改成批量插入每 100 到 500 条commit一次或者用executemany。现象五ECharts 图表空白控制台报 CORS 错误。原因前端直接请求了外部接口或者 Flask 没开跨域。解决数据统一走自己的 Flask 接口不要在前端直接请求第三方如果必须跨域用flask-cors加响应头。6. 从能跑到能讲答辩演示与代码组织的几个技巧答辩时老师最常问的三个问题数据怎么来的、清洗规则是什么、可视化结论有没有业务意义。我的习惯是准备一个README.md把爬虫入口、数据库文件、分析脚本、Flask 启动命令按顺序写清楚再配一张数据流转图用文字描述即可不要画复杂架构。演示时先跑python crawler.py抓 2 页再跑python clean.py入库最后python app.py打开页面整个流程控制在 3 分钟内。代码组织上不要把全部逻辑塞进一个文件。我一般拆成crawler.py采集、cleaner.py清洗、db.py建表和插入、analysis.pypandas 分析、app.pyFlask 接口。每个文件只做一件事答辩时老师翻代码也能快速定位。另外把requirements.txt写好pip install -r requirements.txt能一键装依赖避免换电脑后跑不起来。最后一个技巧在数据库里留一张crawl_log表记录每次抓取的页码、时间、条数。答辩时如果老师问「你抓了多少数据」直接SELECT SUM(cnt) FROM crawl_log就能回答比临时数 CSV 行数靠谱得多。这个习惯我从第一个项目保持到现在希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询