2023年3月

前言

最近调研了一下我做的项目受欢迎程度,大数据分析方向竟然排第一,尤其是这两年受疫情影响,大家都非常担心自家公司裁员或倒闭,都想着有没有其他副业搞搞或者炒炒股、投资点理财产品,未雨绸缪,所以不少小伙伴要求我这边分享下关于股票预测分析的技巧。

基于股票数据是一个和时间序列相关的大数据,所以我打算给大家分享时下最受欢迎的时序模型:
LSTM、XGBoost两大经典模型

@



一、模型简介

1.1 LSTM神经网络模型

根据
百度百科定义
:长短期记忆网络(LSTM,Long Short-Term Memory)是一种时间循环神经网络,是为了解决一般的RNN(循环神经网络)存在的长期依赖问题而专门设计出来的,所有的RNN都具有一种重复神经网络模块的链式形式。在标准RNN中,这个重复的结构模块只有一个非常简单的结构,例如一个tanh层。

LSTM 能够进行一次多步预测,对于时间序列预测有一定的参考价值。LSTM的难点在于配置不方便。
在这里插入图片描述
在这里插入图片描述

1.2 XGBoost机器学习模型

XGBoost全称是eXtreme Gradient Boosting,根据
百度百科定义
:XGBoost是一个优化的分布式梯度增强库,旨在实现高效,灵活和便携。它在 Gradient Boosting 框架下实现机器学习算法。XGBoost提供并行树提升(也称为GBDT,GBM),可以快速准确地解决许多数据科学问题。相同的代码在主要的分布式环境(Hadoop,SGE,MPI)上运行,并且可以解决数十亿个示例之外的问题。

可能大家光看定义,不能太理解XGBoost的牛逼之处,了解过Kaggle竞赛的小伙伴应该知道,由于XGBoost 库专注于计算速度和模型性能,因此几乎没有多余的装饰,算法的实现也旨在提高计算时间和内存资源的效率,所以它目前已经在 Kaggle 竞赛数据科学平台上成为竞赛获胜者的首选算法。

例如,有一个不完整的一、二、三等奖获奖名单,标题为: XGBoost: Machine Learning Challenge Winning Solutions。

为了使这一点更加具体,以下是 Kaggle 竞赛获胜者的一些有见地的引述:

作为越来越多的 Kaggle 比赛的获胜者,XGBoost 再次向我们展示了它是一种出色的全能算法。

——拿督优胜者访谈:第一名,疯狂教授

如有疑问,请使用 xgboost。

— Avito 优胜者访谈:第一名,Owen Zhang

我喜欢表现良好的单个模型,我最好的单个模型是 XGBoost,它可以单独获得第 10 名。

—卡特彼勒获奖者访谈:第一名

我只用过 XGBoost。

— Liberty Mutual Property Inspection,优胜者访谈:第一名,王清臣

我使用的唯一监督学习方法是梯度提升,在优秀的 xgboost 中实现。

— Recruit Coupon Purchase 优胜者访谈:第二名,Halla Yang

同时XGBoost 是免费的开源软件,可在 Apache-2 许可下使用,尤其是支持多种接口,如命令行界面 (CLI)、C++(编写库的语言)、Python 接口以及 scikit-learn 中的模型、R 接口以及 caret 包中的模型、Julia、Java 和 JVM 语言(如 Scala)和平台(如 Hadoop)。所以它应用得也越来越广泛,它也是我学习监督机器学习的最重要算法。

关于它的原理实现,我这就不详细介绍了,这篇主要是讲其应用,感兴趣的小伙伴们可以自己搜索学习下,当然你也可以登录它的
github地址
来详细学习。
在这里插入图片描述

二、项目详细介绍

任何一个大数据分析项目,我觉得都应该先了解清楚项目目的,在做分析,所以我们先来简单明确下本次项目的主要目的:

项目目的

本次项目是使用LSTM神经网络模型、XGBoost模型,来对股票数据中的某个关键价格,进行预测分析。数据来源,搜狐财经中的某个可转债Zclose相关数据。

下面我就按照大数据分析的一般步骤给大家演示下具体项目实现过程,该分析步骤基本适用于大部分数据分析流程,觉得有用的小伙伴们可以收藏关注哈。

2.1 导入数据

主要使用Pandas库进行数据导入

import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)

all_data_set_path = r'csv_export\csv_data_1m_begin_40d_博22转债_SHSE.113650_SHSE.603916.csv'
all_data_set = pd.read_csv(all_data_set_path)
print(all_data_set.head()) 
print(all_data_set.info()) #查看有多少数据及特征
print(all_data_set.isnull().sum()) #检查是否有空数据

在这里插入图片描述
在这里插入图片描述

2.2 研究数据

主要使用matplotlib库对数据进行初步特征研究分析

import matplotlib.pyplot as plt
plt.style.use('fivethirtyeight')

# 特征热力图 相关性分析
list_columns = all_data_set.columns
plt.figure(figsize=(15,10))
sns.heatmap(all_data_set[list_columns].corr(), annot=True, fmt=".2f")
plt.show()

在这里插入图片描述
从热力图可以大概看出,与zclose相关性比较高的几个特征,接下来就是对特征重要性进行排序,选择出相关性较高的特征。可以看出zlow、zhigh、zopen与zclose特征的相关性最高,都达到了98%左右,所以我们可以优先选取这3个特征,进行分析。

# 对特征重要性进行排序
corr_1 = all_data_set.corr()
corr_1["zclose"].sort_values(ascending=False)

在这里插入图片描述

2.3 数据预处理

数据预处理是整个数据分析过程中最重要的一个步骤,都说"
数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已
"。如果数据和关键特征处理的好,使用不同的模型和算法,都可以取得比较好的效果。

而且数据预处理往往要占整个数据分析的大部分时间,有些甚至占到80%-90%的时间。所以想深入学习大数据分析的小伙伴们,要熟练掌握各种数据预处理的方法哈,比如常用的空值、缺失值、异常值处理、数据不平衡处理、特征组合等等。

由于未来股票受近期股票价格波动的影响较大,所以为了能充分利用近期的股票数据,博主编写了一个预处理函数,组合历史前几次的数据生成更多的特征来进行预测。这也是时序特征数据的常用方法,大概可以理解为滑动窗口取值吧,具体如下:

len_ = len(['zopen','zhigh','zlow','zclose'])*3
col_numbers_drop = []
for i in range(3):
    col_numbers_drop.append(len_+i)
