ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python二手车数据闭环分析系统:爬虫+清洗+可视化全流程

Python二手车数据闭环分析系统:爬虫+清洗+可视化全流程 简介本资源是一套完整落地的二手车数据采集与分析毕业设计项目面向计算机、数据科学相关专业本科生及课程设计学习者解决从网页爬取、数据清洗到可视化呈现的全流程实践问题。压缩包共2000个文件主体为1745个Python源码含爬虫、ETL、绘图及Flask后端模块辅以112个头文件支持扩展编译、88个说明文本、13个JSON配置及11个PDF文档含需求分析、系统设计与答辩报告整体54.99MB结构清晰、模块解耦。已有658人学习下载项目经导师指导并获97分高分答辩评价可直接用于毕业设计、课程大作业或自学实战。用户将获得可一键运行的全栈代码、本地SQLite数据库文件、详细技术文档及典型二手车平台如瓜子、人人车的数据采集逻辑实现涵盖反爬绕过、动态渲染处理与多维度可视化图表生成。1. 这不是“爬完就扔”的毕业设计而是一套可复用的二手车数据闭环分析链路很多同学做毕业设计时把爬虫写完、存进 CSV 就算交差结果答辩被问“数据怎么清洗”“价格异常值怎么处理”“可视化图表能反映真实市场趋势吗”当场卡壳。这个基于 Python 的二手车爬虫数据可视化分析项目恰恰补上了这关键一环它不是单点工具包而是一条从网页抓取 → 结构化存储 → 清洗校验 → 特征工程 → 多维可视化 → 本地数据库持久化的完整闭环。项目使用 SQLite 作为嵌入式数据库非 MySQL 或 PostgreSQL所有表结构、索引、初始数据均预置在car_data.db中开箱即用可视化部分未依赖 Web 框架而是采用 Matplotlib Seaborn Plotly 离线渲染生成 HTML 报告和 PNG 图表双输出规避了部署服务器或配置前端的麻烦。适合计算机、信息管理、统计学等专业学生快速上手复现也适合作为数据分析入门者理解“真实业务数据流”的教学样本——你拿到的不是代码快照而是一个能跑通、能调试、能改参数、能换源站的轻量级分析系统。2. 为什么选 Requests BeautifulSoup 而非 Scrapy爬取逻辑与反爬绕过实操2.1 选型依据轻量、可控、易调试契合毕业设计场景Scrapy 功能强大但学习曲线陡峭且默认异步机制在本地单机运行时优势不明显而本项目面向的是典型二手车垂直平台如某瓜、某信等类站页面结构稳定、无强 JS 渲染、分页规则清晰。Requests BeautifulSoup 组合具备三大不可替代优势第一HTTP 请求细节完全暴露headers、cookies、timeout、session 复用便于观察响应状态码、重定向链、Referer 验证失败原因第二BeautifulSoup 解析 DOM 时支持多种 parserlxml / html.parser对 malformed HTML 容错性强避免因页面微小语法错误导致整个解析中断第三调试成本极低——可逐行 printsoup.find_all(div, class_price)查看原始节点无需启动 Scrapy shell 或配置 CrawlSpider 规则。项目中spider.py的核心循环仅 47 行却覆盖了翻页控制、URL 去重、请求延迟、状态码校验四层防护比“黑盒式”框架更利于答辩时讲清技术决策。2.2 关键爬取逻辑拆解动态 URL 构造与字段映射表驱动项目未硬编码目标网站域名而是通过config.py中的BASE_URL和SEARCH_PARAMS实现站点切换# config.py BASE_URL https://www.xxx.com SEARCH_PARAMS { city: shanghai, brand: toyota, year_min: 2015, price_max: 150000 }实际请求 URL 由url_builder.py动态拼接from urllib.parse import urlencode def build_list_url(base_url, params): query_str urlencode(params) return f{base_url}/list?{query_str} # 示例输出: https://www.xxx.com/list?cityshanghaibrandtoyotayear_min2015price_max150000提示urlencode()自动处理中文字符编码如城市名“北京”转为%E5%8C%97%E4%BA%AC避免手动调用urllib.parse.quote()出错。若目标站使用 POST 提交搜索表单需将urlencode()替换为dataparams并修改requests.post()调用方式。字段提取采用“选择器-映射表”双层设计在parser.py中定义FIELD_SELECTORS { title: (h2, {class: title}), price: (span, {class: price-num}), mileage: (li, {data-index: 2}), # 里程在第3个li标签 reg_date: (span, {class: date}), location: (div, {class: location}) } def extract_car_info(soup): data {} for field, (tag, attrs) in FIELD_SELECTORS.items(): elem soup.find(tag, attrs) data[field] elem.get_text(stripTrue) if elem else None return data2.2.1 为什么用字典映射而非硬编码 find()可维护性当网站改版时只需修改FIELD_SELECTORS字典中的 CSS 选择器无需改动提取逻辑容错性elem.get_text(stripTrue)对空元素返回None后续清洗阶段统一处理缺失值避免AttributeError扩展性新增字段如“排放标准”只需追加键值对不影响已有流程。2.3 反爬应对三板斧User-Agent 轮换、请求间隔、Session 复用项目内置user_agents.py提供 12 个主流浏览器 UA 字符串并在每次请求前随机选取import random USER_AGENTS [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36..., Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36... ] def get_random_headers(): return { User-Agent: random.choice(USER_AGENTS), Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en-US;q0.8,en;q0.7, Connection: keep-alive }spider.py中关键控制逻辑import time from requests import Session session Session() session.headers.update(get_random_headers()) for page in range(1, MAX_PAGES 1): url build_list_url(BASE_URL, {**SEARCH_PARAMS, page: page}) try: resp session.get(url, timeout10) resp.raise_for_status() # 抛出 4xx/5xx 异常 soup BeautifulSoup(resp.text, lxml) cars parse_page(soup) save_to_db(cars) # 直接写入 SQLite print(f✅ Page {page} crawled, {len(cars)} records) time.sleep(random.uniform(1.5, 3.0)) # 随机延时 1.5~3.0 秒 except Exception as e: print(f❌ Page {page} failed: {e}) continue注意Session()复用 TCP 连接减少握手开销time.sleep()使用random.uniform()避免固定间隔被识别为机器人resp.raise_for_status()是必须步骤否则 403/404 错误会被静默吞掉导致数据缺失却不报错。3. SQLite 数据库设计与清洗策略从原始爬虫记录到分析就绪表3.1 数据库结构解析三张核心表的职责划分项目附带的car_data.db包含以下表结构可通过sqlite3 car_data.db .schema验证表名字段类型说明raw_carsid, html_content, crawl_time, urlINTEGER, TEXT, DATETIME, TEXT原始 HTML 快照用于二次解析或审计cleaned_carsid, title, price, mileage, reg_date, location, brand, model, yearINTEGER, TEXT, REAL, REAL, DATE, TEXT, TEXT, TEXT, INTEGER主分析表所有字段已标准化crawl_logid, start_time, end_time, total_pages, success_count, error_countINTEGER, DATETIME, DATETIME, INTEGER, INTEGER, INTEGER爬取过程日志支持溯源提示raw_cars表的存在是本项目区别于“一次性脚本”的关键——它允许你在清洗逻辑变更后重新解析历史 HTML无需重复爬取极大提升迭代效率。3.2 清洗函数clean_data.py的核心实现清洗不是简单去空格而是针对二手车数据特性的多层校验import re from datetime import datetime def clean_price(text): 提取数字价格单位统一为万元 if not text: return None # 匹配 ¥12.5万、12.5万元、125000元、12.5w 等格式 match re.search(r(\d\.?\d*)[万wW]|[¥](\d\.?\d*), text) if match: num float(match.group(1) or match.group(2)) # 若原字符串含万则直接返回否则视为元转为万元 if 万 in text or w in text.lower(): return round(num, 2) else: return round(num / 10000, 2) return None def clean_mileage(text): 里程单位统一为万公里 if not text: return None match re.search(r(\d\.?\d*)[万公里], text) if match: return float(match.group(1)) # 处理 3.5万公里 → 3.5120000公里 → 12.0 num_match re.search(r(\d\.?\d*), text) if num_match: num float(num_match.group(1)) return round(num / 10000, 1) if num 1000 else round(num, 1) return None def clean_reg_date(text): 年份提取支持 2020年、2020.03、2020-03 等格式 if not text: return None year_match re.search(r(20\d{2}), text) if year_match: return int(year_match.group(1)) return None3.2.1 为什么清洗函数要独立成模块可测试性每个函数可单独单元测试如assert clean_price(¥125000) 12.5可复用性清洗逻辑可迁移到其他车型数据源可审计性清洗前后数据差异可追溯答辩时能展示“原始值→清洗后值”对照表。3.3 数据库操作封装避免 SQL 注入与事务安全db_utils.py使用参数化查询防止注入并确保插入原子性import sqlite3 def insert_cars(conn, cars): 批量插入清洗后数据启用事务 cursor conn.cursor() try: cursor.execute(BEGIN TRANSACTION) for car in cars: cursor.execute( INSERT INTO cleaned_cars (title, price, mileage, reg_date, location, brand, model, year) VALUES (?, ?, ?, ?, ?, ?, ?, ?) , ( car[title], car[price], car[mileage], car[reg_date], car[location], car[brand], car[model], car[year] )) conn.commit() print(f✅ {len(cars)} records inserted) except Exception as e: conn.rollback() print(f❌ Insert failed: {e}) # 使用示例 conn sqlite3.connect(car_data.db) insert_cars(conn, cleaned_data) conn.close()注意?占位符是 SQLite 参数化查询的唯一安全方式绝不能用 f-string 或.format()拼接 SQL否则存在注入风险。BEGIN TRANSACTIONcommit()/rollback()保证批量写入要么全成功、要么全失败。4. 多维度可视化实战用 Matplotlib/Seaborn/Plotly 生成可交付报告4.1 价格分布分析直方图 KDE 密度曲线双视图viz_price_distribution.py生成price_distribution.html核心代码如下import matplotlib.pyplot as plt import seaborn as sns import pandas as pd from plotly.subplots import make_subplots import plotly.graph_objects as go # 加载数据 df pd.read_sql(SELECT price FROM cleaned_cars WHERE price IS NOT NULL, sqlite3.connect(car_data.db)) # Matplotlib Seaborn 生成静态图 plt.figure(figsize(10, 6)) sns.histplot(df[price], bins30, kdeTrue, colorsteelblue, alpha0.7) plt.title(二手车价格分布万元, fontsize14) plt.xlabel(价格万元) plt.ylabel(频数) plt.grid(True, alpha0.3) plt.savefig(output/price_hist.png, dpi300, bbox_inchestight) # Plotly 生成交互式 HTML fig make_subplots(rows1, cols1) fig.add_trace(go.Histogram(xdf[price], name价格分布, nbinsx30)) fig.add_trace(go.Scatter(xdf[price].sort_values(), ysns.kdeplot(df[price], warn_singularFalse).get_lines()[0].get_ydata(), modelines, name密度曲线)) fig.update_layout(title二手车价格分布交互式, xaxis_title价格万元, yaxis_title频数 / 密度) fig.write_html(output/price_distribution.html)4.1.1 为什么同时输出 PNG 和 HTMLPNG 用于论文插图印刷质量高、加载快HTML 支持缩放、悬停查看精确数值、导出为 PNG/SVG适合答辩现场演示sns.kdeplot()的warn_singularFalse防止单值数据报错增强鲁棒性。4.2 车龄-价格散点图识别异常低价车与高价老车viz_age_vs_price.py使用 Seabornscatterplot并添加回归线df[age] datetime.now().year - df[reg_date] plt.figure(figsize(10, 6)) sns.scatterplot(datadf, xage, yprice, huebrand, sizemileage, sizes(20, 200), alpha0.6, palettetab10) sns.regplot(datadf, xage, yprice, scatterFalse, colorred, line_kws{linestyle:--}) plt.title(车龄 vs 价格按品牌着色里程大小编码, fontsize14) plt.xlabel(车龄年) plt.ylabel(价格万元) plt.legend(bbox_to_anchor(1.05, 1), locupper left) plt.savefig(output/age_price_scatter.png, dpi300, bbox_inchestight)提示sizemileage将里程映射为点大小形成三维信息编码palettetab10使用 10 色区分常见品牌丰田、本田、大众等避免颜色混淆bbox_to_anchor解决图例遮挡问题。4.3 地域价格热力图用 Plotly Choropleth 展示城市均价项目预置city_mapping.json将文本城市名映射为标准行政区划代码如“上海”→“310000”再调用 Plotly 地图import json import plotly.express as px with open(data/city_mapping.json) as f: city_code_map json.load(f) # 计算各城市均价 city_avg df.groupby(location)[price].mean().reset_index(nameavg_price) city_avg[code] city_avg[location].map(city_code_map) fig px.choropleth(city_avg, locationscode, coloravg_price, hover_namelocation, color_continuous_scaleViridis, scopeasia, title各城市二手车平均价格万元) fig.write_html(output/city_price_heatmap.html)4.3.1 如何解决中文城市名匹配失败city_mapping.json手动维护常见别名如“沪”→“上海”“京”→“北京”使用fuzzywuzzy库做模糊匹配项目未内置但可在requirements.txt添加fuzzywuzzy0.18.0若地图显示空白检查code列是否为字符串类型Plotly 要求locations为 str。5. 一键运行与环境配置VS Code Python 3.8 的最小可行方案5.1 环境搭建避开 pip install 全局污染的推荐做法项目根目录下提供environment.ymlConda和requirements.txtpip双方案优先推荐 Conda# environment.yml name: car-analysis channels: - conda-forge dependencies: - python3.8 - pip - pip: - beautifulsoup44.12.2 - matplotlib3.7.1 - seaborn0.12.2 - plotly5.18.0 - pandas1.5.3 - numpy1.24.3执行命令# 创建隔离环境 conda env create -f environment.yml conda activate car-analysis # 验证安装 python -c import sqlite3, pandas, plotly; print(✅ All libs loaded)提示Conda 环境自动解决matplotlib与plotly的依赖冲突如kiwisolver版本比 pip 更稳定python3.8是因部分二手车网站仍使用较旧 TLS 协议Python 3.9 默认禁用某些加密套件。5.2 五步运行流程从解压到生成报告步骤命令说明1. 解压项目unzip 基于Python的二手车爬虫数据可视化分析项目源码文档说明数据库文件毕业设计.zip得到car_project/目录2. 进入目录cd car_project确保config.py、spider.py等文件在当前路径3. 安装依赖conda env create -f environment.yml conda activate car-analysis或pip install -r requirements.txt4. 运行爬虫python spider.py默认爬取 5 页耗时约 2~5 分钟5. 生成报告python main_viz.py输出output/下全部图表与 HTML5.2.1 如果爬虫卡在某页不动怎么办检查config.py中BASE_URL是否可访问浏览器打开确认查看output/log.txt最后一行错误信息如ConnectionError表示网络不通Timeout表示目标站响应慢临时降低MAX_PAGES至 1运行python spider.py并在parser.py中print(soup.prettify()[:500])查看 HTML 结构是否变化。5.3 毕业设计答辩高频问题预演与答案要点问题回答要点答辩时说“为什么用 SQLite 而不用 MySQL”“SQLite 零配置、单文件、无需服务端符合毕业设计‘本地可运行’要求所有表结构已在schema.sql中定义答辩时可现场sqlite3 car_data.db .tables展示。”“数据有重复吗怎么去重”“爬虫层通过url字段唯一索引防重复插入清洗层在cleaned_cars表中对titlereg_dateprice组合去重SQL 语句为DELETE FROM cleaned_cars WHERE rowid NOT IN (SELECT MIN(rowid) FROM cleaned_cars GROUP BY title, reg_date, price)。”“可视化图表怎么嵌入论文”“output/下的 PNG 图分辨率 300dpi直接插入 Word 即可HTML 报告可用浏览器打开答辩时投屏演示交互功能截图保存为高清图备用。”“如果我想换爬某瓜网改哪里”“只需修改config.py的BASE_URL和SEARCH_PARAMS再调整parser.py中FIELD_SELECTORS的 CSS 选择器——比如某瓜的标题是h3 classtit就把title: (h2, {...})改为title: (h3, {class: tit})。”注意所有回答必须基于项目实际代码切勿编造未实现的功能。答辩时打开 VS Code实时演示修改config.py后重新运行main_viz.py生成新图表是最有力的佐证。本文还有配套的精品资源点击获取
返回列表