标签 pandas 下的文章

加密资产市场的高波动特性,给量化策略落地带来了不少实操挑战。作为技术开发者,如何把交易逻辑转化为可运行的自动化量化策略?本文从开发者视角,拆解加密资产量化交易的完整落地流程,聚焦数据、策略、回测、执行、优化五大核心环节,附可直接运行的代码示例。

一、核心问题:数据是量化策略的基础门槛
做量化交易时,开发者最常遇到的问题就是数据层面的坑:

  1. 数据源不稳定,行情数据延迟或丢失;
  2. 数据维度单一,缺乏核心交易指标;
  3. 数据格式不统一,增加后续处理成本。

而可靠的 API 工具能直接对接交易平台,获取指定交易对的实时价格、交易量等核心数据,为策略搭建扫清基础障碍。

二、实操步骤:5 步实现量化策略落地

步骤 1:获取实时行情数据
通过AllTick API 接口拉取目标加密资产的实时数据,是量化策略的第一步,核心代码如下(可直接复制运行):

import requests
def get_crypto_data(symbol='BTCUSDT'): url = 'https://api.alltick.co/crypto/real-time' params = {'symbol': symbol} response = requests.get(url, params=params) data = response.json()
return data
# 获取比特币实时数据
btc_data = get_crypto_data('BTCUSDT')​
print(btc_data)

开发者注意:
需提前安装requests库:pip install requests;
建议增加异常捕获(如try-except),处理接口请求超时、返回数据格式异常等问题。

步骤 2:构建移动平均策略逻辑
移动平均策略是量化交易的基础趋势策略,核心逻辑为「短期均线突破长期均线生成交易信号」,具体实现代码如下:

import pandas as pd​ import numpy as np​
# 假设已经获取了历史数据 historical_data = pd.DataFrame(btc_data)
# 计算短期和长期移动平均
short_window = 50​
long_window = 200​ historical_data['short_mavg'] = 
historical_data['close'].rolling(window=short_window).mean()
historical_data['long_mavg'] = 
historical_data['close'].rolling(window=long_window).mean()​
# 当短期均线突破长期均线时,产生买入信号
historical_data['signal'] = np.where(historical_data['short_mavg'] > 
historical_data['long_mavg'], 1, 0)

开发者注意:
需安装pandas和numpy:pip install pandas numpy;
若历史数据量不足 200 条,long_mavg会出现NaN,需补充数据或调整窗口参数。

步骤 3:搭建策略回测框架
策略写完后,必须通过回测验证有效性,避免直接投入实盘造成损失。以下是行业通用的极简回测框架代码:

def backtest_strategy(data): initial_balance = 10000​
balance = initial_balance​
position = 0​ for i in range(1, len(data)): if data['signal'][i] == 1 and position == 0: position = balance / data['close'][i]​
balance = 0​
if position > 0: elif data['signal'][i] == 0 and position > 0: balance = position * data['close'].iloc[-1]​ balance = position * data['close'][i]​ position = 0​
return balance - initial_balance​
profit = backtest_strategy(historical_data)
print(f'回测利润: {profit}')

开发者注意:
该回测框架为基础版本,未考虑手续费、滑点等实际交易成本,生产环境需补充;
回测结果仅作参考,需结合样本外数据验证策略稳定性。

步骤 4:实现实时交易订单执行
回测达标后,通过 API 接口将策略信号转化为实际交易订单,减少人工操作误差,买入操作核心代码如下:

def place_order(symbol, side, quantity):
url = 'https://api.alltick.co/crypto/order' data = {​ 'symbol': symbol,
'side': side, # 'BUY' 或 'SELL'
'quantity': quantity,
'price': get_crypto_data(symbol)['price']​
}
response = requests.post(url, json=data)
return response.json()
# 假设我们要买入0.1个比特币
order = place_order('BTCUSDT', 'BUY', 0.1)
print(order)​

开发者注意:
实盘交易前需确认 API 接口权限、资金充足性;
建议先在模拟盘测试订单接口,避免因参数错误导致交易异常。

三、生产环境优化:策略迭代与风险控制
量化策略不是写完就结束,生产环境中需要持续优化:

  • 参数迭代:定期基于最新历史数据重新回测,调整均线窗口、交易阈值等参数;
  • 实时监控:编写监控脚本,跟踪策略运行状态,异常时触发止损或暂停机制;
  • 风险控制:添加资金管控逻辑,限定单次交易资金占比(如不超过总资金的 10%)。

总结
加密资产量化交易的落地核心是「数据 - 策略 - 回测 - 执行 - 优化」的闭环,对开发者而言,重点在于:

  • 保证数据获取的稳定性和准确性;
  • 策略逻辑需兼顾简洁性和可验证性;
  • 回测和实盘环节需考虑实际交易场景的边界条件。
    以上代码均可直接运行,开发者可根据自身需求扩展功能(如添加日志、监控、参数优化模块)。

在 FinTech 量化研发场景中,美股数据的获取与整合是策略回测、产品迭代的核心基础。不少开发者实操时都会陷入误区:以为接口调用是核心难点,实则耗时最多的是稳定获取数据、统一数据结构,以及实现历史与实时数据的复用。本文结合 FinTech 初创团队的真实项目经验,拆解美股数据接口接入的核心痛点,分享基于 AllTick API 的高效落地方案,所有代码可直接复用,帮开发者避开常见坑点。

一、核心痛点:开发者必踩的两大数据接入难题
对量化研发团队而言,数据接入效率直接决定策略迭代速度,但美股数据接入常面临两个核心卡点:

  • 数据衔接断层:历史行情与实时推送数据字段定义不统一,需单独编写两套存储、处理逻辑,不仅增加代码冗余,还易出现数据断层,导致回测与实盘结果偏差;
  • 标准化成本高:原始数据时间戳格式混乱、字段冗余 / 缺失,后续统计分析、可视化需重复适配,严重拖慢研发进度,尤其资源有限的初创团队,会直接延长策略验证周期。

二、破局思路:数据接入的核心技术诉求
解决上述问题无需复杂技术,核心抓住「数据获取」和「数据整合」两大环节:

  • 灵活筛选:接口需支持按股票标的(如 AAPL)、时间周期(1min/5min/1day)、时间范围精准筛选,请求方式简洁易实现;
  • 格式统一:历史与实时数据字段结构必须一致,无需重复开发适配代码,同时保障数据无缺失、时间戳准确;
  • 稳定可靠:支持大跨度数据获取,无超时、丢包等问题。