print(col_numbers_drop)

# 依据特征重要性,选择zlow zhigh zopen来进行预测zclose
# 数据选择t-n, ...., t-2 t-1 与 t 来预测未来 t+1
# 转换原始数据为新的特征列来进行预测,time_window可以用来调试用前几次的数据来预测
def series_to_supervised(data,time_window=3):
    data_columns = ['zopen','zhigh','zlow','zclose']
    data = data[data_columns]  # Note this is important to the important feature choice
    cols, names = list(), list()
    for i in range(time_window, -1, -1):
        # get the data
        cols.append(data.shift(i)) #数据偏移量
        
        # get the column name
        if ((i-1)<=0):
            suffix = '(t+%d)'%abs(i-1)
        else:
            suffix = '(t-%d)'%(i-1)
        names += [(colname + suffix) for colname in data_columns]
        
    # concat the cols into one dataframe
    agg = pd.concat(cols,axis=1)
    agg.columns = names
    agg.index = data.index.copy()
    # remove the nan value which is caused by pandas.shift
    agg = agg.dropna(inplace=False)

    # remove unused col (only keep the "close" fied for the t+1 period)
    # Note col "close" place in the columns

    len_ = len(data_columns)*time_window
    col_numbers_drop = []
    for i in range(len(data_columns)-1):
        col_numbers_drop.append(len_+i)

    agg.drop(agg.columns[col_numbers_drop],axis=1,inplace = True)
       
    return agg
    
all_data_set2 = all_data_set.copy()
all_data_set2["index"] = pd.to_datetime(all_data_set2["index"])       # 日期object: to datetime
all_data_set2.set_index("index", inplace=True, drop=True) # 把index设为索引

all_data_set2 = all_data_set2[116:] # 这里把7月28日的数据全部删掉了,主要是数据缺失较多

data_set_process = series_to_supervised(all_data_set2,10) #取近10分钟的数据
print(data_set_process.columns.values)

print(data_set_process.info())

到此,我们数据集就生成了,接下来就可以搭建模型,训练模型和预测数据了。

2.4 搭建模型

2.4.1 LSTM神经网络模型

我们可以直接使用Tensorflow 和 Keras中封装好的LSTM模型来进行模型搭建,这里要告诉大家的是,之所以
很多小伙伴都用python来做数据分析
,是因为很多开发者已经用python搭建了各种各样的wheel/package方便大家直接使用。

所以我们就可以直接站在巨人的肩膀上,快速搭建我们需要的各种模型,同时也可以自己对模型进行调参,获取最优参数组合,从而生成一个高精度的数据模型。

这里要注意:LSTM要求数据格式为numpy格式的数组,所以要将pandas的Dataframe数据转换一下,同时LSTM模型对于不同数据范围的特征较为敏感,一般都要进行相同的范围内数据缩放避免预测错误,所以使用了
MinMaxScaler进行缩放数据
。(也可以使用
StandardScaler
)。后面数据预测后,再进行数据逆缩放就可以获得最后结果了。

具体模型搭建如下:

# 注意这里要安装Tensorflow 和 Keras才能使用
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, LSTM
from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler(feature_range=(0, 1))
scaled_data = scaler.fit_transform(data_set_process)

# split the train and test data
train_size = int(len(data_set_process) * 0.8)
test_size = len(data_set_process) - train_size
train_LSTM, test_LSTM = scaled_data[0:train_size, :], scaled_data[train_size:len(data_set_process), :]

train_LSTM_X, train_LSTM_Y = train_LSTM[:, :(len(data_set_process.columns) - 1)], train_LSTM[:,
                                                                                  (len(data_set_process.columns) - 1)]
test_LSTM_X, test_LSTM_Y = test_LSTM[:, :(len(data_set_process.columns) - 1)], test_LSTM[:,
                                                                               (len(data_set_process.columns) - 1)]

# reshape input to be [samples, time steps, features]
train_LSTM_X2 = np.reshape(train_LSTM_X, (train_LSTM_X.shape[0], 1, train_LSTM_X.shape[1]))
test_LSTM_X2 = np.reshape(test_LSTM_X, (test_LSTM_X.shape[0], 1, test_LSTM_X.shape[1]))

print(train_LSTM_X.shape, train_LSTM_Y.shape, test_LSTM_X.shape, test_LSTM_Y.shape)
print(train_LSTM_X2.shape, test_LSTM_X2.shape)

# creat and fit the LSTM network
model = Sequential()
model.add(LSTM(50, input_shape=(train_LSTM_X2.shape[1], train_LSTM_X2.shape[2])))
# model.add(LSTM(50))
model.add(Dense(1))
model.compile(loss="mae", optimizer="Adam")
print(model.summary()) #这里是打印模型基本信息

print("start to fit the model")
history = model.fit(train_LSTM_X2, train_LSTM_Y, epochs=50, batch_size=50, validation_data=(test_LSTM_X2, test_LSTM_Y),
                    verbose=2, shuffle=False)

plt.plot(history.history['loss'], label='train')  #可视化模型训练的损失函数
plt.plot(history.history['val_loss'], label='test')
plt.legend()
plt.show()

model.save('LSTM_model.h5')  # 这里保存模型,以便以后可以不用再训练,直接使用

# model的使用
# from tensorflow.keras.models import load_model
# del model  # 删除已存在的model
# model = load_model('LSTM_model.h5')

# make prediction
yPredict = model.predict(test_LSTM_X2) #进行模型预测,注意这里要进行数据逆缩放,获取最后结果,同时注意逆缩放数据时必须与scaler的数据维度保持一致
print(yPredict.shape)
testPredict = scaler.inverse_transform(np.concatenate((test_LSTM_X, yPredict), axis=1))[:, -1:]

LSTM模型
在这里插入图片描述

2.4.2 XGBoost模型搭建

XGBoost的模型可以说是非常成熟了,我们可以直接安装xgboost库来搭建模型,博主选了一组初始参数进行模型训练,小伙伴们也可以使用网格搜索GridSearchCV()或者随机搜索RandomizedSearchCV()来进行参数调优操作,具体如下:

import xgboost as xgb
from xgboost import plot_importance, plot_tree

train_size = int(len(data_set_process)*0.8)
test_size = len(data_set_process) - train_size
train_XGB, test_XGB = scaled_data[0:train_size,:],scaled_data[train_size:len(data_set_process),:]

