资讯动态

Python二手车爬虫数据分析可视化系统:从采集到大屏的完整实现

发布时间:2026/9/23 16:50:08 来源:尧图企业网站定制
简介本资源为基于Python的二手车爬虫数据分析可视化系统毕业设计完整项目包面向计算机、软件工程、人工智能、通信工程等专业的在校学生与教师可用于毕业设计、课程设计、作业提交或项目初期立项演示。项目已通过测试运行功能完整个人评分98分适合小白进阶学习也便于在现有代码基础上二次修改扩展。压缩包共约2000个文件整体53.99MB以1745个py源码文件为核心辅以112个h头文件、88个txt说明、15个html页面、13个json配置、11个pdf文档及少量js、css、c等文件覆盖爬虫采集、数据清洗、分析建模与可视化展示等模块。目前已有536人学习下载。读者可获得完整源码、数据库与使用文档理解二手车数据从抓取到图表呈现的全流程实现思路并参考目录结构与排错方法快速完成自己的毕设或课设任务。1. 二手车数据从哪来一套能跑通的采集链路长什么样二手车价格数据是典型的「脏、散、变」三高数据同一辆车在车商页面写「2019款 改款 2.0L」在个人卖家那里可能只写「19年 2.0」品牌名还夹着全角空格和 emoji。想用 Python 做二手车爬虫数据分析可视化系统第一件要解决的事不是画图而是让采集链路稳定产出结构化记录。这套系统适合两类人一是做毕业设计、需要完整闭环采集→存储→分析→可视化的同学二是想练手 requests SQLAlchemy Pandas ECharts 这条主流技术栈的入门者。它不追求分布式高并发追求的是单机可复现、字段可解释、图表能讲故事。下面按我实际搭过的顺序把每个环节的参数和坑讲清楚。2. 采集层requests 抓列表页与详情页的最小闭环2.1 先定字段再写代码别反过来很多人上来就requests.get抓到 HTML 再想解析什么结果字段反复改、数据库反复重建。我一般先定一张字段表采集和入库都围着它转。二手车列表页通常能给到标题、价格、指导价、上牌时间、行驶里程、排放标准、城市、详情链接详情页再补变速箱、排量、过户次数、车源编号。字段定完解析函数只做一件事——把 HTML 节点映射到这些 key。字段来源类型说明title列表页str原始标题保留不清洗price列表页float单位万元去「万」字guide_price列表页float新车指导价可能为空reg_date列表页str上牌时间统一成 YYYY-MMmileage列表页float单位万公里city列表页str城市名用于分组detail_url列表页str详情页入口gearbox详情页str变速箱类型displacement详情页float排量 L提示字段表一旦确定数据库建表语句和 Pandas 的列名都从它派生后面改字段只改一处。2.2 列表页请求请求头、翻页与限速列表页是分页接口或分页 HTML核心是构造 URL 和翻页终止条件。下面这段是我常用的骨架重点看headers、params和sleep三处。import requests import time import random 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, Accept-Language: zh-CN,zh;q0.9, } def fetch_list(page: int, city: str bj) - str: url https://example-car.com/usedcar/list params {city: city, page: page, size: 40} resp requests.get(url, headersHEADERS, paramsparams, timeout10) resp.raise_for_status() # 随机停顿降低被限流的概率 time.sleep(random.uniform(1.2, 2.8)) return resp.text逻辑说明params把城市和页码交给服务端避免手拼字符串出错raise_for_status让 4xx/5xx 直接抛异常而不是把错误页当正常页解析time.sleep用随机区间而不是固定值固定间隔反而更容易被识别为脚本。参数上size不要贪大40 左右一页既减少请求数又不至于触发风控timeout必须设否则网络卡住会挂死整个循环。翻页终止条件别只看「有没有下一页按钮」更稳的是判断本页解析出的记录数是否小于size小于就说明到底了。这个判断比 DOM 结构可靠因为页面改版时按钮 class 最先变。2.3 详情页解析用 lxml 还是 BeautifulSoup解析库我一般用 lxml XPath速度快、表达式紧凑BeautifulSoup 更适合结构混乱、需要容错的页面。二手车详情页字段分散在多个 div 里XPath 写起来直观。下面是一个解析函数注意每个字段都做了空值兜底。from lxml import etree def parse_detail(html: str) - dict: tree etree.HTML(html) def text(xp): r tree.xpath(xp) return r[0].strip() if r else None return { gearbox: text(//div[classinfo-item][1]/span/text()), displacement: text(//div[classinfo-item][2]/span/text()), transfer_count: text(//div[classinfo-item][3]/span/text()), }逻辑说明text内部函数统一处理「节点不存在」的情况返回 None 而不是抛异常这样单条记录缺字段不会中断整批采集。参数上XPath 里的[1]、[2]是位置索引页面结构一变就会错位所以采集完要抽样人工核对几条别全量跑完才发现字段串位。2.4 用 SQLAlchemy 把数据落库别存 CSV 就完事热搜里常出现sqlalchemy储存爬虫数据这不是没道理CSV 没法做去重、没法增量更新、并发写还容易坏。SQLAlchemy 的 ORM 让「采集→入库」变成一次session.add。建表时给detail_url加唯一索引重复采集时用merge或先查后插避免数据翻倍。from sqlalchemy import create_engine, Column, Integer, String, Float from sqlalchemy.orm import declarative_base, sessionmaker Base declarative_base() class UsedCar(Base): __tablename__ used_car id Column(Integer, primary_keyTrue, autoincrementTrue) title Column(String(255)) price Column(Float) mileage Column(Float) city Column(String(32)) detail_url Column(String(255), uniqueTrue) gearbox Column(String(32)) engine create_engine(mysqlpymysql://user:pwdlocalhost:3306/car?charsetutf8mb4) Base.metadata.create_all(engine) Session sessionmaker(bindengine)逻辑说明uniqueTrue是去重的关键配合入库前的查询或INSERT ... ON DUPLICATE KEY UPDATE实现增量。charsetutf8mb4必须写否则标题里的 emoji 和生僻字会报错。参数上连接串里的库名、账号密码按本地环境改别硬编码进提交的代码里。3. 清洗与分析Pandas 把脏标题变成可分组字段3.1 从数据库读到 DataFrame 的正确姿势分析层第一步是把表读进来。用pd.read_sql直接读别先导 CSV 再读多一道工序多一处出错。import pandas as pd from sqlalchemy import create_engine engine create_engine(mysqlpymysql://user:pwdlocalhost:3306/car?charsetutf8mb4) df pd.read_sql(SELECT * FROM used_car, engine) print(df.shape, df.isnull().sum())逻辑说明先看shape和缺失值分布再决定清洗策略。如果price缺失超过三成说明列表页解析规则有问题要回去修采集而不是在分析层硬填。参数上read_sql的 SQL 尽量只选需要的列全表读在数据量大时会吃内存。3.2 价格、里程、上牌时间的标准化价格字段常见「12.8万」「12.80万」「面议」里程常见「3.5万公里」「3.5万公里(表显)」。用正则统一抽取数字抽不到的置为 NaN 并单独统计。import numpy as np def to_float(x): if pd.isna(x): return np.nan import re m re.search(r(\d\.?\d*), str(x)) return float(m.group(1)) if m else np.nan df[price] df[price].apply(to_float) df[mileage] df[mileage].apply(to_float) df[reg_year] df[reg_date].str.extract(r(\d{4})).astype(float)逻辑说明to_float只取第一个数字能覆盖绝大多数写法reg_year单独抽年份方便后面按年分组算折旧。参数上正则\d\.?\d*不匹配负数二手车价格和里程本来也不该为负出现负数说明解析串位要排查。3.3 用分箱和分组把连续值变成可解释维度可视化要的是「能对比的类别」不是一堆散点。把价格分箱成「10万以下 / 10-20万 / 20-30万 / 30万以上」把里程分箱成「3万内 / 3-8万 / 8万以上」再按城市、年份做透视。bins [0, 10, 20, 30, 1000] labels [10万以下, 10-20万, 20-30万, 30万以上] df[price_band] pd.cut(df[price], binsbins, labelslabels) pivot df.pivot_table(indexcity, columnsprice_band, valuesid, aggfunccount, fill_value0) print(pivot.head())逻辑说明pd.cut把连续价格切成有序类别pivot_table直接产出「城市 × 价格区间」的计数矩阵这个矩阵就是后面柱状图和热力图的直接输入。参数上bins的边界要按你采集到的实际价格分布调别照搬否则某一档可能全是 0。3.4 折旧分析的常见口径与陷阱二手车分析绕不开折旧同款车第一年掉多少、第三年掉多少。常见做法是用「当年指导价」和「当前售价」算保值率但这里有个大坑——指导价字段经常缺失而且不同配置指导价差很多。我一般只对「指导价非空且样本量大于 30」的车型做保值率样本太少的车型算出来没有统计意义。另一个陷阱是把上牌年份当购车年份实际两者可能差几个月做年度对比时影响不大做月度分析就会失真。4. 可视化从 ECharts 到可视化大屏的落地选择4.1 图表选型什么数据配什么图可视化不是把数据全画出来而是每张图回答一个问题。价格分布用直方图或箱线图城市对比用横向柱状图年份与价格关系用折线或散点品牌占比用饼图或矩形树图。热搜里的可视化大屏适合答辩展示但大屏的核心是布局和联动不是图表数量堆十几张图反而看不清重点。分析目标推荐图表数据输入价格整体分布直方图price 单列各城市车源量横向柱状图city 计数年份与均价关系折线图reg_year 分组均值品牌价格区间箱线图brand price价格带占比饼图/环形图price_band 计数4.2 用 Pyecharts 生成可交互图表Pyecharts 把 ECharts 封装成 Python 对象适合在脚本里直接出 HTML。下面生成一张城市车源量柱状图。from pyecharts.charts import Bar from pyecharts import options as opts city_count df[city].value_counts().head(10) bar ( Bar() .add_xaxis(city_count.index.tolist()) .add_yaxis(车源量, city_count.values.tolist()) .set_global_opts( title_optsopts.TitleOpts(title城市车源量 TOP10), xaxis_optsopts.AxisOpts(name城市), yaxis_optsopts.AxisOpts(name数量), ) ) bar.render(city_count.html)逻辑说明value_counts().head(10)先排序再截断避免城市太多导致 X 轴挤成一团set_global_opts统一设置标题和轴名比在每个系列里重复设置清晰。参数上head(10)的 10 按展示宽度调大屏可以放到 15网页嵌入建议 8 以内。4.3 大屏布局的三个实用约束做可视化大屏时我踩过的坑集中在三点一是分辨率按 1920×1080 设计别用响应式百分比否则投影仪上全乱二是配色深色底配高饱和系列色浅色底配低饱和混用会显得脏三是数据刷新答辩演示用静态 HTML 就够真要接实时数据再上 Flask 接口别为了「实时」两个字把架构搞复杂。热搜里的python爬虫可视化界面如果指桌面 GUITkinter 或 PyQt 都能做但 Web 大屏在展示效果和开发效率上更划算。5. 避坑与排查采集和分析阶段最容易翻车的地方5.1 现象跑几十页后返回空列表或验证页原因请求频率过高触发风控或User-Agent长期不变被标记。解决把sleep区间拉大到 2-5 秒准备多个User-Agent轮换并在解析前判断返回内容是否包含关键节点不包含就跳过并记录页码而不是硬解析出空记录。5.2 现象数据库里同一条车源出现多条原因detail_url没加唯一约束或入库用add而非先查后插。解决给detail_url建唯一索引入库前用session.query(UsedCar).filter_by(detail_urlurl).first()判断存在则更新价格等易变字段不存在才插入。5.3 现象价格字段大量为 NaN原因页面价格写法超出正则覆盖范围比如「12万8」「1X万」这类非标准写法。解决先抽样 50 条把价格原文打印出来统计所有写法再补正则分支补完仍抽不到的单独存一张异常表人工看几条就能定位规律。5.4 现象Pandas 分组结果里出现空组或顺序错乱原因pd.cut的bins边界没覆盖全部数据超出边界的值变成 NaN 被丢弃或分组列里有前后空格导致同一城市被拆成两组。解决分箱前先df[price].describe()看极值把bins上界设得足够大分组前对city等文本列统一str.strip()。5.5 现象Pyecharts 生成的 HTML 打开空白原因图表数据里含 NaN 或非 JSON 可序列化类型渲染时静默失败。解决出图前对输入列做dropna()和类型转换value_counts()的结果本身就是干净的但透视表要检查有没有 NaN必要时fillna(0)。6. 让这套系统更耐用的两个进阶习惯第一个习惯是给采集加「断点续跑」。做法很简单在数据库里记一张crawl_log表字段是city、page、status、updated_at每采完一页写一条。重跑时先查statusdone的最大页码从下一页继续。这个习惯在毕设答辩前夜尤其救命——网络一断不用从头再来。第二个习惯是把清洗规则写成独立函数并配单元测试比如to_float(12.8万) 12.8、to_float(面议) is np.nan规则一改跑一遍测试就知道有没有误伤。我自己的教训是第一版系统把所有逻辑塞在一个 800 行的脚本里改一个正则要通读全文后来拆成fetch.py、parse.py、clean.py、viz.py四个文件每个文件不超过 200 行排查问题时定位速度快了不止一倍。验证方法上我一般随机抽 20 条数据库记录手工打开对应详情页核对字段准确率低于 95% 就先修解析再谈分析。这套系统值不值得做取决于你能不能把「采集稳定」和「字段可解释」这两件事做扎实图表只是最后一步。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价