三、实战落地:AllTick API 接入全流程(代码可直接复用)

(一)Step 1:HTTP 请求快速获取历史数据
美股历史数据接口主流采用 HTTP 请求方式,核心参数支持标的、时间周期、时间范围精准配置,可直接复用以下代码:

import requests
import pandas as pd​
url = "https://apis.alltick.co/v1/market/history"​
params = {​
"symbol": "AAPL", "market": "US",
"interval": "1day",
"start_time": "2026-01-01", "end_time": "2026-03-01"
}​
headers = {​
"Authorization": "Bearer YOUR_API_KEY"
}​
response = requests.get(url, params=params, headers=headers).json()
if response.get("code") != 0:​
raise ValueError("请求失败", response)
data = response["data"]

核心优势:接口返回数据按时间戳升序排列,字段规整无冗余,无需额外排序、清洗,直接进入后续处理环节。

(二)Step 2:标准化处理适配多场景分析
将原始数据转换为 DataFrame 格式并统一时间字段,是量化分析的基础,代码如下:

df = pd.DataFrame(data)
df["datetime"] = pd.to_datetime(df["timestamp"], unit="s")
df.set_index("datetime", inplace=True)
print(df.head())

处理后价值:

  1. 时间索引规范化,支持按时间区间快速切片,适配不同周期策略回测;
  2. 兼容 pandas/NumPy 等库,可直接开展因子计算、统计检验;
  3. 数据结构统一,为实时数据追加奠定基础。

(三)Step 3:WebSocket 实现实时数据无缝追加
AllTick API 的核心优势是历史 / 实时数据字段完全一致,可通过 WebSocket 直接追加实时数据,无需重构存储逻辑:

import websocket​
import json​
def on_message(ws, message):
    msg = json.loads(message)
    new_df = pd.DataFrame([msg])
    new_df["datetime"] = pd.to_datetime(new_df["timestamp"], unit="s")
    new_df.set_index("datetime", inplace=True)
    global df​
    df = pd.concat([df, new_df])
    print(df.tail())
def on_open(ws):
    ws.send(json.dumps({​
        "action": "subscribe",
        "symbol": "AAPL",
        "market": "US",
        "interval": "1min"
    }))​
ws = websocket.WebSocketApp(​
    "wss://apis.alltick.co/realtime",​
    on_message=on_message,
    on_open=on_open​
)​
ws.run_forever()

关键价值:回测阶段的因子计算、信号生成代码可直接复用至实盘,大幅降低适配成本。

(四)避坑指南:3 个提升稳定性的关键细节

  • 结合实操经验,以下细节能有效规避数据风险:
  • 大跨度历史数据(如 5 年日线、1 年分钟线)需分段请求(按季度 / 年度拆分),避免超时或数据丢失;
  • 接入前校验数据完整性,重点核对停牌、节假日等特殊节点的时间戳连续性;
  • 提前制定缺失值处理策略(如前值填充、线性插值),避免回测样本失真。

四、落地效果:研发效率与稳定性双提升
该方案落地后,团队核心指标显著优化:

  • 数据接入开发工时降低 40%:无需为历史 / 实时数据编写差异化代码;
  • 策略回测周期缩短 30%:标准化数据直接对接回测框架,减少格式转换时间;
  • 长期维护成本降低:新增标的 / 调整周期仅需修改参数,无需重构逻辑。

总结
美股数据接口接入的核心,从来不是技术复杂度,而是数据结构的稳定性、时间字段的规范性,以及历史 / 实时数据的衔接流畅度。如果在实操中遇到接口适配、数据校验等问题,欢迎在评论区交流探讨,共同避坑~

目录

  1. 库的概览与核心价值
  2. 环境搭建与"Hello, World"
  3. 核心概念解析
  4. 实战演练:分析电影评分趋势
  5. 最佳实践与常见陷阱
  6. 进阶指引

1. 库的概览与核心价值

想象一下,你手头有一份包含一百万条销售数据的 Excel 表格,密密麻麻的数字堆叠在一起,让你头晕眼花。你需要找出旺季和淡季的趋势,对比不同产品的销售表现,但这些冰冷的数据就像沉默的密码,让你难以快速洞察其中的规律。这就是数据可视化的痛点——没有图形,数据就是一堆难以理解的数字。

Matplotlib 正是为解决这个核心问题而生的强大工具。它就像一位精通绘画的数据翻译官,能将枯燥的数据转化为直观、生动的图表,让你一眼看出数据背后的故事。在 Python 数据科学生态中,NumPy 负责数值计算,Pandas 处理结构化数据,而 Matplotlib 则承担着将数据"可视化呈现"的关键使命,三者共同构成了数据分析的三剑客。

那么,为什么需要专门的 Matplotlib,而不是直接用 Excel 或其他工具呢?关键在于它的三个独特优势

  • 无缝集成MatplotlibNumPyPandas 完美兼容,你可以直接读取 DataFrame 或数组进行绘图,无需繁琐的数据导出导入
  • 高度可定制:从坐标轴刻度、图例位置到颜色、字体、线型,每一个细节都可以精细控制,满足论文发表、专业汇报的苛刻要求
  • 生态基石:作为 Python 可视化的开山鼻祖,它不仅是独立工具,更是 SeabornPlotly 等高级库的基础,学会了它,后续学习会更轻松

一句话总结:Matplotlib 让数据"说话",让复杂的规律变得一目了然,是每位数据分析师必备的看家本领。

2. 环境搭建与"Hello, World"

安装说明

安装 Matplotlib 非常简单,推荐使用 pipconda

# 使用 pip 安装(推荐)
pip install matplotlib numpy

# 使用 conda 安装
conda install matplotlib numpy

注意Matplotlib 通常与 NumPy 配合使用,建议同时安装。如果安装过程中遇到权限问题,可以尝试使用 --user 参数(pip)或创建虚拟环境。

最简示例

让我们用最经典的"正弦曲线"作为入门案例,只需 5 行代码就能画出一张漂亮的图表:

import matplotlib.pyplot as plt
import numpy as np

# 1. 准备数据:x从0到2π,取100个点
x = np.linspace(0, 2 * np.pi, 100)
y = np.sin(x)

# 2. 创建画布和绘图区域,并绘制曲线
fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(x, y)

