
简介基于PythonFlaskScrapyEcharts构建的数据可视化系统整合了新浪微博与Twitter财报数据采集、清洗、存储和展示全流程。源码面向毕业设计、课程设计等场景适合具备一定Python基础的计算机相关专业学生作为项目起点也便于在此基础上扩展功能。压缩包内共2000个文件其中Python脚本1815个另有头文件、JSON配置、HTML页面、JS及CSS等资源既包含核心爬虫与后端逻辑也包含前端展示所需文件整体大小约25.61MB目录结构较清晰。资源附有部署文档和全部数据资料项目代码已通过测试运行可帮助使用者理解爬虫框架、Flask Web开发及Echarts可视化的结合方式降低从零搭建的难度。该项目获导师指导认可评审95分已有74人学习下载适合用于项目演示、课设作业或作为进阶练手素材。1. 从数据采集到可视化这套系统到底在解决什么问题很多人拿到这种“基于 Python Flask Scrapy ECharts 的数据可视化系统”项目包第一反应是先让 ECharts 出图但真正决定项目质量的是数据从采集到展示整条链路是否闭合。Scrapy 负责把网页数据结构化落库Flask 把库里的数据做成 JSON 接口ECharts 消费 JSON 渲染折线图、饼图和地图任何一环字段对不齐图表上就会出现“提示正常但图形空白”的怪问题。下面按 采集 → 接口 → 图表 → 部署 的顺序把每层的核心代码、参数和坑讲清楚适合正在做课程设计或想快速搭内部看板的读者。2. 用 Scrapy 把页面数据抓全、去重、能续爬爬虫这层最容易被低估。很多人以为项目包里的数据文件是现成的直接导入就行但无论是维护自己抓取的数据还是把手里的 CSV、SQL 资料标准化都要先定义一张“干净的表”。抓取不是下载页面而是从 HTML 中提取结构化字段并处理缺值、重复值和格式不统一。2.1 初始化 Scrapy 工程与第一个爬虫创建工程scrapy startproject data_collector cd data_collector scrapy genspider booklist books.toscrape.comstartproject生成完整的 Scrapy 目录spiders、items、pipelines、middlewares 和 settings。genspider会在 spiders 目录里生成一个模板爬虫直接填规则就能跑。一个只抓目录页的爬虫长这样import scrapy from data_collector.items import BookItem class BooklistSpider(scrapy.Spider): name booklist allowed_domains [books.toscrape.com] start_urls [https://books.toscrape.com/catalogue/page-1.html] def parse(self, response): for article in response.css(article.product_pod): item BookItem() item[title] article.css(h3 a::attr(title)).get() item[price] article.css(p.price_color::text).get() item[rating] article.css(p.star-rating::attr(class)).get() yield item next_page response.css(li.next a::attr(href)).get() if next_page: yield response.follow(next_page, self.parse)这里要注意response.css(...).get()返回单个值抓不到时是 None如果写成.getall()则返回列表。CSS 选择器里::text取标签内文本::attr(属性名)取属性值。response.follow会自动拼接绝对 URL手动做urljoin反而容易漏掉基础路径。分页链接是相对路径时这个 API 直接解决拼接问题。实际调试时先用scrapy crawl booklist -o books.csv跑单页看输出确认每列字段都对了再放开到全站。CSV 导出是最快的核对方式比直接连数据库查要直观得多。2.2 Item Pipeline清洗、去重后写入 MySQLitems.py 里的字段定义要和数据库表字段一一对应命名统一用下划线风格避免在 pipeline 里再做一次转换。清洗管道如下from scrapy.exceptions import DropItem class CleanPipeline: def process_item(self, item, spider): price item.get(price) if price: item[price] float(price.replace(£, ).strip()) if not item.get(title): raise DropItem(fmissing title in {item}) return itemprocess_item返回 item 后交给管道链中的下一个处理类DropItem会把残缺记录直接丢弃不让脏数据进库。清洗和入库建议拆成两个 pipeline 类注册在 settings.py 的ITEM_PIPELINES字典里按数字大小决定执行顺序。去重是另一层独立工作Scrapy 默认按请求指纹去重但“不同 URL 上同一个商品”这种业务级重复是去不掉的需要在入库前按唯一键查一次库再决定 insert 还是 update。如果项目包带的是 CSV 而不是数据库文件先用head -n 20 data.csv确认列名和分隔符再用LOAD DATA LOCAL INFILE导入。连接 MySQL 时要确认charset参数写成utf8mb4而不是utf8否则中文和 emoji 写入直接报错。项目里的 SQL 资料大多按utf8mb4导出导入前先执行SET NAMES utf8mb4;可以避免大量乱码。2.3 必调参数下载延迟、并发、User-Agentsettings.py 里有几个参数决定爬虫对目标站点的压力也是线上跑一段时间后最常回调的地方参数推荐值作用DOWNLOAD_DELAY1.0 - 3.0相邻请求间隔秒数调小容易引发限流CONCURRENT_REQUESTS8 - 16并发请求数不是越大越快ROBOTSTXT_OBEYTrue遵守目标站 robots 协议USER_AGENT浏览器 UA填充真实 UA 降低被拒概率RETRY_TIMES3请求失败重试次数DOWNLOAD_DELAY设置为 1.0 意味着每个请求之间至少间隔 1 秒CONCURRENT_REQUESTS默认 16对中小站点压到 8 更稳。ROBOTSTXT_OBEY在测试期打开能避免误抓但不是所有站点都适合视情况调整。要支持中断后续跑启动时加-s JOBDIRjobs/booklist同一个任务恢复会从上次断点继续。注意同一个 JOBDIR 不能同时被两个进程使用否则会锁冲突。遇到页面由 JS 动态生成、数据藏在 iframe 里的场景常规方案是启用 scrapy-playwright 中间件对指定页面走 headless Chromium 渲染。这会显著拖慢抓取速度只对确实需要渲染的 URL 开启。判断方法也简单先 curl 看 HTML 里有没有目标数据没有再去考虑动态渲染。提示抓取数据仅用于学习或授权范围内的场景运行爬虫前先看目标站点的 robots.txt并控制抓取频率。3. 用 Flask 把数据库变成前端可消费的 JSON 接口Scrapy 完成的是写库Flask 完成的是读库。把 SQL 查询和 HTML 模板直接混在路由函数里项目一开始能跑页面一多接口路径和数据格式会迅速失控。这一层的关键是接口只做三件事接收参数、查询聚合、按约定返回 JSON。3.1 最小 Flask 应用与蓝图拆分一个能启动的最小 Flask 应用from flask import Flask, jsonify app Flask(__name__) app.config.from_pyfile(config.py) app.route(/health) def health(): return jsonify({code: 0, msg: ok}) if __name__ __main__: app.run(host0.0.0.0, port5001, debugFalse)app.config.from_pyfile把数据库口令这类变量放到 config.py代码里不出现敏感信息。host0.0.0.0表示局域网可访问方便调试只在本机用可以去掉。debugTrue会开启调试器生产环境有安全风险部署时必须关闭。路由多起来之后用蓝图拆模块。常见结构是app/api/trend.py放趋势接口app/api/category.py放分类占比接口每个文件里创建Blueprint最后在主应用里注册from flask import Blueprint trend_bp Blueprint(trend, __name__) trend_bp.route(/trend) def trend(): ...主应用里执行app.register_blueprint(trend_bp, url_prefix/api)。这样爬虫的写库逻辑和 Web 查询逻辑分离后面替换数据源不用动路由。3.2 数据库连接封装与聚合查询连接封装放到独立的 db.py 中import pymysql from flask import current_app def query(sql, argsNone): conn pymysql.connect( hostcurrent_app.config[MYSQL_HOST], portcurrent_app.config[MYSQL_PORT], usercurrent_app.config[MYSQL_USER], passwordcurrent_app.config[MYSQL_PASSWORD], databasecurrent_app.config[MYSQL_DB], charsetutf8mb4, cursorclasspymysql.cursors.DictCursor, ) try: with conn.cursor() as cur: cur.execute(sql, args) return cur.fetchall() finally: conn.close()重点是charsetutf8mb4保证中文不乱码DictCursor让每行记录以一个 dict 返回后续jsonify时字段名自动对上。每个请求都新建连接低并发没问题页面变多之后会出现连接堆积简便的处理方式是手动连接池或按请求创建、请求结束关闭。聚合计算要下推到 SQL而不是把全表拉到 Python 里循环统计。下面这条 SQL 返回最近 30 天按天分组的数量SELECT DATE(created_at) AS d, COUNT(*) AS cnt FROM books WHERE created_at DATE_SUB(NOW(), INTERVAL 30 DAY) GROUP BY d ORDER BY dDATE_SUB(NOW(), INTERVAL 30 DAY)生成对比时间点GROUP BY d按天分组一天一行正好对应折线图的 x 轴和 y 轴。3.3 接口返回格式约定与调试先约定格式再写页面前后端才不会来回改。通用包装是{code: 0, msg: ok, data: {...}}业务出错时 code 非 0msg 放错误信息。折线图接口的 data 结构{ code: 0, msg: ok, data: { dates: [2025-01-01, 2025-01-02], counts: [12, 34] } }饼图接口的 data 结构{ code: 0, msg: ok, data: [ {name: Python, value: 120}, {name: Java, value: 80} ] }接口路径、方法、返回结构固定后前后端可以并行开发前端先用 mock 数据渲染后端跑通后替换成真实接口。用 curl 验证本地 Flask 服务器是否连通curl http://127.0.0.1:5001/api/trend | python -m json.tool如果查询结果为空不要返回空字符串而是返回空数组例如{dates: [], counts: []}前端渲染时至少不会报错。4. 用 ECharts 渲染折线图、饼图与地图数据可视化大屏这类效果核心不在炫技而在配置项和接口字段的对齐。ECharts 配置项很多常用的无非折线图、饼图、柱状图和地图把这几类的 data 结构吃透其他图表都能触类旁通。4.1 Flask 模板结构与 ECharts 引入方式Flask 项目里HTML 模板放在 templates 目录ECharts 的 JS 文件放在 static 目录。模板关键部分div idtrend stylewidth:100%;height:360px;/div script src{{ url_for(static, filenamejs/echarts.min.js) }}/script用url_for生成静态资源路径部署时改目录结构不用动模板。ECharts 文件下载到本地 static 目录即可避免页面每次加载都请求外部 CDN。初始化图表前先确认容器有明确高度stylewidth:100%在父容器没有宽度时会失效图表渲染出来是 0 宽。4.2 折线图、饼图和地图的配置参数折线图最常见的配置const chart echarts.init(document.getElementById(trend)); fetch(/api/trend) .then((res) res.json()) .then((res) { const data res.data; chart.setOption({ title: { text: 每日新增趋势 }, tooltip: { trigger: axis }, xAxis: { type: category, data: data.dates, axisLabel: { rotate: 30 } }, yAxis: { type: value }, series: [{ name: 新增, type: line, data: data.counts, smooth: true }] }); });tooltip.trigger: axis让提示框按 X 轴整列显示axisLabel.rotate: 30解决日期标签过多时 X 轴刻度被挤到重叠的问题。series.data必须是数值数组如果接口把数字当字符串返回折线会显示成不连续的点排查时先console.log(typeof data.counts[0])。把 ajax 请求统一放在页面级脚本里多个图表共用一份 fetch 结果比每个 setOption 各自请求接口更容易维护。饼图的 legend 是最容易踩的坑series: [{ name: 分类占比, type: pie, radius: [30%, 70%], data: res.data }]radius传两个值表示环形饼图。饼图默认的图例数据来自 series.data 的 name 字段如果手动设置了legend.data又与接口数据不一致图例会显示文字而扇区只有灰色色块。字段名也要严格用name和value接口返回label或count都会渲染不出来。地图要先注册 geoJSONfetch(/static/map/china.json) .then((r) r.json()) .then((geo) { echarts.registerMap(china, geo); chart.setOption({ series: [{ type: map, map: china, data: res.data }] }); });不执行registerMap直接用map: china控制台会报Map china not exists。地名要和 geoJSON 里的 name 完全一致接口里写“北京市”geoJSON 里是“北京”地图上就空一块。想要立体效果可以引入 echarts-gl 扩展把 series 的 type 换成 map3D用viewControl.distance控制视角远近。图表类型series.type数据格式高频坑折线图linexAxis.data 数组 series.data 数组数字被当字符串饼图pie[{name, value}]legend 手动配置不一致地图map[{name, value}] 且先注册 geoJSON地名对不上4.3 数据加载时机、loading 与 resizeajax 拉数据有个典型时序问题页面先渲染图表再请求数据图表会先显示空坐标轴几秒后才填充。常见做法是初始化时chart.showLoading()收到数据后chart.hideLoading()用户能明确看到加载状态。容器尺寸变化时图表不会自动重绘需要监听window.addEventListener(resize, () chart.resize());这一步经常被漏掉。浏览器缩放后图表要么撑破容器要么留白加上这行就解决。数据点多时可以先用后端聚合而不是把几千条原始记录全塞给前端。折线图 Y 轴太密多半是把每分钟数据都展示了按天聚合后点位自然减少X 轴刻度密集则用axisLabel.interval控制显示间隔。5. 部署文档落地systemd 守护 Flask 服务与接口自检部署文档看着长真正决定能不能跑起来的只有三件事Python 环境隔离、服务常驻、日志可见。先建 virtualenv 虚拟环境再装依赖requirements 先不锁定版本本机跑通后pip freeze requirements.txt再锁定。5.1 systemd 配置文件Flask 开发服务器承担不了常驻任务常见做法是写一个 systemd 单元让服务崩溃后自动重启[Unit] DescriptionData Visualization Service Afternetwork.target [Service] Userwww WorkingDirectory/opt/dataviz EnvironmentPATH/opt/dataviz/venv/bin ExecStart/opt/dataviz/venv/bin/python run.py Restartalways RestartSec3 [Install] WantedBymulti-user.target启用命令sudo cp dataviz.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now datavizRestartalways让进程退出后自动拉起RestartSec3防止崩溃后疯狂重启。Environment指向 venv 里的 python避免误用系统 Python 的依赖。5.2 curl 自检与定时更新部署后先不急着开浏览器用 curl 验证接口curl http://127.0.0.1:5001/health curl http://127.0.0.1:5001/api/trend | python -m json.toolJSON 输出正常再检查页面。爬虫更新数据可以交给 crontab30 2 * * * cd /opt/dataviz venv/bin/scrapy crawl booklist /var/log/scrapy.log 21日志用追加模式而不是覆盖排错时才能看到完整链路。接口返回 500 时先查journalctl -u dataviz -n 50 --no-pager再查看/var/log/scrapy.log绝大多数问题都集中在数据库字段缺失和编码上日志里会明确写出是字段名拼写错误还是连接超时。本文还有配套的精品资源点击获取