train_XGB_X, train_XGB_Y = train_XGB[:,:(len(data_set_process.columns)-1)],train_XGB[:,(len(data_set_process.columns)-1)]
test_XGB_X, test_XGB_Y = test_XGB[:,:(len(data_set_process.columns)-1)],test_XGB[:,(len(data_set_process.columns)-1)]

# 算法参数
params = {
    'booster':'gbtree',
    'objective':'binary:logistic',  # 此处为回归预测,这里如果改成multi:softmax 则可以进行多分类
    'gamma':0.1,
    'max_depth':5,
    'lambda':3,
    'subsample':0.7,
    'colsample_bytree':0.7,
    'min_child_weight':3,
    'slient':1,
    'eta':0.1,
    'seed':1000,
    'nthread':4,
}

#生成数据集格式
xgb_train = xgb.DMatrix(train_XGB_X,label = train_XGB_Y)
xgb_test = xgb.DMatrix(test_XGB_X,label = test_XGB_Y)
num_rounds = 300
watchlist = [(xgb_test,'eval'),(xgb_train,'train')]

#xgboost模型训练
model_xgb = xgb.train(params,xgb_train,num_rounds,watchlist)

#对测试集进行预测
y_pred_xgb = model_xgb.predict(xgb_test)

2.5 数据可视化及评估

数据可视化就相对简单了,类似于我们用excel来生成各种图表,直观地来看数据分布情况。

除了对数据直观展示外,我们也常使用MAPE、MAE、RMSE、R2对数据预测结果准确性进行评估,这里我就简单使用了MAPE(平均绝对误差率)给大家演示,其中LSTM模型下的测试集、训练集的MAPE都在0.07%之内,也就是说准确率在99.9%以上,可以说效果很好了。XGBoost模型的测试集MAPE在1.2%之内,也就是说准确率在98.8%左右,也可以说效果不错。

具体LSTM模型结果可视化及评估如下:

# make prediction
yPredict = model.predict(test_LSTM_X2)
print(yPredict.shape)

testPredict = scaler.inverse_transform(np.concatenate((test_LSTM_X, yPredict), axis=1))[:, -1:]
test_LSTM_Y2 = scaler.inverse_transform(np.concatenate((test_LSTM_X, test_LSTM_Y.reshape(len(test_LSTM_Y),1)), axis=1))[:, -1:]
print(testPredict.shape)
# print(testPredict)

print("start calculate the mape") 

mape = np.mean(np.abs(test_LSTM_Y2.flatten()-testPredict.flatten())/test_LSTM_Y2.flatten())*100  # 这里计算测试集预测结果与真实结果的误差率
print('Test LSTM for test set Score:%.6f MAPE' %(mape)) #0.027897%的误差

yPredict_train = model.predict(train_LSTM_X2)
print(yPredict_train.shape)
print(train_LSTM_X2.shape)
trainPredict = scaler.inverse_transform(np.concatenate((train_LSTM_X, yPredict_train), axis=1))[:, -1:]
train_LSTM_Y2 = scaler.inverse_transform(np.concatenate((train_LSTM_X, train_LSTM_Y.reshape(len(train_LSTM_Y),1)), axis=1))[:, -1:]

print("start calculate the mape2")

mape2 = np.mean(np.abs(train_LSTM_Y2.flatten()-trainPredict.flatten())/train_LSTM_Y2.flatten())*100  # 这里计算训练集预测结果与真实结果的误差率
print('Test LSTM for train set Score:%.6f MAPE' %(mape2))  #0.068852%的误差

plt.plot(train_LSTM_Y2, color = 'red', label = 'Real Price for Train set')
plt.plot(trainPredict, color = 'blue', label = 'Predicted Price for Train set')
plt.title('Zclose Price Prediction for Train set')
plt.xlabel('Time')
plt.ylabel('Sohu Zclose Price')
plt.legend()
plt.show()

plt.plot(test_LSTM_Y2, color = 'red', label = 'Real Price for Test set')
plt.plot(testPredict, color = 'blue', label = 'Predicted Price for Test set')
plt.title('Zclose Price Prediction for Test set')
plt.xlabel('Time')
plt.ylabel('Sohu Zclose Price')
plt.legend()
plt.show()

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

具体XGBoost模型结果可视化及评估如下:

plt.plot(test_XGB_Y, color = 'red', label = 'Real Price for Test set')
plt.plot(y_pred_xgb, color = 'blue', label = 'Predicted Price for Test set')
plt.title('Zclose Price Prediction for Test set')
plt.xlabel('Time')
plt.ylabel('Sohu Zclose Price')
plt.legend()
plt.show()

mape_xgb = np.mean(np.abs(y_pred_xgb-test_XGB_Y)/test_XGB_Y)*100
print('XGBoost平均误差率为:{}%'.format(mape_xgb))  #平均误差率为1.1974%

代码

所有数据集、代码都已经上传到
我的github
,欢迎大家前往fork、下载。

建议

博主有位朋友在大的金融公司专门做量化投资的,他尝试过加入各种相关因子、特征,使用不同的模型,调参来获取最高的准确度,但由于股票、债券、基金这些金融产品都受各种政治、经济、社会等综合因素的影响,经常会有突发情况导致预测出现较大波动。最后他发现,最好的方法是在每天晚上利用当天的数据重新来训练下,往往能获取到最好的模型。

所以说想尝试往量化投资、机器学习大数据分析这块发展的话,一定要根据实际情况,不断地根据最新的数据,去
实时更新自己的模型
,才能获取到最好的效果。当然也奉劝大家常年不变的真理:
金融有风险,投资需谨慎

如果大家想继续了解人工智能相关
学习路线和知识体系
,欢迎大家翻阅我的另外一篇博客
《重磅 | 完备的人工智能AI 学习——基础知识学习路线,所有资料免关注免套路直接网盘下载》
这篇博客参考了Github知名开源平台,AI技术平台以及相关领域专家:Datawhale,ApacheCN,AI有道和黄海广博士等
约有近100G相关资料
,希望能帮助到所有小伙伴们。

最近在忙业务的间隙,穿插着做了些性能测试。

一、背景简介

业务背景大概介绍一下,就是按照国标规定,车辆需要上传一些指定的数据到ZF的指定平台,同时车辆也会把数据传到企业云端服务上,于是乎就产生了一些性能需求。