# 3. 添加标题和标签
ax.set_title("正弦函数图像")
ax.set_xlabel("x值(弧度)")
ax.set_ylabel("sin(x)")

# 4. 显示图表
plt.show()

逐行解释

  • 第1-2行:导入 pyplot 子模块(简写为 plt)和 NumPypyplotMatplotlib 的高级接口,提供了类似 MATLAB 的绘图函数,是日常绘图最常用的模块。
  • 第4行np.linspace(0, 2*np.pi, 100) 生成从 0 到 2π 的 100 个等间距点,这是 NumPy 的核心函数,非常适合生成连续变化的 x 轴数据。
  • 第5行np.sin(x) 计算 x 数组中每个元素的正弦值,返回对应的 y 数组。NumPy 的数学运算会自动应用到数组的每个元素,无需循环。
  • 第8行plt.subplots(figsize=(8, 4)) 同时创建 Figure(画布)和 Axes(坐标轴)对象。figsize 参数设置画布大小为 8 英寸宽、4 英寸高。推荐使用 subplots() 而非单独创建,因为它更高效且符合面向对象风格。
  • 第9行ax.plot(x, y)Axes 对象上绘制折线图。这是最核心的绘图函数,将 x 和 y 数组连接成一条平滑的曲线。
  • 第12-14行set_title()set_xlabel()set_ylabel() 分别设置图表标题、x 轴标签和 y 轴标签。所有以 set_ 开头的方法都是在配置 Axes 的属性。
  • 第17行plt.show() 弹出窗口显示图表。在 Jupyter Notebook 中,可以省略这行代码直接在单元格中显示。

预期输出:运行后会弹出一个窗口,展示一条波浪状的正弦曲线,x 轴范围是 0 到 2π,y 轴范围是 -1 到 1,曲线从原点出发,先上升到 1(π/2 处),下降到 -1(3π/2 处),最后回到 0(2π 处)。

解决中文显示问题

Matplotlib 默认不支持中文,会导致中文显示为方块。需要在导入后添加以下配置:

import matplotlib.pyplot as plt
import matplotlib

# 设置中文字体(Windows 用 SimHei,Mac 用 Arial Unicode MS)
plt.rcParams['font.sans-serif'] = ['SimHei']
# 解决负号显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False

3. 核心概念解析

理解 Matplotlib 的核心概念是掌握它的关键。新手容易混淆的主要是以下四个对象,它们之间的关系就像画画工具的层级:

3.1 Figure(画布)

Figure 是整个图表的容器,相当于一张白纸或画框。一个 Figure 可以包含多个 Axes(子图),它负责管理整个图像的尺寸、背景色、边框等全局属性。你可以把 Figure 想象成一个画板,所有的图表元素都画在这个画板上。

fig = plt.figure(figsize=(10, 6), facecolor='lightgray')

3.2 Axes(坐标轴/子图)

Axes 是实际绘图的区域,每个 Axes 都包含独立的坐标系(x 轴、y 轴)、标题、标签、图例等元素。一个 Figure 可以有多个 Axes(比如 2×2 的子图布局),但每个 Axes 只能属于一个 Figure。你可以把 Axes 想象成画板上的一个画框,具体的线条、点、文字都画在这个画框里。

fig, ax = plt.subplots()  # 创建包含一个 Axes 的 Figure
fig, axs = plt.subplots(2, 2)  # 创建包含 2×2 个 Axes 的 Figure

3.3 Axis(坐标轴对象)

每个 Axes 包含两个(或 3D 图中的三个)Axis 对象,分别代表 x 轴和 y 轴。Axis 负责控制刻度(ticks)、刻度标签(tick labels)、坐标轴范围(limits)等。比如 x 轴的刻度位置是 0、π/2、π、3π/2、2π,刻度标签就是对应的数字。

ax.set_xlim(0, 10)  # 设置 x 轴范围
ax.set_xticks([0, 5, 10])  # 设置 x 轴刻度位置
ax.set_xticklabels(['起点', '中点', '终点'])  # 设置刻度标签

3.4 Artist(艺术家对象)

Artist 是所有可见元素的统称,包括线条(Line2D)、文本(Text)、矩形(Rectangle)、图例(Legend)等。FigureAxesAxis 本身也是 Artist。当调用 plt.show()plt.savefig() 时,所有 Artist 会被渲染到画布上。

line, = ax.plot([1, 2, 3], [4, 5, 6])  # line 是一个 Line2D Artist
title = ax.set_title("标题")  # title 是一个 Text Artist

核心概念关系图

以下 Mermaid 图表展示了这些核心对象之间的层次关系:

graph TD
    A[Figure<br/>画布容器] --> B[Axes<br/>绘图区域1]
    A --> C[Axes<br/>绘图区域2]
    A --> D[Axes<br/>绘图区域N]
    B --> E[Axis X<br/>X轴对象]
    B --> F[Axis Y<br/>Y轴对象]
    B --> G[Line2D<br/>线条]
    B --> H[Text<br/>标题/标签]
    B --> I[Legend<br/>图例]
    E --> J[刻度]
    E --> K[刻度标签]
    F --> L[刻度]
    F --> M[刻度标签]

这个图清晰地展示了:

  • Figure 是最顶层容器,可以包含多个 Axes
  • 每个 Axes 包含 Axis 对象和具体的 Artist 元素
  • Axis 负责刻度和标签管理
  • 所有的 Artist 最终渲染到 Figure

记住一句话:我们绘图时,先创建 Figure,再在 Figure 上添加 Axes,最后在 Axes 上调用绘图方法(如 plot()scatter()bar()),然后通过 set_xxx() 方法配置样式,最后用 plt.show()plt.savefig() 展示或保存图表。

4. 实战演练:分析电影评分趋势

需求分析

假设我们有一份电影数据集,包含电影类型、评分、上映年份等信息。我们需要分析不同类型电影的平均评分趋势,找出评分最高和最低的电影类型,并用可视化方式展示结果。这个任务涉及数据统计、多系列折线图绘制、图例和标签设置等核心技能。

方案设计

我们将按以下步骤实现:

  1. 生成模拟数据(包含电影类型、评分、年份)
  2. 按类型和年份分组计算平均评分
  3. 使用 Matplotlib 绘制多系列折线图,每种类型一条曲线
  4. 添加图例、标题、标签,美化图表样式
  5. 保存为高清图片

