CSDN数据大屏可视化【开源】
项目简介
本次基于版本3 开源
版本3开源地址:https://github.com/nangongchengfeng/CsdnBlogBoard.git
版本1开源地址:https://github.com/nangongchengfeng/CSDash.git
这是一个基于 Python 的 CSDN 博客数据可视化看板项目,通过爬虫采集 CSDN 博客数据,并以现代化的可视化界面展示博主的各项数据指标。该项目采用前后端分离架构,集成了数据采集、数据存储、API 服务和数据可视化等多个功能模块。
版本一
版本二
版本三
技术栈
后端技术
- Python Flask: 作为 Web 框架,提供 RESTful API 服务
- SQLAlchemy: ORM 框架,用于数据库操作
- BeautifulSoup4: 网页解析,用于爬虫数据提取
- Requests: HTTP 请求库,用于数据爬取
前端技术
- ECharts: 强大的数据可视化图表库
- Axios: HTTP 客户端,用于前后端数据交互
- 现代 CSS: Flexbox 布局、CSS 变量、响应式设计
核心功能模块
1. 数据采集模块
数据采集模块通过spider.py
实现,是项目的核心组件之一。该模块采用了多种技术和策略来确保数据采集的准确性、稳定性和实时性。
1.1 核心技术栈
- Requests: 处理 HTTP 请求,支持自定义请求头和超时设置
- BeautifulSoup4: 使用 lxml 解析器进行高效的 HTML 解析
- 正则表达式(re): 用于精确提取数字和特定格式的数据
- SQLAlchemy: 实现数据持久化和 ORM 映射
- 随机延时策略: 使用
random.uniform()
避免频繁请求
1.2 数据采集功能
1.2.1 博主基础信息采集
-
采集内容:
- 用户名和头像
- 文章数量统计
- 粉丝数和访问量
- 点赞和评论数
- 等级和积分信息
- 博主排名数据
-
技术实现:
# 示例:用户信息提取 user_info = soup.find('div', class_='user-info d-flex flex-column profile-intro-name-box') author_name = user_info.find('a').get_text(strip=True)
1.2.2 文章分类信息采集
-
采集内容:
- 专栏名称和链接
- 专栏文章数量
- 订阅人数统计
- 阅读量和收藏数
- 专栏唯一标识
-
技术实现:
# 示例:分类信息提取 spans = soup.find_all('a', attrs={'class': 'special-column-name'}) for span in spans: href = span.get('href') blog_column = span.text.strip()
1.3 错误处理机制
-
多级异常处理
- 请求异常捕获
- 数据解析异常处理
- 数据库操作异常处理
-
重试机制
max_retries = 3 retry_count = 0 while retry_count < max_retries: try: # 请求逻辑 except Exception: retry_count += 1 time.sleep(5) # 失败后等待
1.4 数据更新策略
-
增量更新机制
- 检查数据是否存在
- 根据情况执行更新或插入
- 保持数据时效性
-
数据一致性保证
with app.app_context(): existing_info = Info.query.filter_by(author_name=author_name).first() if existing_info: # 更新现有记录 else: # 插入新记录
1.5 性能优化策略
-
请求优化
- 自定义请求头
- 连接超时设置
- 随机延时控制
-
解析优化
- 使用 lxml 解析器提升性能
- 精确的选择器定位
- 数据预处理和清洗
-
存储优化
- 批量数据处理
- 事务管理
- 会话复用
1.6 反爬虫策略应对
-
请求头模拟
headers = { 'User-Agent': 'Mozilla/5.0 (MSIE 10.0; Windows NT 6.1; Trident/5.0)', 'referer': 'https://passport.csdn.net/login' }
-
访问频率控制
- 随机延时间隔
- 请求限速
- IP 代理支持(预留)
2. 数据存储模块
数据存储模块采用 SQLAlchemy ORM 框架进行数据建模,实现了高效的数据持久化和查询操作。该模块设计了三个核心数据模型,每个模型都针对特定的数据场景进行了优化。
2.1 数据模型设计
2.1.1 博主信息模型 (Info)
class Info(db.Model):
__tablename__ = 'info'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
date = db.Column(db.Text) # 数据更新时间
head_img = db.Column(db.Text) # 博主头像URL
author_name = db.Column(db.Text) # 博主用户名
article_num = db.Column(db.Text) # 文章总数
fans_num = db.Column(db.Text) # 粉丝数量
like_num = db.Column(db.Text) # 获赞数量
comment_num = db.Column(db.Text) # 评论数量
level = db.Column(db.Text) # 博主等级
visit_num = db.Column(db.Text) # 访问量
score = db.Column(db.Text) # 积分
rank = db.Column(db.Text) # 排名
特点说明:
- 使用自增主键确保记录唯一性
- 采用 Text 类型存储可变长度文本
- 包含完整的博主数据画像
- 支持时间序列分析(通过 date 字段)
2.1.2 文章分类模型 (Categorize)
class Categorize(db.Model):
__tablename__ = 'categorize'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
href = db.Column(db.Text) # 分类链接
categorize = db.Column(db.Text) # 分类名称
categorize_id = db.Column(db.BigInteger) # 分类ID
column_num = db.Column(db.BigInteger) # 专栏数量
num_span = db.Column(db.BigInteger) # 订阅数量
article_num = db.Column(db.BigInteger) # 文章数量
read_num = db.Column(db.BigInteger) # 阅读量
collect_num = db.Column(db.BigInteger) # 收藏数
特点说明:
- 使用 BigInteger 类型存储大数值数据
- 支持分类数据的统计分析
- 包含完整的分类元数据
- 可追踪分类的受欢迎程度
2.1.3 文章详情模型 (Article)
class Article(db.Model):
__tablename__ = 'article'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
url = db.Column(db.Text) # 文章链接
title = db.Column(db.Text) # 文章标题
date = db.Column(db.Text) # 发布日期
read_num = db.Column(db.BigInteger) # 阅读数
comment_num = db.Column(db.BigInteger) # 评论数
type = db.Column(db.Text) # 文章类型
特点说明:
- 记录文章的基本信息和统计数据
- 支持文章时间序列分析
- 可追踪章的受欢迎程度
- 便于文章分类统计
2.2 数据库优化策略
-
字段类型优化
- 使用 BigInteger 存储大数值,避免数值溢出
- 采用 Text 类型存储变长文本,节省存储空间
- 主键使用自增 Integer,提高插入性能
-
查询性能优化
- 可以根据需要添加索引
- 支持复杂的聚合查询
- 优化的数据结构设计
-
数据一致性保证
- 主键约束确保记录唯一性
- 适当的字段类型确保数据完整性
- 支持事务操作
2.3 数据操作示例
- 数据插入
new_info = Info(
author_name="博主名称",
article_num="100",
fans_num="1000"
)
db.session.add(new_info)
db.session.commit()
- 数据查询
# 获取博主信息
author_info = Info.query.filter_by(author_name="博主名称").first()
# 获取分类统计
categories = Categorize.query.order_by(Categorize.read_num.desc()).all()
# 获取热门文章
hot_articles = Article.query.order_by(Article.read_num.desc()).limit(10).all()
- 数据更新
info = Info.query.filter_by(author_name="博主名称").first()
info.fans_num = str(int(info.fans_num) + 1)
db.session.commit()
3. 可视化展示模块
前端采用现代化的可视化方案,基于 ECharts 实现了丰富的数据可视化功能,并通过 Axios 实现了与后端的数据交互。
3.1 技术架构
-
核心技术
- ECharts 5.5.1:数据可视化库
- Axios:HTTP 客户端
- CSS3:现代布局和动画
- Flexbox:响应式布局
-
设计规范
:root { --bg-primary: #f7f8fa; --bg-secondary: #ffffff; --text-primary: #333333; --text-secondary: #555555; --accent-color: #0066cc; --border-radius: 12px; --shadow: 0 4px 6px rgba(0, 0, 0, 0.05); }
3.2 图表组件实现
3.2.1 柱状图(季度数据分析)
-
数据流程
async function updateBarChart() { // 1. 获取数据 const response = await axios.get("/api/quarter"); // 2. 数据转换 const dimensions = [ ...Object.keys(chartData[0]).filter((key) => key !== "category"), ]; const source = chartData.map((item) => ({ product: item.category, ...item, })); // 3. 图表配置 const option = { legend: {}, tooltip: {}, dataset: { dimensions: dimensions, source: source, }, xAxis: { type: "category" }, yAxis: {}, series: dimensions.slice(1).map((dim) => ({ type: "bar", name: dim, })), }; }
-
交互特性
- 动态数据加载
- 点击事件响应
- 自适应布局
3.2.2 饼图(分类占比分析)
-
实现细节
async function updatePieChart() { // 1. 数据获取 const response = await axios.get("/api/categorize"); // 2. 图表配置 const option = { tooltip: { trigger: "item", formatter: "{a} <br/>{b}: {c} ({d}%)", }, series: [ { name: "分类统计", type: "pie", radius: ["30%", "70%"], data: chartData, label: { show: true, position: "outside", formatter: "{b}: {d}%", }, }, ], }; }
-
视觉优化
- 内外半径设计
- 标签自动布局
- 悬停动画效果
3.2.3 混合图表(阅读量分析)
async function updateMixChart() {
const option = {
color: ["#3E82F7", "#F86C6B"],
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow",
},
},
legend: {
data: ["文章数", "阅读量"],
},
grid: {
top: "10%",
bottom: "25%",
right: "10%",
},
};
}
3.3 响应式布局实现
-
布局结构
<div class="dashboard-container"> <div class="top-section"> <div class="mac-header">...</div> <div class="stats-grid">...</div> </div> <!-- 图表容器 --> </div>
-
样式优化
.dashboard-container { display: flex; flex-direction: column; height: 100vh; padding: 15px; gap: 15px; }
3.4 数据更新机制
-
初始化流程
function initCharts() { updateBarChart(); updatePieChart(); updateMixChart(); updateHeatmap(); updateArticleList(); }
-
数据刷新策略
- 页面加载时初始化
- 用户交互触发更新
- 定时自动刷新
3.5 交互设计
-
图表联动
- 点击饼图更新文章列表
- 柱状图分类筛选
- 数据钻取功能
-
视觉反馈
.stat-card:hover, .chart-card:hover { transform: translateY(-5px); box-shadow: 0 6px 12px rgba(0, 0, 0, 0.08); }
3.6 性能优化
-
加载优化
- 异步数据加载
- 图表按需渲染
- 防抖和节流处理
-
渲染优化
- 合理的图表配置
- 数据预处理
- 动画性能调优
4. 后端实现逻辑流程
4.1 核心技术栈
- Flask: Web 框架
- Blueprint: 路由模块化
- Cache: 文件系统缓存
- SQLAlchemy: ORM 数据库操作
- PyMySQL: MySQL 数据库驱动
4.2 系统架构
4.3 核心功能实现
4.3.1 应用初始化
app = Flask(__name__)
# 配置缓存
cache.init_app(app, config={
'CACHE_TYPE': 'filesystem',
'CACHE_DIR': 'cache-directory'
})
# 数据库配置
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://user:pass@host:port/db'
db.init_app(app)
4.3.2 路由注册机制
DEFAULT_BLUEPRINT = [
(cs, '/'), # CSDN API蓝图
]
url_path_prefix = "/api"
def config_blueprint(app):
for blueprint, url_prefix in DEFAULT_BLUEPRINT:
url_prefix = url_path_prefix + url_prefix
app.register_blueprint(blueprint, url_prefix=url_prefix)
4.4 API 接口设计
4.4.1 数据统计接口
- 季度数据统计
@cs.route('/quarter')
@cache.cached(timeout=60)
def GetQuarter():
"""获取每年每季度博客数量"""
year_quarter_count = defaultdict(lambda: defaultdict(int))
data = GetArticle()
for article in data:
year = article["year"]
quarter = article["quarter"]
year_quarter_count[year][quarter] += 1
return Result.success(result)
- 分类数据统计
@cs.route('/categorize')
@cache.cached(timeout=60)
def Pie():
"""获取文章分类统计"""
categorize_data = Categorize.query.all()
pie_data = [
{"value": item.article_num, "name": item.categorize}
for item in categorize_data
]
return Result.success(pie_data)
4.4.2 数据处理流程
4.5 性能优化策略
-
缓存优化
- 使用文件系统缓存
- 60 秒缓存过期时间
- 针对高频访问接口启用缓存
-
数据处理优化
- 使用 defaultdict 优化数据聚合
- 批量数据查询
- 查询 据预处理和转换
-
查询优化
- ORM 延迟加载
- 查询结果缓存
- 合理的数据索引
4.6 数据流转流程
4.7 错误处理机制
-
全局异常处理
try: # 业务逻辑 except Exception as e: print(f"Error: {str(e)}") return Result.error(str(e))
-
数据验证
if not data or not data.labels: return Result.error("Invalid data format")
-
结果封装
class Result: @staticmethod def success(data): return jsonify({"code": 200, "data": data}) @staticmethod def error(msg): return jsonify({"code": 500, "msg": msg})