news 2026/10/19 2:16:33

爬虫与Flask数据采集分析面板:从零搭建趋势可视化系统

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
爬虫与Flask数据采集分析面板:从零搭建趋势可视化系统

简介:pythonProject.zip是一个ZIP压缩包,内容属于Python工程范畴,主要面向需要参考完整项目结构、学习Python与C扩展混合开发,或希望复用现有代码的开发者。整个压缩包约57.14MB,共2000个文件,其中1745个为.py源码,另含103个js、68个txt、45个c、29个h,以及少量md、html、json、xml、cpp、sh等辅助文件,覆盖Python主逻辑、前端脚本、说明文档、C/C++底层模块、环境配置与辅助工具链。该资源已有134人学习下载。从文件构成和内容预览看,项目中涉及Fortran接口、CPU特性检测等底层实现,推测其可能用于数值计算、性能优化或科学计算场景。解压后可直接查看源码组织方式、依赖清单和文档说明,有助于分析Python工程的整体打包规范,理解Python与C混合编程的接口写法;同时也可作为二次开发、模块移植的参考,对独立完成中小型Python项目有帮助。

1. pythonProject.zip:一个开箱即用的数据采集与分析面板

同事离职前丢给我一个 pythonProject.zip,解压后没有 README,只有一堆目录和散落的 .py 文件。花了半天把它跑通,才发现这套代码的价值不在某个单独函数,而是一个完整闭环:爬虫抓取公开页面上的结构化数据,pandas 做清洗聚合,Flask 提供接口,前端 ECharts 渲染成趋势图。对正在入门爬虫或者想快速搭一个内部数据看板的开发者来说,这份压缩包非常值得拆开看看。下面我按运行链路的顺序,把技术选型、目录结构、核心代码和跑通步骤逐一讲透,最后给出二次开发的改造思路。

2. 技术选型与架构设计:Flask 撑起的三层数据管道

2.1 为什么依赖这么少:Flask + requests + pandas

解开压缩包第一件事就是看 requirements.txt。里面只有六个包:Flask、requests、beautifulsoup4、pandas、flask_sqlalchemy、apscheduler。没有 Django,没有 scrapy,也没有 Node 那一堆东西。这个选型明显是冲着“轻量、够用、能跑”去的。

Flask 作为 Web 层,只负责提供页面和接口,不掺和业务逻辑;requests 加 BeautifulSoup 完成网页请求和解析,连 scrapy 都没上,说明作者预设的是中小规模采集场景,不追求分布式爬虫;pandas 则承包了所有数据清洗和聚合计算。整个技术栈没有一处是炫技,每个库都在干自己最擅长的事。

有人会问,爬虫为什么不用 scrapy?scrapy 确实更强,但它的学习曲线和项目结构都更重,需要定义 Item、Pipeline、Middleware。而这个项目要抓的页面就一种,请求 → 解析 → 入库三步走,requests 加 BeautifulSoup 反而最直白,调试也方便——直接在 Python 交互环境里跑一遍 parse_records 就能看结果。同理,Flask 而不是 Django,是因为这里没有用户系统、没有后台管理、没有权限模型,Django 那套全家桶在这里是负资产。

这个组合对二次开发很友好。想换成异步采集,把 requests 改成 httpx 就够;想上重型框架,Flask 蓝图也能平移到 FastAPI。依赖少意味着环境好搭,这是我愿意推荐这套代码的第一个原因。

2.2 目录结构:一个目录干一件事

pythonProject/ ├── app/ │ ├── __init__.py # 应用工厂,注册蓝图和数据库 │ ├── views.py # 页面路由和 JSON 接口 │ ├── models.py # ORM 模型 │ ├── spider/ │ │ ├── __init__.py │ │ ├── crawler.py # 网页请求与容错 │ │ └── parser.py # 页面解析,抽取结构化字段 │ ├── analysis/ │ │ ├── __init__.py │ │ ├── processor.py # 数据清洗 │ │ └── stats.py # 聚合统计 │ ├── templates/ │ │ └── index.html # 单页看板 │ └── static/ │ └── js/ │ └── dashboard.js # ECharts 渲染逻辑 ├── config.py # 全局配置:数据库、调度间隔 ├── requirements.txt ├── init_db.py # 建表 + 写入种子数据 ├── run.py # 入口 └── README.md