这个案例将练习以下核心功能:DataFrame 分组统计、subplots 多图布局、plot 折线图、图例和标签设置、样式定制、图片保存。

完整代码实现

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

# ===== 步骤1:生成模拟数据 =====
np.random.seed(42)  # 确保结果可复现

# 电影类型列表
genres = ['剧情', '动作', '喜剧', '科幻', '恐怖', '爱情']
n_movies = 1000  # 总电影数

# 生成随机数据
data = {
    'genre': np.random.choice(genres, n_movies),
    'year': np.random.randint(2010, 2024, n_movies),
    'rating': np.random.uniform(3.0, 9.0, n_movies)  # 评分3.0-9.0
}
df = pd.DataFrame(data)

# ===== 步骤2:数据统计 =====
# 按类型和年份分组,计算平均评分
grouped = df.groupby(['genre', 'year'])['rating'].mean().reset_index()

# 将数据转换为更适合绘图的格式:每种类型一个 Series
pivot_data = grouped.pivot(index='year', columns='genre', values='rating')

# ===== 步骤3:创建图表 =====
fig, ax = plt.subplots(figsize=(12, 6))

# 为每种类型绘制一条曲线,使用不同颜色和标记
colors = plt.cm.tab10(np.linspace(0, 1, len(genres)))
markers = ['o', 's', '^', 'D', 'v', 'p']

for i, genre in enumerate(genres):
    if genre in pivot_data.columns:
        ax.plot(pivot_data.index, pivot_data[genre],
                color=colors[i],
                marker=markers[i],
                markersize=6,
                linewidth=2,
                label=genre)

# ===== 步骤4:美化图表 =====
ax.set_title('2010-2023年各类型电影平均评分趋势',
             fontsize=16, pad=20)
ax.set_xlabel('年份', fontsize=12)
ax.set_ylabel('平均评分', fontsize=12)

# 设置 x 轴刻度为每年一个
ax.set_xticks(range(2010, 2024))
ax.set_xticklabels([str(year) for year in range(2010, 2024)],
                   rotation=45, ha='right')

# 设置 y 轴范围,突出差异
ax.set_ylim(3.0, 9.0)
ax.grid(True, linestyle='--', alpha=0.3)

# 添加图例
ax.legend(loc='upper left', fontsize=10, ncol=3)

# 添加参考线(平均分)
avg_rating = df['rating'].mean()
ax.axhline(y=avg_rating, color='red', linestyle=':',
           linewidth=1.5, label=f'总体平均分 ({avg_rating:.2f})')

# ===== 步骤5:保存和显示 =====
plt.tight_layout()  # 自动调整布局,避免标签被截断
plt.savefig('movie_rating_trend.png', dpi=300, bbox_inches='tight')
print("图表已保存为 movie_rating_trend.png")
plt.show()

运行说明

  1. 将上述代码保存为 movie_analysis.py 文件
  2. 确保已安装依赖:pip install matplotlib numpy pandas
  3. 运行命令:python movie_analysis.py
  4. 程序会弹出窗口显示图表,并在当前目录下生成 movie_rating_trend.png 高清图片

结果展示

生成的图表将展示:

  • 6条折线:每种电影类型一条曲线,用不同颜色和标记区分
  • x 轴:2010-2023 年,每年一个刻度,标签旋转 45 度避免重叠
  • y 轴:评分范围 3.0-9.0,突出评分差异
  • 红色虚线:总体平均分参考线,便于对比
  • 图例:显示所有类型和参考线,位于左上角,分 3 列排列
  • 网格线:浅灰色虚线,辅助读取数据

这个案例展示了 Matplotlib 的核心能力:数据处理与可视化的无缝结合、多系列图表绘制、样式精细控制、专业级图表输出。掌握了这些技能,你就能应对大多数数据可视化任务。

5. 最佳实践与常见陷阱

常见错误及规避方法

错误1:混淆 FigureAxes

问题描述:直接使用 plt.plot() 绘图,却不知道"画在哪个 Axes 上",导致多图布局混乱。

# ❌ 错误做法:使用 pyplot 状态机,难以控制
plt.plot(x, y1)  # 自动创建 fig1 和 ax1
plt.figure()     # 新建 fig2
plt.plot(x, y2)  # 画在 fig2 的 ax2 上,但 ax1 无法再修改
# ✅ 正确做法:手动创建 Axes,精准控制
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))
ax1.plot(x, y1)
ax1.set_title('图表1')
ax2.plot(x, y2)
ax2.set_title('图表2')

原因plt 是便捷接口,会自动创建和管理对象,但复杂绘图时容易失控。面向对象风格更清晰、更可控。

错误2:保存图表的顺序错误

问题描述:先 plt.show()plt.savefig(),保存的是空白图片!

# ❌ 错误做法
plt.show()           # 弹出窗口并释放资源
plt.savefig('plot.png')  # 此时 Figure 已为空,保存空白
# ✅ 正确做法
plt.savefig('plot.png', dpi=300, bbox_inches='tight')  # 先保存
plt.show()            # 再显示

原因plt.show() 会弹出窗口并释放绘图资源,之后再调用 savefig()Figure 已为空。必须先保存再显示。

错误3:中文显示乱码

问题描述:图表中的中文显示为方块,无法识别。

# ❌ 错误做法:未配置字体
plt.title('电影评分趋势')  # 显示为方块
# ✅ 正确做法:配置中文字体
import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif'] = ['SimHei']  # Windows 用黑体
# plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']  # Mac 用这个
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示为方块
plt.title('电影评分趋势')  # 正确显示中文

原因Matplotlib 默认字体不支持中文,axes.unicode_minus 也需设置为 False 否则负号会乱码。

错误4:误解 figsize 的单位

问题描述:以为 figsize=(8, 4) 表示 8 像素×4 像素,结果图片太小。

# ❌ 错误理解
fig = plt.figure(figsize=(8, 4))  # 不是 8×4 像素!
# ✅ 正确理解
fig = plt.figure(figsize=(8, 4), dpi=100)  # 8英寸×4英寸,dpi=100,实际是 800×400 像素
# 想要 800×400 像素,要么设置 dpi=100,要么设置 figsize=(8, 4) 且 dpi=100

原因figsize 的单位是"英寸",而非像素。最终像素数 = figsize × dpi(默认 dpi=100)。