目前我们只是先简单的进行了一个性能场景的测试,就是评估目前服务是否能够支持,预期的最大同时在线车辆上传数据。经过评估,在线车辆数据按照预期的10倍来进行的,并且后面增加持续运行12h查看服务链路的稳定性。

本篇并不是一个严谨的性能测试过程结果分享,主要是分享下关于mqtt协议服务的压测脚本的编写。因为之前我也没接触过MQTT协议的压测,网上关于相关的压测脚本的内容也比较杂乱,所以记录一下,仅供参考。

捋一下链路就知道需要生成哪些数据(因为服务还未上线使用,所以产生的压测数据后面可以直接清理掉即可。):

  1. 一些前置数据:比如数据库、缓存里涉及到的车辆数据,通信秘钥数据等等,这些可以之前写脚本一次性生成即可。
  2. 车辆上报的数据:车辆上报到云端的数据,是经过一系列加密转码,期间还要设计到解密等,这个经过评估,可以简化其中的某些环境,所以所有的车可以直接发送相同的数据即可。
  3. 车辆数据:最后就是生成对应的车辆数据,同时在线,按照评估的频率发送数据。

其中第1、2的数据在之前针对性的分别生成即可,第3步的车辆发送数据就是压测脚本要干的事情了。

二、技术选型

这个倒是很快,搜索引擎大概搜了一下,内容很少,或者说对我有用的内容很少。有看到jmeter有相关插件的,但是这个方案基本上我都是否决的,一来我不擅长用,而来我觉得用起来肯定会比自己编码要麻烦的多。

所以就继续编码好了,仍然首选python,想到了
locust
库,后来看官方文档的时候,看到
locust
也针对
mqtt
协议拓展了一些内容。但是我尝试下来不太符合我这的需求,也可能当时我用的不对吧,所以就只能自己来从零开始编写了。

搜索中又发现
Python
中用于
mqtt
协议的库叫
paho.mqtt
,支持连接代理,消息的订阅、收发等等,于是最后确定使用:
locust
+
paho.mqtt
的组合来实现本次的负载脚本。

三、代码编写

1. 脚本代码

暂时没做代码分层,目前场景简单,就直接都放一个模块里了,有点长,先贴上来,后面部分会对脚本的重点内容进行拆解。

脚本目前做了这些事情:

  • 从db中查询有效可用的所有测试车辆信息数据
  • 根据命令行的输入参数,指定启动的车辆数,以及与broker代理建立连接的频率
  • 建立连接成功的车辆,就可以根据脚本里指定的频次,来像broker发送数据
  • 脚本统计连接数、请求数、响应时间等信息写到报表中
  • 调试遇到车辆会批量断开连接的情况,增加了当车辆断开连接时,把断开时间、车辆信息写到本地csv中,方便第二天来查看分析。
import csv
import datetime
import queue
import os
import sys
import time
import ssl

from paho.mqtt import client as mqtt_client

# 根据不同系统进行路径适配
if os.name == "nt":
    path = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
    sys.path.insert(0, path)
    from GB_test.utils.mysql_operating import DB
elif os.name == "posix":
    sys.path.append("/app/qa_test_app/")
    from GB_test.utils.mysql_operating import DB

from locust import User, TaskSet, events, task, between, run_single_user


BROKER_ADDRESS = "broker服务地址"
PORT = 6666661
PASSWORD = "666666666666"
PUBLISH_TIMEOUT = 10000  # 超时时间
TEST_TOPIC = "test_topic"

TEST_VALUE = [16, 3, -26, 4, 0, 36,.......]  # 用来publish的测试数据,仅示意

BYTES_DATA = bytes(i % 256 for i in TEST_VALUE)  # 业务需要转换成 byte 类型后再发送

# 创建队列
client_queue = queue.Queue()

# 连接DB,读取车辆数据
db = DB("db_vmd")
select_sql = "select xxxx"  
client_list = db.fetch_all(select_sql)
print("车辆数据查询完毕,数据量:{}".format(len(client_list)))
for t in client_list:
    # 把可用的车辆信息存到队列中去
    client_queue.put(t)


def fire_success(**kwargs):
    """请求成功时调用"""
    events.request.fire(**kwargs)


def calculate_resp_time(t1, t2):
    """计算响应时间"""
    return int((t2 - t1) * 1000)


class MQTTMessage:
    """已发送的消息实体类"""
    def __init__(self, _type, qos, topic, payload, start_time, timeout):
        self.type = _type,
        self.qos = qos,
        self.topic = topic
        self.payload = payload
        self.start_time = start_time
        self.timeout = timeout


# 统计总共发送成功的消息数量
total_published = 0
disconnect_record_list = []  # 定义存放连接断开的记录的列表容器


class PublishTask(TaskSet):

    @task
    def task_publish(self):
        self.client.loop_start()
        topic = TEST_TOPIC
        payload = BYTES_DATA
        # 记录发送的开始时间
        start_time = time.time()
        mqtt_msg_info = self.client.publish(topic, payload, qos=1, retain=False)
        published_mid = mqtt_msg_info.mid
        # 将发送成功的消息内容,放入client实例的 published_message 字段
        self.client.published_message[published_mid] = MQTTMessage(REQUEST_TYPE,
                                                                   0,
                                                                   topic,
                                                                   payload,
                                                                   start_time,
                                                                   PUBLISH_TIMEOUT)
        # 发送成功回调
        self.client.on_publish = self.on_publish
        # 断开连接回调
        self.client.on_disconnect = self.on_disconnect

    @staticmethod
    def on_disconnect(client, userdata, rc):
        """ broker连接断开,放入列表容器"""
        disconnected_info = [str(client._client_id), rc, datetime.datetime.now()]
        disconnect_record_list.append(disconnected_info)
        print("rc状态:{} - -".format(rc), "{}-broker连接已断开".format(str(client._client_id)))

    @staticmethod
    def on_publish(client, userdata, mid):
        if mid:
            # 记录消息发送成功的时间
            end_time = time.time()
            # 从已发送的消息容器中,取出消息
            message = client.published_message.pop(mid, None)
            # 计算开始发送到发送成功的耗时
            publish_resp_time = calculate_resp_time(message.start_time, end_time)
            fire_success(
                request_type="p_success",
                name="client_id: " + str(client._client_id),
                response_time=publish_resp_time,
                response_length=len(message.payload),
                exception=None,
                context=None
            )
            global total_published
            # 成功发送累加1
            total_published += 1