这个结构最大的特点是“按功能切目录而不是按文件切”。spider 和 analysis 被拆成独立包,以后换数据源或者换算法,不用动 Web 层。templates 和 static 分开,前端资源不会跟 Python 代码混在一起。

看一下文件职责就明白主干逻辑:crawler.py 只负责把网页拿下来,parser.py 负责从 HTML 里抽字段,processor.py 负责把脏数据洗干净,stats.py 负责算指标,views.py 负责把数据喂给前端。链路是单向的,没有循环依赖,这一点对新手读代码非常友好。

实际跑起来你会发现,真正需要手工改的文件只有三个:config.py(换数据库、改调度周期)、spider/parser.py(换解析规则)、views.py(改接口返回)。其余文件基本属于“一次写好,长期不动”。

2.3 数据流设计:一个请求从头到尾怎么走

这套系统的核心数据流可以用四句话概括:定时任务到点触发爬虫,爬虫写入数据库;用户打开页面时 Flask 从库里读数据;聚合结果转成 JSON 返回;浏览器端 ECharts 拿到 JSON 画图。

先说采集侧。config.py 里有一个 SCHEDULER_INTERVAL 参数,默认 3600 秒。apscheduler 每隔一小时调一次 crawler.fetch_page(),把新数据追加到 data_record 表。parser.py 里按 title 做了去重,所以同一标题的记录不会重复插入,这算是一个简单幂等保护。

再说展示侧。用户在浏览器访问 /,Flask 返回 index.html;页面加载后 dashboard.js 主动请求 /api/trend;后端从数据库取最近 30 天记录,按天聚合,返回格式固定的 JSON;前端拿到数据直接 setOption。

采集和展示完全解耦是这套设计最聪明的地方:爬虫挂了,页面还能看历史数据;数据库换了,只要 views.py 里的查询不变,前端零改动。理解这条链路,后面的代码拆解就顺理成章了。

2.4 配置项拆解:改哪些参数会影响行为

配置项默认值作用常见改法
SQLALCHEMY_DATABASE_URIsqlite:///data.db数据库连接串换 MySQL 时改成 mysql+pymysql://user:pass@host/dbname
SCHEDULER_INTERVAL3600爬虫定时任务间隔(秒)调试期改成 60,验证完再改回
JSON_AS_ASCIIFalse接口中文是否转义保持 False,调试体验好很多
DEBUGTrue启动时是否开启调试模式部署时务必改成 False

换 MySQL 时要注意,SQLAlchemy 连接串里如果密码带特殊字符,需要做 URL 编码,否则启动直接报错。这个坑我后面单独说。

3. 核心模块拆解:从一条数据进入到页面渲染的完整链路

3.1 入口与配置:应用工厂模式

run.py 是启动入口,代码不长:

# run.py from app import create_app app = create_app() if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True)

逻辑说明:create_app() 是典型的应用工厂函数,它把配置加载、数据库初始化、蓝图注册都封装在函数内部。run.py 只负责实例化和启动,好处是测试时可以用不同配置创建多个 app 实例,不会互相污染。

config.py 里的核心配置:

# config.py import os BASE_DIR = os.path.dirname(os.path.abspath(__file__)) class Config: SQLALCHEMY_DATABASE_URI = "sqlite:///" + os.path.join(BASE_DIR, "data.db") SQLALCHEMY_TRACK_MODIFICATIONS = False SCHEDULER_INTERVAL = 3600 # 爬虫执行间隔,单位秒

这里有个容易被忽略的点:SQLite 连接串的路径是拼出来的,BASE_DIR 取了 config.py 所在目录。如果你把项目移动位置,只要保持目录结构不变,路径就自动是对的。这是比写死绝对路径稳得多的做法。

应用工厂核心代码:

# app/__init__.py from flask import Flask from config import Config from app.models import db def create_app(): app = Flask(__name__) app.config.from_object(Config) db.init_app(app) from app.views import bp app.register_blueprint(bp) return app