最佳实践建议

  1. 优先使用面向对象接口:虽然 plt.plot() 更简洁,但复杂场景(如多子图、自定义样式)必须用 fig, ax = plt.subplots() 面向对象风格。
  2. 统一配置字体和样式:在脚本开头一次性设置 rcParams,避免每个图表都重复配置。
  3. 养成使用 tight_layout() 的习惯:自动调整子图间距,避免标签被截断。
  4. 合理设置 dpi 参数:保存图片时 dpi=300 适合打印,dpi=150 适合屏幕显示,dpi=72 适合网页。
  5. 利用 colormaps 自动生成配色:不要手动指定颜色列表(如 ['red', 'blue', 'green']),用 plt.cm.tab10plt.cm.viridis 生成专业配色。
  6. 保存图片时使用 bbox_inches='tight':自动裁剪空白边距,让图片更紧凑。
  7. 多图布局时用 subplots_adjust 微调:当 tight_layout() 不能满足需求时,手动调整 left, right, top, bottom, wspace, hspace 参数。
  8. 避免使用过时的 API:如 plt.axes() 已被 plt.subplots() 替代,plt.hold() 已在新版本中移除。

6. 进阶指引

掌握了基础用法后,你可以继续探索 Matplotlib 的高级功能和生态系统:

高级功能

  • 多子图复杂布局:使用 plt.subplot_mosaic() 创建非网格状布局(如左大右小、上一下三等)
  • 3D 可视化:使用 mpl_toolkits.mplot3d 绘制三维曲面图、散点图
  • 动画制作:使用 matplotlib.animation 模块制作动态图表,展示数据变化过程
  • 交互式可视化:结合 ipywidgets 在 Jupyter Notebook 中实现滑块、下拉框等交互控件

生态扩展

  • Seaborn:基于 Matplotlib 的高级库,提供更简洁的 API 和更美观的默认样式,适合快速生成统计图表
  • Plotly:专注于交互式可视化,生成的图表支持缩放、拖拽、悬停查看数据,适合网页展示
  • Cartopy:地理数据可视化,支持地图投影、地理坐标转换等

学习资源

学习路径建议

  1. 第一阶段(1-2周):熟练掌握折线图、柱状图、散点图、饼图、直方图 5 种基础图表
  2. 第二阶段(2-3周):学会多子图布局、样式定制、图例标签设置
  3. 第三阶段(3-4周):尝试 3D 可视化、动画制作、交互式图表
  4. 第四阶段(持续):结合实际项目(如个人数据分析、Kaggle 比赛),在实战中积累经验

记住:Matplotlib 的核心是"多动手实践"。找一份真实数据(如公开数据集、个人消费记录),尝试用不同图表展示,逐步掌握参数调整和样式优化。从基础图表到专业可视化,Matplotlib 能伴随你从数据分析新手成长为可视化高手。

对于开发者而言,最痛苦的不是写不出策略,而是受限于基础设施的性能。如果你还在用 requests 轮询接口获取股票价格,那你基本上已经告别实时性要求较高的金融场景了。

今天我们就从工程化的角度,聊聊如何用 Python 优雅地解决港股实时行情的接入问题。

痛点分析:HTTP vs WebSocket 在传统的 Web 开发中,我们习惯了无状态的 HTTP。但在金融数据领域,高频的握手开销是不可接受的。我们需要全双工通信,Server 端有数据变动直接 Push 给 Client。

技术选型与环境依赖 我们追求的是极致的轻量化。Python 3.9+ 配合 websocket-client 是目前性价比最高的方案。它足够底层,让你能控制每一个字节的流向,又不需要像 asyncio 那样处理复杂的时间循环(当然,如果你需要极高并发,后期可以重构)。

pip install websocket-client

核心代码实现 不管是你是对接交易所直连,还是使用像 AllTick 这样集成的三方 API,核心范式都是一样的:定义 on_message、on_open 等回调函数。

下面的代码片段展示了如何建立一个持久化的 WebSocket 连接。注意看,我们在 on_open 阶段发送了 JSON 格式的订阅 payload,这是目前主流金融 API 的标准交互方式。

import websocket
import json

url = "wss://api.alltick.co/realtime/hk"

def on_message(ws, message):
    data = json.loads(message)
    # 打印最新成交价和涨跌情况
    print(f"{data['symbol']} 最新价格: {data['last_price']} 涨跌: {data['change']}")

def on_open(ws):
    # 订阅恒生指数及指定股票行情
    ws.send(json.dumps({
        "action": "subscribe",
        "symbols": ["HSI", "00700.HK"]
    }))

ws = websocket.WebSocketApp(url, on_message=on_message, on_open=on_open)
ws.run_forever()

数据流的下游处理 原始数据通常是 JSON 字符串,直接解析的开销很小。在生产环境中,我建议你拿到数据后不要直接 print,而是通过消息队列(如 Kafka)或者直接落库。但为了演示方便,我们这里直接用 Pandas 做一个简单的内存化清洗。

import pandas as pd

# 假设我们有一个行情列表
ticks = [
    {"time": "09:30:01", "price": 500, "volume": 100},
    {"time": "09:30:02", "price": 502, "volume": 50},
    {"time": "09:30:03", "price": 501, "volume": 80},
]

df = pd.DataFrame(ticks)
df['time'] = pd.to_datetime(df['time'])
print(df)

经验总结 通过这种方式,我们将数据的获取延迟从“秒级”压缩到了“毫秒级”。在处理港股这种波动剧烈的市场时,这种技术架构的升级,能让你的程序在起跑线上就领先别人一个身位。

目录

  1. 库的概览与核心价值
  2. 环境搭建与"Hello, World"
  3. 核心概念解析
  4. 实战演练:解决一个典型问题
  5. 最佳实践与常见陷阱
  6. 进阶指引

1. 库的概览与核心价值

想象一下,在数据科学的战场上,如果缺少高效的数值计算能力,就像厨师缺少了锋利的刀具——你依然可以切菜,但效率低下且难以处理复杂的食材。NumPy 正是为解决科学计算中的效率瓶颈而生的工具。

NumPy(Numerical Python)是 Python 科学计算生态系统的核心基石,它提供了高性能的多维数组对象和用于处理这些数组的工具。在 Python 生态中,NumPy 的地位类似于建筑物的地基——虽然平时不常被直接看到,但几乎所有上层的数据科学库(如 Pandas、Scikit-learn、TensorFlow)都构建在 NumPy 之上。