class MQTTLocustUser(User):
    tasks = [PublishTask]
    wait_time = between(2, 2)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 从队列中获取客户端 username 和 client_id
        current_client = client_queue.get()

        self.client = mqtt_client.Client(current_client[1])
        self.client.username_pw_set(current_client[0], PASSWORD)
        # self.client.username_pw_set(current_client[0] + "1", PASSWORD)  # 模拟client连接报错

        # 定义一个容器,存放已发送的消息
        self.client.published_message = {}

    def on_start(self):
        # 设置tls
        context = ssl.SSLContext(ssl.PROTOCOL_TLS)
        self.client.tls_set_context(context)

        self.client.connect(host=BROKER_ADDRESS, port=PORT, keepalive=60)
        self.client.on_connect = self.on_connect

    def on_stop(self):
        print("publish 成功, 当前已成功发送数量:{}".format(total_published))
        if len(disconnect_record_list) == 0:
            print("无断开连接的client")
        else:
            # 把断开记录里的信息写入csv
            with open("disconnect_record.csv", "w", newline='', encoding='UTF8') as csvfile:
                writer = csv.writer(csvfile)
                writer.writerow(['client_id', 'rc_status', 'disconnected_time'])
                for i in disconnect_record_list:
                    writer.writerow(i)
            print("断开连接的client信息已写入csv文件")

    @staticmethod
    def on_connect(client, userdata, flags, rc, props=None):
        if rc == 0:
            print("rc状态:{} - -".format(rc), "{}-连接broker成功".format(str(client._client_id)))
            fire_success(
                request_type="c_success",
                name='count_connected',
                response_time=0,
                response_length=0,
                exception=None,
                context=None
            )
        else:
            print("rc状态:{} - -".format(rc), "{}-连接broker失败".format(str(client._client_id)))
            fire_success(
                request_type="c_fail",
                name="client_id: " + str(client._client_id),
                response_time=0,
                response_length=0,
                exception=None,
                context=None
            )


if __name__ == '__main__':
    run_single_user(MQTTLocustUser)

2. 代码分析-locust库部分

并发请求能力还是使用的
locust
库的能力。官方只提供了
http
协议接口的相关类,没直接提供
mqtt
协议的,但是我们可以按照官方的规范,自定义相关的类,只要继承
User

TaskSet
即可。

User

首先是先定义
User
类,这里就是用来生成我要用来测试的车辆。

类初始化的时候,黄色框里,会去队列里取出车辆信息,用来做一些相关的设置。
client
来源于
from paho.mqtt import client as mqtt_client
提供的能力,固定用法,按照人家的文档使用就行。

红色框里,是
User
类的2个重要熟悉属性:

  • tasks
    : 这里定义了生成的用户需要去干哪些事情,也就是对应脚本里的
    PublishTask
    类下面定义的内容。
  • wait_time
    : 用户在执行task时间隔停留的时间,可以是个区间,在里面随机。我这里意思是每2s发送一次数据到broker。

绿色框里,定义了一个字典容器,用来存放当前用户已发送成功的消息内容,因为后面我要取出来把里面相关的数据写到生成的报表中去。

蓝色框里有2个方法,也是
locust
提供的能力:

  • on_start
    :当用户开始运行时调用,这里我做了车辆连接broker代理的处理,注意这里需要设置tls,因为服务连接需要。

  • on_stop
    :当用户结束运行时调用,这里我做了一些其他的处理,比如把运行期间断开连接的车辆信息写到本地csv中。

TaskSet

定义好
User
类,就需要来定义
TaskSet
类,你得告诉产生出来的用户,要干点啥。

我这根据业务需要,就是让车辆不停的像broker发送数据即可。

红色部分,同样是
paho.mqtt
提供的能力,会启动新的线程去执行你定义的事情。

黄色部分,就是做发送数据的操作,并且我可以拿到一些返回,查看源码就可以知道返回的是
MQTTMessageInfo
类。

注意返回的2个属性:

  • mid
    : 返回这个消息发送的顺序
  • rc
    : 表示发送的响应状态,0 就是成功

绿色部分,还记得我在上面的
User
类中定义了一个容器,在这里就把发送的消息相关信息放到容器中去,留着后面使用。

2. 代码分析-paho.mqtt库部分

上面的代码已经用到了不少
paho.mqtt
的能力,这里再进行整体梳理下。

  • client.Client():声明一个client
  • client.username_pw_set(): 设置客户端的用户名,密码
  • client.tls_set_context: 设置ssl模式
  • client.connect(): 连接代理
  • client.publish:向代理推送消息

还用到了一些回调函数:

  • on_connect:连接操作成功时回调
  • on_publish:发布成功时回调
  • on_disconnect:客户端与代理断开连接时回调

另外还用到了一个事件函数
events.request

当客户端发送请求时会调用,不管是请求成功还是请求失败;当我需要自定义我的报告内容时,就需要用到这个
event

查看源码,知道里面要传哪些参数,那我们在调用时候就需要传入对应的参数。

比如我在发送回调函数里调用了该方法。

所以最后在控制台显示的报告里就有我定义的内容了。

由于后来在使用中发现,不知道会在什么时候出现批量断开的情况,于是在
on_disconnect
回调函数里增加了对应处理,把相关的断开信息记录下来,运行结束的时候写到本地文件里去。

后来我主动尝试客户端断开的情况测试了下文件的写入结果,功能正常。

三、小结

后面就开始运行了,在运行过程中,开发关注链路服务的各项指标,这里就不展开了,业务缠身就并没有过多的去做这个事情,况且也不专业。确实也发现了不少问题,后面逐步优化,再继续测试。

现在稳定运行12h,服务正常,暂时就先告一段落了。后面还有会相关其他性能测试场景,届时就可以针对性的展开分享下了。

另外,这个脚本分享也只是仅供参考,现在我这是使用简单,本着能用就行,可能存在一些不合理需要优化的地方,有需要的朋友还请自行查阅相关文档。

操作系统:

RHEL7.x 或CentOS 7.x

  • 最小化安装
  • 配置好固定的IP,能访问互联网
  • 配置好yum源(yum repolist 可以查看yum源)
    • 本地光盘
      • 挂载光盘,开机自动挂载
        • vim + /etc/fstable
        • /dev/sr0 /mnt iso9660 defaults 0 0
      • 创建挂载点目录:
        • mkdir /media/cdrom
      • 挂载:mount -a
      • 配置yum源:
        • yum-config-manger --add-repo=file:/// media/cdrom
        • echo "gpgcheck = 0" >> /etc/yum.repos.d/media_cdrom.repo
    • EPEL
      • aliyun sohu 中科大 清华 网易