逻辑说明:这里用了 Flask 蓝图,把路由从主模块中抽离。db.init_app(app) 是 SQLAlchemy 的标准用法,不在创建时绑定数据库,而是在应用初始化时才绑定。蓝图在函数内 import,是为了避免循环引用——app/init.py 和 app/views.py 之间如果互相 import,很容易死循环。

3.2 爬虫模块:请求、解析、容错三板斧

crawler.py 负责网络请求,这层最怕遇到反爬。常见做法是伪装 UA、设置超时、捕获异常后重试:

# app/spider/crawler.py import time import requests HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/120.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml" } def fetch_page(url: str, retry: int = 3) -> str: """请求页面,失败自动重试,最多 retry 次""" for attempt in range(retry): try: resp = requests.get(url, headers=HEADERS, timeout=10) resp.raise_for_status() resp.encoding = resp.apparent_encoding return resp.text except requests.RequestException as e: print(f"[crawler] 第 {attempt + 1} 次请求失败: {e}") time.sleep(2 * (attempt + 1)) raise RuntimeError(f"页面请求失败: {url}")

参数说明:url 是目标页面地址;retry 是重试次数,默认 3。timeout=10 表示连接和读取的等待上限,避免某个慢请求卡死整个调度任务。resp.encoding 这行很关键——部分站点不声明 charset,requests 会猜错编码,导致后面解析全是乱码,这里用 apparent_encoding 从内容反推。

parser.py 负责从 HTML 里抽结构化数据:

# app/spider/parser.py from bs4 import BeautifulSoup def parse_records(html: str) -> list[dict]: """解析列表页,返回记录列表""" soup = BeautifulSoup(html, "html.parser") records = [] for item in soup.select("div.data-row"): title_node = item.select_one("span.title") value_node = item.select_one("span.value") date_node = item.select_one("span.date") if title_node is None or value_node is None: continue records.append({ "title": title_node.get_text(strip=True), "value": value_node.get_text(strip=True), "date": date_node.get_text(strip=True) if date_node else "", }) return records

逻辑说明:CSS 选择器 div.data-row 定位每一条记录,再分别取标题、数值、日期三个字段。选这种写法不是巧合,而是因为目标站点列表页的 DOM 结构就是这样,每条数据包在一个 div.data-row 里。用 select_one 而不是 find_all,是明确只要第一个匹配节点,避免页面结构变化时抽到多余内容。

这里用了保守策略——字段缺失就跳过这一条而不是抛异常,防止页面局部结构变化导致整个任务崩溃。代价是解析失败时没有提示,所以我在实际使用时会建议在公共日志里记录跳过的条数,方便排查网站是不是改版了。

3.3 数据处理模块:pandas 把脏数据洗成可直接分析的表

采集到的原始字段都是字符串,加上页面里偶尔缺字段,没法直接用。processor.py 里做了标准化处理:

# app/analysis/processor.py import pandas as pd def clean_records(raw: list[dict]) -> pd.DataFrame: """转为 DataFrame 并清洗:去重、转数值、补日期""" df = pd.DataFrame(raw) df = df.drop_duplicates(subset=["title"]) # 按标题去重 df["value"] = pd.to_numeric(df["value"], errors="coerce") # 非数字变 NaN df = df.dropna(subset=["value"]) # 干掉没有数值的行 df["date"] = pd.to_datetime(df["date"], errors="coerce") df = df.fillna({"date": pd.Timestamp("today").date()}) return df def daily_summary(df: pd.DataFrame) -> list[dict]: """按天聚合,返回 [{date, total}] 列表""" result = df.groupby(df["date"].dt.date)["value"].sum().reset_index() return result.rename(columns={"value": "total"}).to_dict(orient="records")

逻辑说明:clean_records 里最容易翻车的是 pd.to_numeric 的 errors 参数。网页里 value 可能是 "3,200" 这种带逗号的字符串,直接强转会报错,errors="coerce" 会把这些转成 NaN,再由 dropna 过滤。日期字段同理,to_datetime 遇到无法解析的值会变成 NaT,fillna 用当天日期兜底,保证后续聚合不崩。