NumPy 解决的核心问题是在 Python 中进行大规模数值计算时的性能瓶颈。通过提供连续内存存储的数组和向量化操作,NumPy 将计算速度提升了几个数量级,让 Python 在科学计算领域具备了与 C、Fortran 等编译型语言竞争的能力。无论是处理百万级的数据集,还是进行复杂的矩阵运算,NumPy 都是不可或缺的工具。


2. 环境搭建与"Hello, World"

安装说明

NumPy 的安装非常简单,推荐使用以下方式:

使用 pip 安装:

pip install numpy

使用 conda 安装(推荐用于 Anaconda 用户):

conda install numpy

验证安装:

python -c "import numpy; print(numpy.__version__)"

常见安装问题:如果安装过程中出现权限错误,请使用 --user 参数;如果网络不稳定,考虑使用国内镜像源。

Hello, World 示例

让我们从一个最简单的示例开始,体验 NumPy 的核心功能:

import numpy as np

# 创建一个包含5个元素的一维数组
arr = np.array([1, 2, 3, 4, 5])

# 对数组中的每个元素进行平方运算
squared = arr ** 2

print(f"原始数组: {arr}")
print(f"平方结果: {squared}")
print(f"平均值: {np.mean(arr)}")

逐行解释:

  1. import numpy as np:导入 NumPy 库并使用 np 作为别名,这是社区的通用约定
  2. arr = np.array([1, 2, 3, 4, 5]):创建一个 NumPy 数组对象,这是 NumPy 最核心的数据结构
  3. squared = arr ** 2:使用向量化操作对数组中所有元素进行平方,无需循环
  4. np.mean(arr):计算数组的平均值,这是 NumPy 提供的众多统计函数之一

预期输出:

原始数组: [1 2 3 4 5]
平方结果: [ 1  4  9 16 25]
平均值: 3.0

这个简单的示例展示了 NumPy 的三个关键特性:数组创建、向量化运算和内置数学函数。


3. 核心概念解析

NumPy 的强大建立在几个核心概念之上,理解这些概念是掌握 NumPy 的关键。

3.1 ndarray:多维数组对象

ndarray(n-dimensional array)是 NumPy 的核心数据结构,它是一个同质的多维容器,其中所有元素必须是相同类型。与 Python 原生列表相比,ndarray 在内存中是连续存储的,这使得访问速度更快,也支持向量化操作。

关键特性:

  • 维度(ndim):数组的维度数量,如一维、二维、三维等
  • 形状(shape):每个维度上的元素数量,如 (3, 4) 表示3行4列
  • 数据类型(dtype):数组中元素的类型,如 int32float64

3.2 广播机制

广播是 NumPy 的魔法机制,它允许不同形状的数组进行算术运算。当操作两个数组时,NumPy 会自动将较小的数组"广播"到较大数组的形状上,而无需显式复制数据。

广播规则:

  1. 如果两个数组的维度数不同,则在较小数组的形状前面补1
  2. 如果两个数组的形状在某个维度上不匹配,但其中一个为1,则扩展为匹配
  3. 如果所有维度都匹配或其中一个为1,则广播成功,否则报错

3.3 向量化运算

向量化是指用数组表达式代替显式循环来处理数据。NumPy 的向量化运算底层使用 C 语言实现,比 Python 循环快几十倍甚至上百倍。

概念关系图:

graph TD
    A[ndarray 多维数组] --> B[连续内存存储]
    A --> C[统一数据类型]
    A --> D[维度与形状属性]
    
    B --> E[高效内存访问]
    C --> F[类型优化计算]
    D --> G[灵活数据组织]
    
    E --> H[向量化运算]
    F --> H
    G --> H
    
    H --> I[广播机制]
    H --> J[性能优化]
    
    I --> K[灵活数组运算]
    J --> L[大规模数据处理能力]
    
    K --> M[科学计算应用]
    L --> M

这三个概念相互配合,构成了 NumPy 高效计算的基础:ndarray 提供了数据容器,向量化运算提供了高效操作,而广播机制则增强了运算的灵活性。


4. 实战演练:解决一个典型问题

让我们通过一个实际项目来体验 NumPy 的强大功能。我们将构建一个简单的数据分析工具,分析某公司过去12个月的销售额数据,计算统计指标并识别销售趋势。

需求分析

我们需要:

  1. 处理12个月的销售额数据(单位:万元)
  2. 计算基本统计信息:平均值、标准差、最大最小值
  3. 计算移动平均值以平滑数据
  4. 识别异常销售月份(超过平均值2个标准差)
  5. 计算环比增长率

方案设计

选择 NumPy 的原因:

  • 数组创建:快速构造销售数据数组
  • 统计函数:内置 meanstdmaxmin 等函数
  • 数组切片:高效提取数据子集
  • 布尔索引:快速筛选异常数据
  • 向量化运算:高效计算增长率

代码实现

import numpy as np

# 步骤1:创建销售数据(模拟12个月的销售数据)
monthly_sales = np.array([120, 135, 128, 142, 156, 148, 163, 175, 169, 182, 195, 188])

# 步骤2:计算基本统计信息
mean_sales = np.mean(monthly_sales)
std_sales = np.std(monthly_sales)
max_sales = np.max(monthly_sales)
min_sales = np.min(monthly_sales)

print("=== 基本统计信息 ===")
print(f"平均销售额: {mean_sales:.2f} 万元")
print(f"标准差: {std_sales:.2f} 万元")
print(f"最高销售额: {max_sales} 万元")
print(f"最低销售额: {min_sales} 万元")

# 步骤3:计算3个月移动平均值
window_size = 3
moving_avg = np.convolve(monthly_sales, np.ones(window_size)/window_size, mode='valid')

print(f"\n=== {window_size}个月移动平均值 ===")
for i, avg in enumerate(moving_avg):
    print(f"{i+1}-{i+window_size}月: {avg:.2f} 万元")

# 步骤4:识别异常月份(超过平均值2个标准差)
threshold = mean_sales + 2 * std_sales
abnormal_months = np.where(monthly_sales > threshold)[0]

print(f"\n=== 异常销售月份(超过{threshold:.2f}万元)===")
if len(abnormal_months) > 0:
    for month_idx in abnormal_months:
        print(f"{month_idx + 1}月: {monthly_sales[month_idx]}万元")
else:
    print("无异常月份")

# 步骤5:计算环比增长率
growth_rates = np.diff(monthly_sales) / monthly_sales[:-1] * 100