开发环境:vim

查看系统shell类型:

[root@template ~]# cat /etc/shells
/bin/sh
/bin/bash
/usr/bin/sh
/usr/bin/bash

查看当前默认shell:

[root@template ~]# echo $SHELL
/bin/bash

快速如何快速生成脚本开头的版本版权注释信息

[root@template ~]# cat ~/.vimrc
autocmd BufNewFile *.go,*.py,*.cc,*.sh,*.java exec ":call SetTitle()"
func SetTitle()
if expand("%:e") == 'sh'
call setline(1,"#!/bin/bash")
call setline(2,"#########################")
call setline(3,"#File name:".expand("%"))
call setline(4,"#Version:v1.0")
call setline(5,"#Email:admin@test.com")
call setline(6,"#Created time:".strftime("%F %T"))
call setline(7,"#Description:")
call setline(8,"#########################")
call setline(9,"")
endif
endfunc

修改Tab缩进

在/etc/vim/下有两个文件,分别为vimrc 和vimrc.tiny

在vimrc文件中加入:set tabstop=4

流程控制与判断练习:

1、ping主机测试,查看主机是否存活

[root@template chap04]# cat ping.sh
#!/bin/bash
#########################
#File name:ping.sh
#Version:v1.0
#Email:admin@test.com
#Created time:2023-03-23 17:32:18
#Description:
#########################
read -p "please enter your host:" host
if ping -c2 $host &> /dev/null
then
        echo "$host is running"
else
        echo "$host is down"
fi

2、判断一个用户是否存在

[root@template chap04]# cat user.sh
#!/bin/bash
#########################
#File name:user.sh
#Version:v1.0
#Email:admin@test.com
#Created time:2023-03-23 17:45:03
#Description:
#########################
read -p "please enter a username:" username
if id $username &> /dev/null
then
        echo "$username is exist"
else
        echo " $username is not exist "
fi

3、判断当前内核主版本是否为3,且次版本是否大于10

[root@template chap04]# cat sys.sh
#!/bin/bash
#########################
#File name:sys.sh
#Version:v1.0
#Email:admin@test.com
#Created time:2023-03-23 17:52:56
#Description:
main_version=`uname  -r | awk -F . '{print $1 }'`
minor_version=`uname -r | awk -F . '{print $2}'`
if [ "$main_version"  -eq 3 ] && [ "$minor_version" -ge 10 ]
  then
     echo "主版本是:$main_version 次版本是:$minor_version"
else
       echo "不满足条件,此系统的主版本是:$main_version 次版本是:$minor_version"
fi

4、判断vsftpd软件包是否安装,如果没有则自动安装

#!/bin/bash
#########################
#File name:isvsftp.sh
#Version:v1.0
#Email:admin@test.com
#Created time:2023-03-23 18:05:56
#Description:
#########################
if rpm -qa | grep vsftpd &> /dev/null
then
        echo "vsftp is exist"
else
        echo "vsftp is not exist"
        read -p "please enter your choice:" choice
        if [ $choice -eq 1 ]
                then
                        yum install vsftpd -y &> /dev/null
        else
                echo " break and uninstall"
        fi
fi

# 测试结果:最初环境没有安装,选择时随机输入,最后选择安装,最后一次测试检查是否安装成
[root@template chap04]# ./isvsftp.sh
vsftp is not exist
please enter your choice:2
 break and uninstall
[root@template chap04]# ./isvsftp.sh
vsftp is not exist
please enter your choice:1
[root@template chap04]# ./isvsftp.sh
vsftp is exist

5、判断httpd是否运行

# 也不算完整:先检测是否安装htppd.service此软件,注意centos7版本中自带一个httpd-xxx的工具,所以在写服务时最好加上httpd.service,程序流程:如果包存在,直接输出active,如果不存在就选择是否安装
[root@template chap04]# cat httpd.sh
#!/bin/bash
#########################
#File name:httpd.sh
#Version:v1.0
#Email:admin@test.com
#Created time:2023-03-23 18:20:59
#Description:
#########################
if rpm -qa |  grep httpd.service &> /dev/null
then
        echo "system  `systemctl is-active httpd`"
else
        echo "httpd is uninstall"
        read -p " please enter your choice : " choice
        if [ $choice -eq 1 ]
        then
                yum install httpd -y &> /dev/null
                systemctl restart httpd
        else
                echo " break and uninstall"
        fi
fi

6、判断指定的主机是否能ping通,必须使用$1变量

[root@template chap04]# cat ping2.sh
#!/bin/bash
#########################
#File name:ping2.sh
#Version:v1.0
#Email:admin@test.com
#Created time:2023-03-23 18:55:21
#Description:
#########################
if ping -c1 $1 &> /dev/null
then
        echo "$1 is runing"
else
        echo "$1 is dead"
fi

[root@template chap04]# ./ping2.sh  192.168.11.10
192.168.11.10 is runing
[root@template chap04]# ./ping2.sh  baidu.com
baidu.com is runing

7、报警脚本,要求如下

根分区剩余空间小于20%
内存已用空间大于80%
向用户alice发送告警邮件
配合crond每5分钟检查一次

yum install mailx -y &> /dev/null
#!/bin/bash
total_mem=$(free -m | tr -s " " | cut -d " " -f 2 | head -2 | tail -1)
used_mem=$(free -m | tr -s " " | cut -d " " -f 3 | head -2 | tail -1)
used_memper=$(echo "scale=2;$used_mem/$total_mem*100" | bc)
total_root=$(df | grep "/"$ |tr -s " " | cut -d " " -f 2)
used_root=$(df | grep "/"$ |tr -s " " | cut -d " " -f 4)
free_rootper=$(echo "scale=2;$used_root/$total_root*100" | bc)
v1=$(echo "used_memper > 80" | bc)
v2=$(echo "free_rootper < 20" | bc)
if [ $v1 -eq 1 ];then
  echo "内存已用空间大于80%" | mail -s "警告信息" alice
elif [ $v2 -eq 1 ];then
  echo "根分区剩余空间小于20%" | mail -s "警告信息" alice
else
  echo "正常使用"
fi

8、判断用户输入的是否是数字,如果是数字判断该数字是否大于10