daily_summary 是整个看板的核心函数:按天分组求和,转成前端好消费的列表结构。注意 groupby 之后 date 字段的类型是 pandas 的 Timestamp,调用 dt.date 才能转成普通日期对象,这一步不处理,后面 JSON 序列化时会报 Object of type Timestamp is not JSON serializable。

stats.py 里一般放扩展指标,比如环比:

# app/analysis/stats.py def growth_rate(current: float, previous: float) -> float | None: """计算环比增速,前一期为 0 时返回 None 避免除零""" if previous == 0: return None return round((current - previous) / previous * 100, 2)

这段逻辑没什么可说的,唯一的坑是除零。业务上今天数据为 0 是有可能的,直接除会崩,所以接口层要做一层容错,返回 None 由前端显示“暂无数据”。

3.4 Web 层与前端渲染:接口返回什么,页面就画什么

views.py 里两个核心接口:

# app/views.py from flask import Blueprint, jsonify, render_template from app.analysis.processor import daily_summary from app.models import db, DataRecord import pandas as pd bp = Blueprint("main", __name__) @bp.route("/") def index(): return render_template("index.html") @bp.route("/api/trend") def api_trend(): records = DataRecord.query.order_by(DataRecord.date.desc()).limit(30).all() rows = [{"date": r.date.isoformat(), "value": r.value} for r in records] df = pd.DataFrame(rows) summary = daily_summary(df) return jsonify({"code": 0, "data": summary})

参数说明:/api/trend 返回的 data 是列表,每一项包含 date 和 total 两个字段。前端判断 code 是否等于 0,非 0 时弹出错误提示。这里把 records 先转成字典列表再交给 pandas,是因为 DataRecord 模型对象不能直接传给 DataFrame 构造器,这是 Flask-SQLAlchemy 使用者最容易踩的坑之一。

models.py 的 ORM 模型:

# app/models.py from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() class DataRecord(db.Model): __tablename__ = "data_record" id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(200), nullable=False) value = db.Column(db.Float, nullable=False) date = db.Column(db.Date, nullable=False, index=True)

前端 dashboard.js 的请求逻辑:

// app/static/js/dashboard.js fetch("/api/trend") .then(res => res.json()) .then(json => { if (json.code !== 0) throw new Error(json.message); const dates = json.data.map(item => item.date); const values = json.data.map(item => item.total); chart.setOption({ xAxis: { type: "category", data: dates }, series: [{ type: "line", data: values }] }); });

逻辑说明:这里用 fetch 而不是 jQuery 的 ajax,是浏览器原生 API,不需要额外引库。拿到数据后拆成两个平行数组,填进 ECharts 的 setOption。注意 xAxis 的 type 是 category,配合字符串日期数组才能正确显示;如果 type 写成 time,数据格式就不一样了,需要换成 Date 对象。

4. 运行与验证:从解压到浏览器看到图表

4.1 创建虚拟环境,避免污染全局

Python 项目跑不起来,八成是环境问题。第一步先建虚拟环境:

cd pythonProject python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate

为什么必须建虚拟环境?因为这台机器上可能同时存在多个 Python 项目,有的要 Flask 2.x,有的要 3.x,直接用全局 pip install 会把环境搅浑,装完这个项目另一个项目就崩了。venv 把依赖隔离在项目目录内,删除项目依赖也跟着消失,是最省心的方案。

建完环境后命令行前缀会变成 (venv),说明已经进入虚拟环境。如果 source 之后没反应,检查一下当前终端目录是不是真的在 pythonProject 根目录下。

4.2 安装依赖与版本验证

pip install -r requirements.txt

网络慢或者公司内网有限制时,建议先升级 pip 再换国内镜像源:

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt

装完不要急着启动,先验证关键包是否可用:

python -c "import flask, pandas, bs4, apscheduler; print(flask.__version__)"

能打印出版本号说明核心依赖没问题。如果某些包报错,大概率是 Python 版本不匹配——这套代码建议用 Python 3.9 以上,pandas 在 3.8 上部分函数行为不一样。

4.3 初始化数据库

python init_db.py

