
简介一份面向大数据与爬虫场景的汽车信息采集展示系统设计资源适合正在做课程设计、毕业设计或希望熟悉全栈技术整合的学习者。系统基于Scrapy实现汽车数据抓取Django负责后端服务与接口MySQL用于数据存储前端通过Vue3与Element-Plus搭建页面ECharts完成可视化图表Pinia管理状态整体技术链路清晰。压缩包共39个文件内含20个Python脚本、16张运行截图、2张二维码图片和1份说明文档整体大小约38.62MB。Python脚本覆盖网络请求、爬虫框架、后端开发、数据库操作、数据处理与分析等模块既可逐段学习也能组合成演示项目截图展示系统首页、数据列表、统计图表等界面帮助理解页面与数据的联动方式说明文档提供源码或使用指引便于快速部署。资源已有416人学习可作为汽车信息类项目设计模板也能为大数据可视化方向提供界面构思与代码参考。1. 汽车数据爬取与可视化系统从Scrapy到ECharts的完整链路打开任意一个汽车垂直网站你能看到车型、价格、上市时间但很难快速回答“15万以下有哪些品牌值得关注”“近半年新上市的车哪个价位最集中”。手工翻页收集太慢复制到表格里更不现实。这套基于Python的汽车信息爬取与可视化分析系统就是为解决这类“将公开网页转化为结构化分析面板”而设计的。它用Scrapy抓取车市公开数据用Django做查询接口用MySQL存储再用Vue3配合ECharts绘制品牌销量、价格区间等可视化图表形成了从数据采集到图表展示的完整闭环。适合准备毕业设计、想练习爬虫与前后端协作或者需要搭建内部竞品数据看板的开发者参考。2. Scrapy爬虫抓取汽车信息的项目结构与关键代码2.1 为什么选Scrapy而不是requests模块在车市数据场景里目标站点通常包含几十个品牌入口和上千条车型详情页。如果用requests手动循环抓取要自己处理线程池、重试、页面解析和数据清洗代码量很大维护起来也麻烦。Scrapy把整个采集链路拆成Spider、Item、Pipeline和Middleware四个关注点异步调度并发量可控新增页面只需要扩展Spider里的解析函数。项目附带的LearnPython目录中正好有python_requests.py和python_coroutine.py两个示例对照着看能更直观地理解同步请求与异步爬虫在吞吐量上的差别。2.2 定义汽车数据模型在Scrapy工程中items.py用来声明要抓取哪些字段。本系统需要的核心字段包括品牌、车系、具体车型、裸车价、动力信息以及上市时间。字段表如下字段名类型说明brandstring品牌如大众、丰田seriesstring车系如迈腾modelstring具体车型名称pricefloat裸车价单位万元enginestring排量或动力类型market_timedate上市时间对应的CarItem定义把后续写MySQL时需要用的字段先固定下来# carspider/items.py import scrapy class CarItem(scrapy.Item): brand scrapy.Field() # 品牌用于销量统计 series scrapy.Field() # 车系 model scrapy.Field() # 具体型号 price scrapy.Field() # 价格万元 engine scrapy.Field() # 动力信息 market_time scrapy.Field() # 上市时间在这段Item里每个Field都只是占位符并不做类型校验。后续Pipeline中插入MySQL时再根据字段名把值逐一取出。如果还要抓新能源续航里程可以在Item中继续增加Field同时记得修改建表SQL和插入语句。2.3 编写汽车列表页Spider抓取流程是先在start_requests中请求品牌入口页解析出每个品牌的链接再依次进入车系列表和详情页。CarSpider的代码会用到CssSelector定位页面元素这里以示例域名展示# carspider/spiders/car.py import scrapy from carspider.items import CarItem class CarSpider(scrapy.Spider): name car start_urls [https://example-auto.com/brands] def parse(self, response): # 每个品牌入口 for brand_url in response.css(div.brand-list a::attr(href)).getall(): yield scrapy.Request( urlresponse.urljoin(brand_url), callbackself.parse_series ) def parse_series(self, response): # 车系列表 for item_url in response.css(div.series-card a::attr(href)).getall(): yield scrapy.Request( urlresponse.urljoin(item_url), callbackself.parse_detail ) def parse_detail(self, response): item CarItem() item[brand] response.css(div.breadcrumb a::text).get() item[series] response.css(h1.title::text).get().strip() item[model] response.css(div.model-name::text).get() item[price] self.parse_price( response.css(span.price-num::text).get() ) item[engine] response.css(td.engine::text).get() item[market_time] response.css(td.date::text).get() yield item staticmethod def parse_price(raw): # 清洗12.98万这类文本 if not raw: return None return float(raw.replace(万, ).strip())代码里用CssSelector定位了品牌、车系和详情页的链接每一级解析函数通过callback参数串联起来。parse_price单独抽取是为了方便做边界测试真实站点里经常出现“暂无报价”或“万”字号被HTML实体替换的情况清洗时还要做空值判断与字符替换。start_urls中的域名是示例实际运行时要替换成目标站点的入口页面并确认页面选择器与真实DOM结构一致。2.4 settings.py中的下载策略与合规要求Scrapy默认的并发参数并不适合汽车垂直网站建议修改DOWNLOAD_DELAY、CONCURRENT_REQUESTS和默认请求头让抓取节奏更贴近正常浏览行为。配置如下# carspider/settings.py BOT_NAME carspider DOWNLOAD_DELAY 1.5 CONCURRENT_REQUESTS 16 DEFAULT_REQUEST_HEADERS { Accept: text/html,application/xhtmlxml, User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36, Accept-Language: zh-CN,zh;q0.9, }DOWNLOAD_DELAY控制相邻请求间隔1.5秒是一个兼顾效率与友好性的初始值。CONCURRENT_REQUESTS表示同时处理的请求数量可以根据目标站点的响应速度在8到32之间调整。User-Agent写成了Chrome 124的完整UA避免默认的Scrapy UA被识别也可以单独写一个middleware从列表里随机切换UA。还需要把ROBOTSTXT_OBEY设为False但前提是只抓取网站允许公开访问的列表页如果目标站点在robots协议中明确禁止抓取应遵守规则。2.5 用Pipeline写入MySQL抓取到的Item会进入Pipeline这里用pymysql连接MySQL把数据插入到car_info表中。关键是使用参数化SQL避免拼接字符串带来的注入风险# carspider/pipelines.py import pymysql class CarPipeline: def __init__(self, db_conf): # 连接参数从settings中传入 self.conn pymysql.connect( hostdb_conf[host], userdb_conf[user], passworddb_conf[password], databasedb_conf[database], charsetutf8mb4, ) self.cursor self.conn.cursor() classmethod def from_crawler(cls, crawler): return cls( db_confcrawler.settings.getdict(MYSQL_CONF) ) def process_item(self, item, spider): sql ( INSERT INTO car_info (brand, series, model, price, engine, market_time) VALUES (%s, %s, %s, %s, %s, %s) ) values ( item.get(brand), item.get(series), item.get(model), item.get(price), item.get(engine), item.get(market_time), ) self.cursor.execute(sql, values) self.conn.commit() return item def close_spider(self, spider): self.cursor.close() self.conn.close()from_crawler方法会在爬虫启动时从settings中读取MYSQL_CONF字典这段配置可以放在settings.py最底部。process_item是Pipeline的执行入口每收到一个Item就执行一次插入。注意这里用了逐条commit对课程设计或小规模数据集足够。如果单次抓取超过上万条建议先用list缓存在close_spider里用executemany批量写入能明显减少网络与磁盘I/O。执行scrapy crawl car之后打开MySQL客户端执行SELECT COUNT(*) FROM car_info就能验证数据是否落库。3. Django对车市数据的接口化与查询优化3.1 建立Car模型与MySQL连接Django项目命名为auto_panel在cars应用中定义Car模型。模型字段与Scrapy的Item尽量保持一致但价格字段改用DecimalField避免浮点数比较时出现误差# auto_panel/cars/models.py from django.db import models class Car(models.Model): brand models.CharField(max_length32, db_indexTrue) series models.CharField(max_length64) model models.CharField(max_length128) price models.DecimalField(max_digits6, decimal_places2, nullTrue) engine models.CharField(max_length32) market_time models.DateField(nullTrue) class Meta: db_table car_infobrand字段加了db_indexTrue是为了后续按品牌聚合统计时走索引。为了兼容Scrapy已经写入的car_info表Meta中显式指定了db_table。如果不想手工建表可以让Django代替执行建表操作只要保证数据库名和用户权限配置正确。在settings.py中配置MySQL连接DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: auto_db, USER: root, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, OPTIONS: {charset: utf8mb4}, } }这里编码使用utf8mb4与Pipeline写入时的charset保持一致否则中文会乱码。用户名和密码换成本地MySQL的实际值并确保Django用户拥有auto_db库的增删改查权限。3.2 用DRF提供品牌统计与排序接口直接写JsonResponse也能工作但接口需要支持按品牌筛选、按价格排序还要输出聚合统计引入djangorestframework更高效。安装后先定义序列化器把模型字段暴露给前端# auto_panel/cars/serializers.py from rest_framework import serializers from .models import Car class CarSerializer(serializers.ModelSerializer): class Meta: model Car fields [id, brand, series, model, price, engine, market_time]视图集里配置排序字段并添加一个品牌统计的action# auto_panel/cars/views.py from rest_framework import viewsets, filters from rest_framework.decorators import action from rest_framework.response import Response from django.db.models import Count, Avg from .models import Car from .serializers import CarSerializer class CarViewSet(viewsets.ModelViewSet): queryset Car.objects.all() serializer_class CarSerializer filter_backends [filters.OrderingFilter] ordering_fields [price, market_time] action(detailFalse, methods[get]) def brand_stats(self, request): data ( Car.objects.values(brand) .annotate(countCount(id), avg_priceAvg(price)) .order_by(-count) ) return Response(list(data))brand_stats使用Django ORM的annotate完成分组聚合一次SQL就能返回每个品牌的车型数量和平均价格。视图集默认提供列表、详情、创建、更新和删除接口所以GET /api/cars/?ordering-price直接就能按价格倒序查询。如果想让前端对车型名称做模糊搜索可以再增加filters.SearchFilter并设置search_fields [model]。3.3 路由注册与跨域配置在auto_panel/urls.py中使用DRF的DefaultRouter注册视图集from rest_framework.routers import DefaultRouter from cars.views import CarViewSet router DefaultRouter() router.register(cars, CarViewSet, basenamecars) urlpatterns [ path(api/, include(router.urls)), ]前端开发服务器运行在8080端口后端在8000端口浏览器会执行CORS跨域检查。使用django-cors-headers并配置允许来源INSTALLED_APPS [ corsheaders, ] MIDDLEWARE [ corsheaders.middleware.CorsMiddleware, ] CORS_ALLOWED_ORIGINS [ http://localhost:8080, ]CORS配置只影响浏览器环境用curl或postman调试接口时不触发该机制但前端部署后要有实际域名时这里还需要同步修改。通常生产环境会把CORS_ALLOWED_ORIGINS改成正式域名的列表而不是使用*通配符避免安全隐患。4. Vue3与ECharts绘制汽车品牌销量与价格区间4.1 前端工程依赖设计用Vite创建Vue3项目核心依赖包括axios、element-plus、pinia和echarts。各依赖的用途整理如下npm包用途备注vue3前端框架配合vue-router 4.xvite开发服务器5.x版本要求Node 18以上axiosHTTP请求统一处理API调用element-plus表单、容器组件按需引入优化打包体积echarts图表渲染5.x版本API更稳定pinia状态共享替代Vuex写法更轻量element-plus负责页面布局和筛选组件pinia保存用户选择的品牌和价格区间echarts负责把后端返回的数据渲染为图表。如果vite启动报错先检查Node和npm版本是否满足vite 5的安装要求。4.2 用Pinia维护全局筛选条件不同图表需要共享品牌、价格上限等筛选条件把这些状态放到Pinia中组件之间不需要层层绑定事件。car.js定义如下// src/stores/car.js import { defineStore } from pinia export const useCarStore defineStore(car, { state: () ({ brand: , maxPrice: 50, }), actions: { setBrand(name) { this.brand name }, setMaxPrice(value) { this.maxPrice value }, }, })Pinia的state是响应式的组件中可以直接读取carStore.brand。动作函数只做同步状态更新发起HTTP请求的逻辑放在组件或独立的api模块中避免store变成服务层。这样品牌切换后多个图表组件都能通过watch响应同一状态变化。4.3 用ECharts渲染品牌销量柱状图BrandChart.vue组件负责展示品牌销量先通过axios请求/api/cars/brand_stats/拿到数据再设置ECharts的optiontemplate div refchartEl styleheight: 320px/div /template script setup import { onMounted, ref, watch } from vue import * as echarts from echarts import axios from axios import { useCarStore } from /stores/car const chartEl ref(null) const carStore useCarStore() let chart null async function loadAndRender() { const res await axios.get(http://localhost:8000/api/cars/brand_stats/) const data res.data chart.setOption({ xAxis: { type: category, data: data.map(d d.brand) }, yAxis: { type: value }, series: [{ type: bar, data: data.map(d d.count), itemStyle: { color: #409EFF }, }], }) } onMounted(() { chart echarts.init(chartEl.value) loadAndRender() window.addEventListener(resize, () chart.resize()) }) watch(() carStore.brand, () { // 品牌变化后重新拉取统计 loadAndRender() }) /script组件挂载后先初始化图表并请求一次。后端返回的是包含brand和count对象的数组代码中分别映射到x轴类别和y轴数值。当品牌超过20个时x轴文字会互相遮挡需要设置axisLabel: { interval: 0, rotate: 30 }把标签旋转30度。图表容器需要显式高度否则ECharts初始化时宽度和高度都是0导致图表区域空白。销毁组件前用chart.dispose()释放实例避免内存泄漏。4.4 价格区间饼图与数据格式转换价格区间图能反映车市整体价位结构实现时先请求全部数据再在前端按阈值分组。核心逻辑是function groupByPrice(data) { const buckets [ { name: 10万以下, value: 0 }, { name: 10-20万, value: 0 }, { name: 20-30万, value: 0 }, { name: 30万以上, value: 0 }, ] data.forEach(d { const price Number(d.price) if (price 10) buckets[0].value else if (price 20) buckets[1].value else if (price 30) buckets[2].value else buckets[3].value }) return buckets }后端不一定要做价格分组前端拿到明细数据后自己分桶这样调整区间阈值不需要重新写SQL。ECharts的饼图series需要{ name, value }结构groupByPrice返回的数组正好可以直接传给data字段。每个区间还可以配置不同颜色让占比差异更直观。5. 项目运行验证与定时爬取进阶5.1 按依赖顺序启动系统整个系统的启动顺序不能乱。先确保MySQL服务开启再启动Scrapy抓数据然后启动Django后端最后运行前端。常见做法是# 1. 启动MySQL服务 systemctl start mysqld # 2. 抓取汽车数据 cd carspider scrapy crawl car # 3. 启动Django后端 cd ../auto_panel python manage.py runserver 0.0.0.0:8000 # 4. 启动前端开发服务 cd ../web npm run dev如果MySQL连接失败先检查settings里的用户名、密码和数据库名再用命令行工具直接连接测试。Django启动后可以用curl验证接口是否返回JSONcurl http://127.0.0.1:8000/api/cars/?ordering-priceformatjson5.2 前端跨域与白屏排错打开浏览器F12开发者工具如果Network面板看到Access-Control-Allow-Origin缺失说明django-cors-headers配置没生效检查MIDDLEWARE中是否把CorsMiddleware放在其他中间件之前。ECharts图表不显示时先看Console有没有Cannot read properties of undefined错误这通常是接口返回空数组代码里.map执行失败。在loadAndRender开头加一个if (!res.data.length) return可以避免空数据导致渲染异常。5.3 用schedule实现每日定时爬取车市价格和车型列表会更新可以写一个Django管理命令定时调用Scrapy爬虫。项目附带的LearnPython目录中有python_schedule.py示例参考它完成管理命令# auto_panel/cars/management/commands/run_spider.py import schedule import time import subprocess from django.core.management.base import BaseCommand class Command(BaseCommand): help 定时执行汽车爬虫 def handle(self, *args, **options): def job(): subprocess.run([scrapy, crawl, car], cwd/opt/carspider) schedule.every().day.at(01:00).do(job) while True: schedule.run_pending() time.sleep(60)执行python manage.py run_spider后进程会常驻每天凌晨1点执行一次抓取任务。subprocess调用scrapy命令时cwd参数必须指向包含settings.py的目录否则Scrapy会报找不到配置。如果服务器不允许常驻进程也可以在crontab中配置缩短字段本质上等价。5.4 用项目附带示例代码定位框架细节源码包中的LearnPython目录还提供了一些Python基础示例它们不是项目直接依赖但在排查问题时很有参考价值。python_sqlalchemy.py展示了一种ORM访问数据库的写法与Django ORM的思路相似可以帮助理解会话和事务管理。python_requests.py对比了直接用requests抓取和通过Scrapy抓取的特点当你需要调试单页内容时可以临时用requests快速验证选择器。遇到Pipeline中MySQL链接超时、序列化器嵌套返回异常这类问题检查基础示例中的连接参数写法往往比直接搜索报错信息更快定位到原因。本文还有配套的精品资源点击获取