[root@template chap04]# cat num.sh
#!/bin/bash
read -p "please input a num:" num
if echo  " $num" | grep " [ 0-9 ]" &> /dev/null
then
        if [ $num -gt 10 ]
        then
                echo "$num is more than 10"
        else
                echo "$num is less than 10"

        fi
else
        echo "input a num!!!"
fi

9、计算用户输入的任意两个整数的和、差、乘积、商、余数

a=$1
b=$2
if [ $# -eq 2 ]
then
  if [[ "$a" =~ ^[0-9]*$ && "$b" =~ ^[0-9]*$ ]]
  then
    echo "a、b Is an integer"
    echo a+b=$((a+b))
    echo a-b=$((a-b))
    echo a*b=$((a*b))
    echo a/b=$((a/b))
    echo a%b=$((a%b))
  else
    echo "a,b Is  not an integer"
    exit 0
  fi
else
  echo "The number of parameters is 2"
  exit 0
fi

我的需求提问

创建一个html页面,这个页面包括通过学生id查询学生详情的组件,和通过学生姓名,身份证,选择班级的组件,并把代码告诉我

chatgpt回答

好的,以下是一个包含两个组件的HTML页面,分别是通过学生ID查询学生详情和通过姓名、身份证号码和班级选择查询学生详情。
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>查询学生信息</title>
</head>

<body>
    <h1>查询学生信息</h1>

    <!-- 查询学生ID -->
    <div>
        <h2>通过学生ID查询学生详情</h2>
        <input type="text" id="student-id-input" placeholder="请输入学生ID">
        <button id="get-student-by-id-btn">查询</button>
        <br><br>
        <div id="student-info"></div>
    </div>

    <hr>

    <!-- 查询学生姓名/身份证号码/班级选择 -->
    <div>
        <h2>通过学生姓名、身份证号码和班级选择查询学生详情</h2>
        <label for="student-name-input">姓名:</label>
        <input type="text" id="student-name-input"><br><br>
        <label for="student-id-number-input">身份证号码:</label>
        <input type="text" id="student-id-number-input"><br><br>
        <label for="student-class-select">班级:</label>
        <select id="student-class-select">
            <option value=""></option>
            <option value="一年级">一年级</option>
            <option value="二年级">二年级</option>
            <option value="三年级">三年级</option>
        </select><br><br>
        <button id="get-student-by-condition-btn">查询</button>
        <br><br>
        <div id="student-info-2"></div>
    </div>

    <!-- 引入jQuery库 -->
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

    <!-- js代码 -->
    <script>
        // 通过学生ID查询学生详情
        $('#get-student-by-id-btn').click(function () {
            let studentId = $('#student-id-input').val();
            if (studentId) {
                $.ajax({
                    url: '/api/getStudentById',
                    type: 'GET',
                    data: { id: studentId },
                    success: function (res) {
                        if (res.code === 0) {
                            $('#student-info').html(`
                                姓名:${res.data.name};<br>
                                年龄:${res.data.age};<br>
                                身份证号码:${res.data.idNumber};<br>
                                班级:${res.data.class};<br>
                            `);
                        } else {
                            $('#student-info').text('查询失败');
                        }
                    },
                    error: function () {
                        $('#student-info').text('查询失败');
                    }
                });
            } else {
                $('#student-info').text('请输入学生ID');
            }
        });

        // 通过学生姓名、身份证号码和班级选择查询学生详情
        $('#get-student-by-condition-btn').click(function () {
            let name = $('#student-name-input').val();
            let idNumber = $('#student-id-number-input').val();
            let className = $('#student-class-select').val();
            $.ajax({
                url: '/api/getStudentByCondition',
                type: 'POST',
                data: { name, idNumber, class: className },
                success: function (res) {
                    if (res.code === 0) {
                        $('#student-info-2').html(`
                            姓名:${res.data.name};<br>
                            年龄:${res.data.age};<br>
                            身份证号码:${res.data.idNumber};<br>
                            班级:${res.data.class};<br>
                        `);
                    } else {
                        $('#student-info-2').text('查询失败');
                    }
                },
                error: function () {
                    $('#student-info-2').text('查询失败');
                }
            });
        });
    </script>

</body>

</html>

需要注意的是,上述代码仅为示例代码,具体实现需要根据后端接口的返回值以及前端的具体需求进行相应的修改。同时,需要在服务器端实现两个接口'/api/getStudentById'

为了解决跨域等问题 我向chatgpt提问

egg 接口传递一个网页

chatgpt回答

Egg是一个基于Node.js和Koa的Web开发框架,可以用于构建企业级的应用程序和API接口。如果您想通过Egg接口传递网页,您需要在后端使用模板引擎来渲染HTML页面,并将其响应给前端请求。以下是一个简单的示例代码:
// 引入Egg框架
const egg = require('egg');

// 创建应用实例
const app = egg();

// 定义路由
app.get('/', async (ctx) => {
  // 渲染HTML页面
  const html = await ctx.render('index.html', { title: 'My Page Title' });

  // 响应HTML页面
  ctx.body = html;
});

// 监听端口
app.listen(3000);

上述代码中,我们首先引入了Egg框架,并创建了一个应用实例。接着我们定义了一个路由,当用户访问根路径时,会渲染名为index.html的HTML页面,并将其响应给前端请求。最后我们监听了端口号3000,启动了应用程序。

当然,具体的实现方式还需要根据您的具体需求和开发环境进行调整。希望以上信息能够对您有所帮助!

由于后台报错

   at ContextView.[contextView#render] (F:\后端代码\nodejs\egg-test2\node_modules\egg-view\lib\context_view.js:68:5)
   at async HomeController.index (F:\后端代码\nodejs\egg-test2\app\controller\home.js:10:5)
   at async F:\后端代码\nodejs\egg-test2\node_modules\egg-development\app\middleware\egg_loader_trace.js:9:50
   at async xframe (F:\后端代码\nodejs\egg-test2\node_modules\egg-security\lib\middlewares\xframe.js:7:5)
   at async xssProtection (F:\后端代码\nodejs\egg-test2\node_modules\egg-security\lib\middlewares\xssProtection.js:7:5)    at async nosniff (F:\后端代码\nodejs\egg-test2\node_modules\egg-security\lib\middlewares\nosniff.js:8:5)
   at async noopen (F:\后端代码\nodejs\egg-test2\node_modules\egg-security\lib\middlewares\noopen.js:8:5)
   at async session (F:\后端代码\nodejs\egg-test2\node_modules\koa-session\index.js:41:7)
   at async bodyParser (F:\后端代码\nodejs\egg-test2\node_modules\koa-bodyparser\index.js:91:5)
   at async F:\后端代码\nodejs\egg-test2\node_modules\koa-static-cache\index.js:41:56
generatedMessage: false
code: "ERR_ASSERTION"
actual: ""
expected: true
operator: "=="
pid: 4956
hostname: DESKTOP-801VTT1

于是我直接将错误结果返回给chatgpt直接不动脑子

提问:ERROR 4956 nodejs.AssertionError: Can't find viewEngine

chatgpt的回答:

这个错误提示表明 Egg 框架在渲染视图模板时找不到对应的模板引擎。

要解决此问题,您需要确保已经正确配置了模板引擎,并将其添加到了 Egg 的插件列表中。以下是一个示例代码,演示如何使用 Nunjucks 模板引擎和 egg-view-nunjucks 插件来渲染 HTML 视图:

  1. 安装依赖

首先,我们需要安装 egg-view-nunjucks 插件和 nunjucks 模板引擎:

npm install egg-view-nunjucks nunjucks --save
  1. 配置插件

在 Egg 项目的 config/plugin.js 文件中添加以下内容:

exports.nunjucks = {
  enable: true,
  package: 'egg-view-nunjucks',
};

上述代码告诉 Egg 框架启用 nunjucks 模板引擎,并将其封装为 egg-view-nunjucks 插件。

  1. 配置模板路径

在 Egg 项目的 config/config.default.js 文件中添加以下内容:

exports.view = {
  defaultViewEngine: 'nunjucks',
  mapping: {
    '.html': 'nunjucks',
  },
};

上述代码指定了视图模板的默认引擎为 nunjucks,并将 .html 后缀映射到 nunjucks 引擎。

  1. 创建模板文件

在 Egg 项目的 app/view 目录下创建一个名为 index.html 的模板文件,内容可以按照您的需求自行编写。

  1. 编写控制器

在 Egg 项目的 app/controller 目录下创建一个名为 home.js 的控制器文件,内容如下所示:

const Controller = require('egg').Controller;

class HomeController extends Controller {
  async index() {
    await this.ctx.render('index.html', { title: 'My Page Title' });
  }
}

module.exports = HomeController;

上述代码定义了一个名为 HomeController 的控制器类,并创建了一个名为 index 的方法用于渲染 index.html 模板文件。

  1. 配置路由

最后,在 Egg 项目的 app/router.js 文件中添加以下内容:

module.exports = (app) => {
  app.get('/', 'home.index');
};

上述代码将根路径
/
映射到 HomeController 控制器的 index 方法中。

希望以上信息能够对您有所帮助!

参考上诉方法

完美渲染网页!

chatgpt的强大真的出乎我的意料,能够根据逻辑一步步解决我的问题,就像一个学习阶段非常懂我的老师并且知识量丰富程度非常离谱

写在最前

我们经常看到非常多的 API 推荐,但又经常收藏到收藏夹里吃灰,仿佛收藏了就是用了。

很多时候没有用起来,可能是因为想不到某类 API 可以用来做什么或者能应用在哪里。

下面我将我思考的一些方向给到大家,希望我们都能共同致富。

天气类 API

天气预报查询
:获取城市的天气实况数据;更新频率分钟级别。

空气质量查询
:获取指定城市的整点观测空气质量等。

这两个 API 是相辅相成的,我们可以整合起来使用。那么应用在哪里呢?

天气类 API 应用场景:

  • 旅游出行:帮助用户了解目的地的天气状况,提前做好出行准备。
  • 生活服务:提供当地实时天气信息,帮助用户安排日常生活活动,比如安排户外运动等。
  • 农业生产:提供当地的天气信息和预报,帮助农民安排种植和收获的时间。。

天气类 API 可以用来做什么:

  • 天气预报小程序:可以查询当天、未来数日的天气情况,包括温度、降水量、风力等信息。
  • 旅游出行小程序:可以查询目的地的天气情况,以便做出合适的出行计划。
  • 运动健身小程序:可以查询当天和未来数日的天气情况,以决定是否适合进行户外运动和健身活动。
  • 空气质量小程序:可以查询当天和未来数日的空气质量情况,以决定是否适合进行户外活动。
  • 疾病防护小程序:可以查询当地的疾病预警情况,以及建议的预防措施。
  • 车主出行小程序:可以查询当天和未来数日的天气情况,以决定是否需要进行车辆保养,以及是否需要换上特定类型的轮胎。

交通类 API

尾号限行
:查询多个城市 未来15天的机动车尾号限行数据查询,包括限行区域、限行规则等。

公交及站点查询
:全国城市公交站点、线路、换乘查询。

尾号限行
API
可以用来做什么

  • 尾号限行提醒小程序:可以设置自己的车辆尾号,小程序会实时查询最新的限行规定,提醒用户在相应的限行时间内不要开车上路。
  • 交通出行小程序:可以查询当前日期的限行规定,以便规划自己的出行路线。
  • 车辆管理小程序:可以输入自己的车牌号,小程序会自动查询该车辆的限行规定,并提醒用户注意限行时间。
  • 车辆购买建议小程序:可以查询目标城市的限行规定,以便在购买车辆时做出合适的决策。
  • 城市交通拥堵预警小程序:可以查询目标城市的限行规定和道路拥堵情况,以便规划自己的出行路线。

除此之外,还可以根据具体需求开发更多与尾号限行相关的小程序或功能,例如车辆保险推荐、道路建设规划等。

公交及站点查询
API
可以用来做什么

  • 公交车实时查询小程序:可以查询当前位置或目的地附近的公交车实时到站情况,以便更好地规划出行路线。
  • 公交路线查询小程序:可以查询从起点到终点的公交路线,包括具体的公交车线路、换乘站点等信息。
  • 公交站点查询小程序:可以查询附近的公交站点,以及每个站点所经过的公交车线路和到站时间等信息。
  • 公交卡管理小程序:可以查询自己的公交卡余额、消费记录、充值方式等信息,以及通过小程序进行在线充值。

写在最后

还有其他诸如
历史上的今天

万年历

周公解梦

将图片转换成漫画风格
等 API 也有超多的应用场景和功能等待着我们挖掘,如果你有相关的 API 却苦于无用武之处,可以评论交流一下。