init_db.py 的逻辑很简单,主要做两件事:调用 db.create_all() 建表,再写入几条种子数据。这样即使爬虫还没跑到数据,页面打开也有东西可看。

# init_db.py from app import create_app from app.models import db, DataRecord import datetime app = create_app() with app.app_context(): db.create_all() if DataRecord.query.count() == 0: db.session.add(DataRecord( title="示例数据", value=100.0, date=datetime.date.today() )) db.session.commit()

执行完可以用 sqlite3 命令快速确认表结构:

sqlite3 data.db ".schema data_record"

能看到 CREATE TABLE 语句就说明建表成功。注意 init_db.py 也要放在 app_context 里执行,否则会报 Working outside of application context 错误。

4.4 启动服务与接口验证

python run.py

看到 Flask 的启动日志后,浏览器打开 http://127.0.0.1:5000/ ,如果页面出现趋势图,整条链路就算跑通了。如果不方便开浏览器,可以先用 curl 验证接口:

curl http://127.0.0.1:5000/api/trend

返回包含 data 数组的 JSON 就说明后端正常。这时页面还看不到图,问题大概率在前端资源路径上,检查浏览器控制台有没有 404。

还有个细节:Flask 默认监听 127.0.0.1,只能本机访问。如果想把看板给同局域网的人看,把 run.py 里的 host 改成 "0.0.0.0" 并重启。这时候访问 http://你的局域网IP:5000/ 就行。

5. 避坑指南:跑这套代码常见的 5 个问题

5.1 页面全是乱码

现象:浏览器打开看板,标题和日期显示成方块或 ????。

原因:两层。第一层是 Flask 返回 JSON 时,如果 JSON_AS_ASCII 是默认值 True,中文会被转成 \uXXXX 形式,这是 JSON 标准转义,不算乱码但很难读;第二层是 HTML 页面本身没有声明 UTF-8 编码,浏览器用系统默认编码解析,直接乱码。

解决:config.py 里加一行 JSON_AS_ASCII = False;templates/index.html 的 head 里加<meta charset="UTF-8">。改完重启服务,强制刷新浏览器。从那以后我每次拿到 Flask 项目,第一件事就是查这两处。

5.2 爬虫请求返回 403,目标站点拒绝访问

现象:爬虫日志里出现 403 Client Error,或者抓回来的内容全是验证码页面。

原因:目标站点检测到非浏览器请求。没有携带足够完整的请求头,尤其是 User-Agent 和 Accept 缺一不可;也可能是请求频率太高,IP 被临时封禁。

解决:先检查 crawler.py 里 HEADERS 是否完整,UA 要模仿真实浏览器版本,不要用 Python-requests/数字 这种默认 UA;再把请求频率降到 1 秒以上,或者加随机延时。如果还不行,考虑使用代理池,但这套轻量级代码不建议为几个页面引入这么重的方案,手动加 Cookie 往往更直接。

5.3 接口报 500,日志提示 no such table

现象:启动正常,页面也能打开,但 /api/trend 返回 500,控制台显示 OperationalError: no such table: data_record。

原因:直接运行 run.py 启动服务,但没跑过 init_db.py,数据库文件里根本没有表。

解决:先执行 python init_db.py,确认 data.db 文件大小大于 0,再重启服务。如果表存在依然报这个错,检查 models.py 里tablename是否写错,和数据库里实际表名对不上。

5.4 端口被占用,服务起不来

现象:run.py 启动后立刻退出,报 Errno 98 Address already in use。

原因:5000 端口已经被另一个进程占用了,可能是之前启动的服务没关干净,也可能是本机其他程序在监听这个端口。

解决:先查端口占用情况,Linux 用lsof -i:5000,Windows 用netstat -ano | findstr 5000,找到进程号后结束对应进程;不想关进程就改端口,把 run.py 里 port 改成 5001,记得访问地址也跟着改。实际上这个项目的模板里没有写死资源地址,所以改端口很简单。

5.5 pandas 报错 Object of type Timestamp is not JSON serializable

现象:接口返回 500,日志里显示 Flask 序列化 JSON 时遇到 Timestamp 对象。