print(f"\n=== 环比增长率 ===")
for i, rate in enumerate(growth_rates):
    print(f"{i+2}月相对于{i+1}月: {rate:+.2f}%")

# 步骤6:整体趋势分析
overall_trend = np.polyfit(range(len(monthly_sales)), monthly_sales, 1)[0]
print(f"\n=== 整体趋势 ===")
print(f"月均增长: {overall_trend:.2f} 万元")
if overall_trend > 0:
    print("趋势: 上升")
else:
    print("趋势: 下降")

运行说明

将上述代码保存为 sales_analysis.py,然后在命令行运行:

python sales_analysis.py

结果展示

程序将输出完整的销售数据分析报告:

=== 基本统计信息 ===
平均销售额: 158.33 万元
标准差: 24.17 万元
最高销售额: 195 万元
最低销售额: 120 万元

=== 3个月移动平均值 ===
1-3月: 127.67 万元
2-4月: 135.00 万元
3-5月: 142.00 万元
4-6月: 148.67 万元
5-7月: 155.67 万元
6-8月: 162.00 万元
7-9月: 169.00 万元
8-10月: 175.33 万元
9-11月: 182.00 万元
10-12月: 188.33 万元

=== 异常销售月份(超过206.67万元)===
无异常月份

=== 环比增长率 ===
2月相对于1月: +12.50%
3月相对于2月: -5.19%
4月相对于3月: +10.94%
5月相对于4月: +9.86%
6月相对于5月: -5.13%
7月相对于6月: +10.14%
8月相对于7月: +7.36%
9月相对于8月: -3.43%
10月相对于9月: +7.69%
11月相对于10月: +7.14%
12月相对于11月: -3.59%

=== 整体趋势 ===
月均增长: 5.86 万元
趋势: 上升

这个实战项目展示了 NumPy 在数据分析中的典型应用:数据创建、统计计算、滑动窗口、条件筛选、趋势分析等。所有操作都通过向量化运算完成,代码简洁且高效。


5. 最佳实践与常见陷阱

常见错误与规避方法

错误1:数据类型不一致导致的精度丢失

# ❌ 错误做法
arr = np.array([1.5, 2.7, 3.9], dtype=int)  # 强制转换为整数,丢失小数部分
print(arr)  # 输出: [1 2 3]

# ✅ 正确做法
arr = np.array([1.5, 2.7, 3.9])  # 保持默认的float64类型
print(arr)  # 输出: [1.5 2.7 3.9]

错误2:数组视图与拷贝混淆

# ❌ 错误做法:误以为切片创建了新数组
original = np.array([1, 2, 3, 4, 5])
slice_view = original[1:4]
slice_view[0] = 99
print(original)  # 输出: [ 1 99  3  4  5] - 原数组被修改!

# ✅ 正确做法:显式创建拷贝
original = np.array([1, 2, 3, 4, 5])
slice_copy = original[1:4].copy()
slice_copy[0] = 99
print(original)  # 输出: [1 2 3 4 5] - 原数组保持不变

错误3:不合理的循环使用

# ❌ 错误做法:使用 Python 循环处理数组
arr = np.random.rand(1000000)
result = np.zeros_like(arr)
for i in range(len(arr)):
    result[i] = arr[i] * 2 + 1

# ✅ 正确做法:使用向量化运算
result = arr * 2 + 1

最佳实践建议

1. 内存优化:
对于大型数组,使用合适的数据类型可以显著减少内存占用:

# 对于0-255的整数,使用uint8而非默认的int64
small_integers = np.array([1, 2, 3, 255], dtype=np.uint8)

2. 预分配数组:
在循环中预分配数组比动态扩展更高效:

# ✅ 预分配
result = np.zeros(1000)
for i in range(1000):
    result[i] = calculate_value(i)

3. 利用广播机制:
合理使用广播可以避免不必要的数据复制:

# 将一维数组广播到二维数组
data = np.random.rand(5, 3)
row_means = data.mean(axis=1, keepdims=True)
normalized = data - row_means  # 广播减法

4. 使用掩码数组处理缺失值:

data = np.array([1, 2, np.nan, 4, 5])
masked_data = np.ma.masked_invalid(data)
mean_value = masked_data.mean()  # 自动忽略NaN值

注意事项

  • 当处理超过内存大小的数据时,考虑使用内存映射文件(np.memmap
  • 在多线程环境中使用 NumPy 时要注意 GIL(全局解释器锁)的影响
  • 对于超大规模数据,考虑使用 Dask 或 Spark 等分布式计算框架
  • 定期检查 NumPy 版本更新,新版本通常包含性能优化和新功能

6. 进阶指引

掌握了 NumPy 的基础用法后,你可以探索以下高级特性和相关生态:

高级功能

结构化数组: 允许存储异构数据,类似数据库表格

dt = np.dtype([('name', 'U10'), ('age', 'i4'), ('salary', 'f8')])
employees = np.array([('张三', 30, 8000.5), ('李四', 25, 6500.0)], dtype=dt)

ufunc(通用函数): 自定义向量化函数

def custom_operation(x, y):
    return x * 2 + y ** 2

vectorized_func = np.frompyfunc(custom_operation, 2, 1)
result = vectorized_func(arr1, arr2)

生态扩展

  • Pandas: 构建在 NumPy 之上的数据分析库,提供更高级的数据结构和分析工具
  • SciPy: 科学计算工具集,包含优化、积分、线性代数等功能
  • Matplotlib: 基于 NumPy 数组的绘图库,与 NumPy 无缝集成
  • Scikit-learn: 机器学习库,其核心算法都依赖 NumPy 数组

学习路径

  1. 深入理解数组操作: 掌握高级索引、排序、形状操作等
  2. 学习线性代数: 深入理解矩阵运算、特征值、奇异值分解等
  3. 性能优化: 学习如何编写高效的 NumPy 代码,避免性能陷阱
  4. 专业领域应用: 根据需要深入学习信号处理、图像处理、金融计算等领域的 NumPy 应用

推荐资源

  • 官方文档: https://numpy.org/doc/ - 最权威的信息来源
  • NumPy 用户指南: 包含详细教程和最佳实践
  • 《Python for Data Analysis》 by Wes McKinney - 深入理解 NumPy 和 Pandas
  • Stack Overflow NumPy 标签: 解决实际问题的社区资源

NumPy 的学习曲线相对平缓,但要真正精通需要持续的实践和探索。建议在项目中不断应用新学到的技巧,通过实际问题的解决来加深理解。随着你对 NumPy 的掌握程度加深,你会发现它不仅仅是一个计算工具,更是一种思维方式——用向量化、广播化的方式思考问题。

本工具仅限学术交流使用,严格遵循相关法律法规,符合平台内容的合法及合规性,禁止用于任何商业用途!

1. 项目背景与核心功能整合

开发初衷

小红书作为国内头部的社区种草平台,其海量笔记数据蕴含着极高的商业与学术价值。此前,为了满足不同场景的采集需求,我曾分别开发了针对评论、博主主页以及UID转换的三款独立工具。然而,许多用户反馈在处理复杂任务(如同时采集评论和主页笔记)时,频繁切换软件带来了操作上的不便。

为了解决这一痛点,我将上述三个核心模块进行了深度融合,推出了全新的 “爬小红书聚合软件v1.0”。这是一款集成了“评论采集”、“达人笔记采集”及“UID转换”的一体化数据解决方案。

适用场景

本工具严格遵循相关法律法规,仅限于学术交流与合规性研究,具体适用场景包括:

  • 获客截流: 从行业热门作品评论区精准挖掘目标用户画像。
  • 舆情分析: 用于社会舆情挖掘、网络传播规律等学术研究。
  • 内容优化: 辅助内容创作者分析优质博主风格与热门话题。
  • 运营辅助: 解决跨平台协作中链接与ID转换的痛点。

2. 技术架构与实现逻辑

本软件完全由 Python 语言独立开发,采用模块化设计以保证高效运行与维护。

核心模块分工

序号模块名称功能描述
1tkinter构建GUI图形用户界面
2requests负责发送HTTP请求
3json解析服务器返回的响应数据
4pandas处理并保存为CSV数据结果
5logging记录运行日志,便于异常回溯

核心代码实现

以下是软件中处理数据请求与保存的关键代码片段:

发送请求与解析:

# 发送请求
r = requests.get(url, headers=h1, params=params)
# 解析数据
json_data = r.json()

数据解析示例(评论内容):

for c in json_data['data']['comments']: 
    # 评论内容 
    content = c['content'] 
    self.tk_show('评论内容:' + str(content)) 
    content_list.append(content)

数据保存至CSV:

# 保存数据到DF
df = pd.DataFrame( {  
    '笔记链接': 'https://www.xiaohongshu.com/explore/' + note_id,  
    '笔记链接_长': note_url2,  
    '页码': page,  
    '评论者昵称': nickname_list,  
    '评论者id': user_id_list,  
    '评论者主页链接': user_link_list,  
    '评论时间': create_time_list,  
    '评论IP属地': ip_list,  
    '评论点赞数': like_count_list,  
    '评论级别': comment_level_list,  
    '评论内容': content_list, })
# 设置csv文件表头
if os.path.exists(self.result_file3): 
    header = False
else: 
    header = True
# 保存到csv
df.to_csv(self.result_file3, mode='a+', header=header, index=False, encoding='utf_8_sig')
self.tk_show('文件保存成功:' + self.result_file3)

采用logging模块记录日志运行过程,方便debug回溯场景:

def get_logger(self):    
    self.logger = logging.getLogger(__name__)    
    # 日志格式
    formatter = '[%(asctime)s-%(filename)s][%(funcName)s-%(lineno)d]--%(message)s'    
    # 日志级别
    self.logger.setLevel(logging.DEBUG)    
    # 控制台日志
    sh = logging.StreamHandler()    
    log_formatter = logging.Formatter(formatter, datefmt='%Y-%m-%d %H:%M:%S')    
    # info日志文件名
    info_file_name = time.strftime("%Y-%m-%d") + '.log'    
    # 将其保存到特定目录
    case_dir = r'./logs/'    
    info_handler = TimedRotatingFileHandler(filename=case_dir + info_file_name,                                        
                                          when='MIDNIGHT',                                        
                                          interval=1,                                        
                                          backupCount=7,                                        
                                          encoding='utf-8')

3. 功能详解与数据产出

本软件通过接口协议进行数据交互,相比模拟浏览器(RPA)具有更高的稳定性。采集过程中,系统会实时(每页请求间隔1~2s)将数据存入CSV文件,有效防止因网络异常导致的数据丢失。

功能一:搜索笔记与评论采集

该模块支持根据关键词或笔记链接采集评论区数据。在这里插入图片描述

  • 笔记数据字段(19个): 包含关键词、笔记ID、标题、正文、点赞/收藏/评论数、发布时间及IP属地等。
  • 评论数据字段(11个): 包含评论者昵称/ID、评论内容、点赞数、IP属地及评论级别等。
  • 多媒体支持: 自动下载搜索到的笔记封面图片。

功能二:博主主页笔记采集

支持根据博主主页链接批量抓取其发布的历史笔记。在这里插入图片描述

  • 采集字段(18个): 包含作者信息、笔记ID、链接、类型、互动数据及正文内容等。
  • 结果展示: 生成结构化的CSV文件及对应的图片素材包。

功能三:UID与链接转换工具

提供高频使用的转换功能,无需打开网页即可完成:在这里插入图片描述

  1. 主页链接 $\leftrightarrow$ 小红书号(xhs号)互转。
  2. App端作品链接 $\rightarrow$ PC端作品链接转换。

4. 使用指南

前置准备

  • 在开始采集前,用户需获取并填写自己的Cookie值。
  • 打开浏览器开发者工具(F12),复制Cookie值。
  • 将其粘贴至软件同级目录下的 cookie.txt 文件中。

操作流程

  • 登录界面: 启动软件并完成登录验证。
  • 选择模块: 根据需求选择“搜索采集”、“主页采集”或“转换工具”。
  • 配置参数: 填写关键词、时间范围或博主链接等信息。
  • 执行任务: 点击「开始执行」,实时监控进度条。
  • 查看结果: 任务完成后,在软件所在文件夹查看生成的CSV文件及图片文件夹。

5.演示视频

为了方便用户上手,附带了完整的操作演示视频:

mp.weixin.qq.com/s/t9cKGsgJoI9rca3I1w5RdA

END. 版权声明

本软件及文章均为本人独立原创开发与编写。请尊重原创成果,严禁任何形式的二创、转载或盗发,违者必究!