原因:daily_summary 里 groupby 后的 date 字段是 pandas 的 Timestamp 类型,Flask 的 jsonify 不认识这个类型,没法直接转成 JSON 字符串。

解决:在聚合函数里对 date 列调用df["date"] = df["date"].dt.strftime("%Y-%m-%d"),把日期转成字符串再返回。或者更省事,在 clean_records 里就对 date 做 strftime,后面所有环节拿到的都是纯字符串,彻底避开序列化陷阱。

6. 二次开发:把这份代码改成你自己的数据看板

6.1 替换爬虫目标站点

最常改的就是 parser.py。先打开目标站点的页面,按 F12 看审查元素,找到包裹单条数据的容器选择器,比如原来是 div.data-row,新站可能是 li.list-item。把 parse_records 里的选择器换掉,再调整三个字段的取值路径就行。如果新站点的数据不在页面源码里,而是异步接口返回 JSON,那就更简单了,直接 requests 请求那个接口,去掉 BeautifulSoup 解析步骤。

6.2 新增聚合指标

比如想加“平均数值”而不是只看总和,在 processor.py 里加一个函数:

def daily_average(df: pd.DataFrame) -> list[dict]: result = df.groupby(df["date"].dt.date)["value"].mean().round(2).reset_index() return result.rename(columns={"value": "average"}).to_dict(orient="records")

views.py 里新增一个 /api/average 路由,前端再加一个折线系列,30 分钟搞定。

6.3 自定义前端展示

看板默认是一条趋势折线,改成柱状图或者加一个排行表格都不是难事。ECharts 的 series 类型换成 bar 就行;表格可以在 index.html 里加一个 div,再写一段 JavaScript 渲染。更推荐的做法是把接口的数据直接喂给组件,保持后端不因前端改动而改动。

从那以后我每次接手别人的 Python 压缩包,都会先跑一遍这个流程:看 requirements 确认技术栈,看目录确认模块边界,找入口文件确认启动方式,再改配置跑通最小闭环。遇到问题先按链路排查,而不是盲目改代码。这套 pythonProject 拆完你会发现,绝大多数踩坑点都集中在编码、序列化和环境三件事上,希望这些经验能帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 2:15:56

机器人在北美楼里坐电梯、过门禁:三条集成路线、谁有资格动电梯、火警时怎么办(KeepRobots 工程笔记)

本文由 KeepRobots 团队根据公开资料整理。KeepRobots 是温哥华的机器人现场服务公司&#xff0c;给别家生产的机器人做北美现场部署和售后。本文是工程笔记&#xff0c;不是法律或规范解释&#xff1b;电梯和消防相关的判断&#xff0c;以当地监管机构、持照电梯承包商和消防工…

作者头像 李华
网站建设 2026/10/19 2:15:56

03-深刻观-DeepSeek-V4.1-Flash设计哲学与升华

第 3 篇 深刻不忘观 — 设计哲学与升华前两篇&#xff0c;我们"看见"了它的全貌&#xff0c;也"看懂"了它的算法。这一篇&#xff0c;让我们跃出水面&#xff0c;问一句"为什么"——它更深层的设计哲学是什么&#xff1f;这种思想的根源在哪&am…

作者头像 李华
网站建设 2026/10/19 2:15:53

基于LSTM的对话情绪识别系统计算机毕设(源码+lw+部署文档+讲解等)

博主介绍&#xff1a;✌ 专注于VUE,小程序&#xff0c;安卓&#xff0c;Java,python,物联网专业&#xff0c; 从事毕业指导&#xff0c;项目实战✌选取一个适合的毕业设计题目很重要。✌关注✌私信我✌具体的问题&#xff0c;我会尽力帮助你。一、研究目的随着自然语言处理技术…

作者头像 李华
网站建设 2026/10/19 2:15:52

夜深了,心静了

又一次失眠&#xff0c;不知是不是喝茶的原因。戴上耳机&#xff0c;耳朵里又传来那些熟悉的老歌&#xff0c;时间也在这一刻停止。那个真实的我开始对自己独白。最近的生活&#xff0c;多了些忙碌&#xff0c;想把时间填满&#xff0c;希望少一些杂念。这两天又翻看了以前写的…

作者头像 李华