这是继上一篇python脚本之api数据合并之后做出的第二版,最近比较忙一直拖到现在才想起来更新。
第一版实现的是单参数的合并,这一版就是实现多参数的合并了,样例如下:

1
2
3
4
/api/login/001-1/index/1
/api/login/001-2/index/2
/api/login/001-3/index/3
/api/login/001-4/index/4

最后合并成下面的数据:

1
/api/login/{id1}/index/{id2}

代码加注释一共三百行左右,说实话我自己看着都费事,所以做了个xmind思维导图,和部分模块的“白话版”代码。
思维导图:

图画的一般,,,,凑合看吧。
“白话版”代码块放最后,先上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
import re,math,datetime
from collections import Counter

# 将包含数字的段替换为“{id1},{id2},{id3}...”
def sub(data):
list = re.findall('/[^/,]*\d[^/,]*', data)
for i in range(len(list)):
data = data.replace(list[i], "/{id" + str(i + 1) + "}", 1)
return data

def main():
# C:\\Users\\lkm86\\Desktop\\API自动发现2021-06-24 17_46_41.csv
file = open("C:\\Users\\lkm86\\Desktop\\API自动发现2021-07-20 16_04_32.csv", "r", encoding="utf-8")
# 读取数据
content = file.readlines()
# 存放一个包含数字的段的要计数api
patternList = []
# 存放一个包含数字的段的api
patternBranch = []
# 存放多个包含数字的段的api
patternsDict = {}
# # 存放多个包含数字的段的api
patternsBranch = {}
# 存放最终结果
patterns = {}

'''
数据分组,划分为:
(1)不包含数字;
(2)1个段包含数字;
(3)N+1个段包含数字。
'''
# 遍历数据列表
for lines in content:
# 取出api中包含数字的段
apiList = re.findall('/[^/,]*\d[^/,]*', lines)
# 正则匹配是否有包含数字的段
if len(apiList) >= 2:
# 替换包含数字的段为“{id}”
api = sub(lines)

# 将替换后的api表达式再和别的字段合并放入patternsDict,后续计数使用
if patternsDict.get(len(apiList)) == None:
patternsDict[len(apiList)] = [api.strip()]
else:
patternsDict[len(apiList)].append(api.strip())

# 将未做替换的api和其余字段合并并放入patternsBranch,后续过滤符合表达式的api使用
if patternsBranch.get(len(apiList)) == None:
patternsBranch[len(apiList)] = [lines.strip()]
else:
patternsBranch[len(apiList)].append(lines.strip())

elif len(apiList) == 1:
# 替换包含数字的段为“{id}”
api = sub(lines)
# 将替换后的api表达式再和别的字段合并放入patternList,后续计数使用
patternList.append(api.strip())
# 将未做替换的api和其余字段合并并放入patternBranch,后续过滤符合表达式的api使用
patternBranch.append(lines.strip())
else:
# 将不包含数字的数据直接放入patterns,并计算与总数据量的占比
patterns[lines.strip()] = math.ceil(1 / len(content) * 100)

'''
处理一个段包含数据的api
'''
if len(patternList) > 0:
# 将patternList计数,得到字典cnt,key是patternList的原数据,value是原数据出现的次数
cnt = Counter(patternList)

# 遍历字典cnt
for key in cnt.keys():
# 判断表达式出现次数是否小于2
if cnt[key] < 2:
# 将表达式中的api的“{id1}”替换为是否包含数字的正则表达式
reString = key.replace("{id1}", "[^\\/]*\d[^\\/]*")
reString = "^" + reString + "$"
# 遍历patternBranch
for line in patternBranch:
# 判断patternBranch中数据的api是否符合正则表达式reString
if re.compile(reString).findall(line):
# 将符合正则表达式的放入patternDict,并计算与总数据量的占比
patterns[line] = math.ceil(1 / len(content) * 100)
else:
# 将表达式出现次数大于等于2的放入patternDict,并计算与总数据量的占比
patterns[key] = math.ceil(cnt[key] / len(content) * 100)

'''
处理N+1个段包含数据的api
'''
# 遍历转换“{id}”后的正则表达式
for keyDict in patternsDict.keys():
# 将包含“{id}”个数相同的api统一计数
cnt = Counter(patternsDict[keyDict])

# 判断如果计数的key为1就说明只有一个表达式,不需要再做是否匹配表达式这一操作
if len(cnt) > 1 :
# 遍历计数字典cnt
for keyCnt in cnt.keys():
# 替换表达式为正则,以此筛选出符合当前格式的数据
reString = re.compile('/[^/,]*\d[^/,]*').sub("/[^/,]*\\\d[^/,]*", keyCnt)
reString = "^" + reString + "$"
# 如果value小于或等于2,说明此表达式最多由两条数据生成,就不产生表达式,直接将匹配此表达式的数据加入到 patterns
if cnt[keyCnt] <= 2:
# 遍历当前包含“{id}”个数的数据列表
for data in patternsBranch[keyDict]:
# 符合当前表达式格式的数据加入patterns
if re.compile(reString).findall(data):
patterns[data] = math.ceil(1 / len(content) * 100)
else:
# 存放要计数的数据
dataList = []
# 存放不是{id}的段的下标
idList = []
# 计数,防止下标越界
num = 1
# keyDict为当前处理的数据的“{id}”个数,个数多少就循环多少次,可以得到相同数据类型下不是真的参数的包含数字的段的下标
while num <= keyDict:
# 清空dataList
dataList.clear()
# 遍历当前包含“{id}”个数的数据列表
for data in patternsBranch[keyDict]:
# 判断数据是否符合当前表达式格式
if re.compile(reString).findall(data):

# 存放转换后的数据
dataString = ""
# 计数,和num做对比
datanum = 1
# 根据包含数字的部分拆分数据
list = re.split("(/[^/,]*\d[^/,]*)", data)
# 遍历拆分后的列表
for i in list:
# 判断是否包含数字,不包含直接和dataString拼接
if re.compile("/[^/,]*\d[^/,]*").findall(i):
# 判断是否和num相同,相同则不转换为“/{id}”,不相同则转换为“/{id}”
if datanum == num:
dataString = dataString + i
else:
dataString = dataString + "/{id}"
# 计数+1
datanum = datanum + 1
else:
dataString = dataString + i

# 将转换后的数据放入dataList,后续计数使用
dataList.append(dataString)
# 去重,得到当前类型的数据中,“{id}”的值不同的个数
dataSet = set(dataList)
# 如果“{id}”值不同的个数占全部的60%以下,则判定当前段不是真正的参数,将当前段的下标加入到idList
if len(dataSet) / len(dataList) < 0.6:
idList.append(num)
# 计数 +1
num = num + 1

# 存放转换“{id}”后的数据,计数使用
pattern = []
# 遍历当前包含“{id}”个数的数据列表
for data in patternsBranch[keyDict]:
# 判断数据是否符合当前表达式格式
if re.compile(reString).findall(data):

# 存放转换后的数据
dataString = ""
# 计数,判断idList中包不包含当前下标的段
datanum1 = 1
# 计数,统计当前数据有几个“{id}”
datanum2 = 1
# 根据包含数字的部分拆分数据
list = re.split("(/[^/,]*\d[^/,]*)", data)
# 遍历拆分后的列表
for i in list:
# 判断当前段是否包含数字,如果不包含直接和dataString拼接
if re.compile("/[^/,]*\d[^/,]*").findall(i):
# 如果当前下标的段在idList中,则表示当前包含数字的段非真正的“{id}”;不在idList中,就拼接“{id}”
if datanum1 in idList:
dataString = dataString + i
else:
dataString = dataString + "/{id" + str(datanum2) + "}"
datanum2 = datanum2 + 1
# 计数+1
datanum1 = datanum1 + 1
else:
dataString = dataString + i

pattern.append(dataString)

# 计数,也是去重,之所以不直接去重是因为要计算当前表达式的个数来算百分比
patternsCnt = Counter(pattern)

# 将表达式加入列表patterns
for key in patternsCnt.keys():
patterns[key] = math.ceil(patternsCnt[key] / len(content) * 100)
else:
# 遍历计数字典cnt
for keyCnt in cnt.keys():

# 如果value小于或等于2,说明此表达式最多由两条数据生成,就不产生表达式,直接将匹配此表达式的数据加入到 patterns
if cnt[keyCnt] <= 2:
# 遍历当前包含“{id}”个数的数据列表,将数据直接加入patterns
for data in patternsBranch[keyDict]:
patterns[data] = math.ceil(1 / len(content) * 100)
else:
# 存放要计数的数据
dataList = []
# 存放不是{id}的段的下标
idList = []
# 计数,防止下标越界
num = 1

# keyDict为当前处理的数据的“{id}”个数,个数多少就循环多少次,可以得到相同数据类型下不是真的参数的包含数字的段的下标
while num <= keyDict:
# 清空dataList
dataList.clear()
# 遍历当前包含“{id}”个数的数据列表
for data in patternsBranch[keyDict]:

# 存放转换后的数据
dataString = ""
# 计数,和num做对比
datanum = 1
# 根据包含数字的部分拆分数据
list = re.split("(/[^/,]*\d[^/,]*)", data)
# 遍历拆分后的列表
for i in list:
# 判断是否包含数字,不包含直接和dataString拼接
if re.compile("/[^/,]*\d[^/,]*").findall(i):
# 判断是否和num相同,相同则不转换为“/{id}”,不相同则转换为“/{id}”
if datanum == num:
dataString = dataString + i
else:
dataString = dataString + "/{id}"
# 计数+1
datanum = datanum + 1
else:
dataString = dataString + i

# 将转换后的数据放入dataList,后续计数使用
dataList.append(dataString)
# 去重,得到当前类型的数据中,“{id}”的值不同的个数
dataSet = set(dataList)

# 如果“{id}”值不同的个数占全部的60%以下,则判定当前段不是真正的参数,将当前段的下标加入到idList
if len(dataSet) / len(dataList) < 0.6:
idList.append(num)
# 计数 +1
num = num + 1

# 存放转换“{id}”后的数据,计数使用
pattern = []

# 遍历当前包含“{id}”个数的数据列表
for data in patternsBranch[keyDict]:

# 存放转换后的数据
dataString = ""
# 计数,判断idList中包不包含当前下标的段
datanum1 = 1
# 计数,统计当前数据有几个“{id}”
datanum2 = 1
# 根据包含数字的部分拆分数据
list = re.split("(/[^/,]*\d[^/,]*)", data)
# 遍历拆分后的列表
for i in list:
# 判断当前段是否包含数字,如果不包含直接和dataString拼接
if re.compile("/[^/,]*\d[^/,]*").findall(i):
# 如果当前下标的段在idList中,则表示当前包含数字的段非真正的“{id}”;不在idList中,就拼接“{id}”
if datanum1 in idList:
dataString = dataString + i
else:
dataString = dataString + "/{id" + str(datanum2) + "}"
datanum2 = datanum2 + 1
# 计数+1
datanum1 = datanum1 + 1
else:
dataString = dataString + i

pattern.append(dataString)

# 计数,也是去重,之所以不直接去重是因为要计算当前表达式的个数来算百分比
patternsCnt = Counter(pattern)

# 将表达式加入列表patterns
for key in patternsCnt.keys():
patterns[key] = math.ceil(patternsCnt[key] / len(content) * 100)

'''
打印输出
'''
for key in patterns.keys():
print(key)
print(patterns[key])

if __name__ == '__main__':
print("开始时间:")
print(datetime.datetime.now())
main()
print("结束时间:")
print(datetime.datetime.now())

处理多参数数据模块的“白话版”代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
patternsDict{	
2:
[......],
3:
["/api/{id1}/login/{id2}/index/{id3}",
"/api/{id1}/login/{id2}/index/{id3}",
"/api/login/{id1}/index/{id2}/{id3}",
......],
4:
[......]
}
patternsBranch{
2:
[......],
3:
["/api/1/login/1/index/1",
"/api/2/login/2/index/2",
"/api/login/1/index/1/1",
......],
4:
[......]
}

假设 patternsDict[3] 中"/api/{id1}/login/{id2}/index/{id3}"有 5 个,"/api/login/{id1}/index/{id2}/{id3}"有 4 个

遍历patternsDict
cnt = Counter(patternsDict[key])

cnt{
"/api/{id1}/login/{id2}/index/{id3}" : 5 ,
"/api/login/{id1}/index/{id2}/{id3}" : 4
}

# 如果len(cnt)为1就说明只有一个表达式,不需要再做下面是否匹配表达式这一操作
if len(cnt) > 1 :

遍历cnt

转换数据得到正则表达式
例:"/api/{id1}/login/{id2}/index/{id3}",得到正则表达式"/api/[^/,]*\d[^/,]*/login/[^/,]*\d[^/,]*/index/[^/,]*\d[^/,]*"

如果value小于等于2,不再进行计算,直接把符合当前key的表达式的数据加入 patterns (patterns存放最终结果)

如果value大于2,进行下一步计算

while循环,循环次数为当前patternsDict[key],也就是包含数字的段的个数

遍历patternsBranch[3]
“/api/1/login/1/index/1”转换为“/api/{id}/login/1/index/1”,
将“/api/{id}/login/1/index/1”放入dataList

注:随着while的循环,patternsBranch[3]的数据会不断转换成“/api/1/login/{id}/index/1”、“/api/1/login/1/index/{id}”

dataSet = set(dataList),得到去重后的dataSet
if len(dataSet) / len(dataList) < 0.6 :
判断len(dataSet)不到len(dataList)的五分之三,说明当前 {id} 不是真的参数,将当前 {id} 的下标加入idList

遍历patternsBranch[3](也就是遍历一条条data)

判断是否符合当前key的正则表达式

根据idList中记录的下标,将data中对应下标的包含数字的段转换为 {id1}、{id2}、{id3}...

将转换后的data,放入列表 pattern 中

patternsCnt = Counter(pattern) , 得到patternsCnt{
"/api/{id1}/login/{id2}/index/{id3}" : 5 ,
"/api/login/{id1}/index/{id2}/{id3}" : 4
}

将patternsCnt中的表达式放入 patterns (patterns存放最终结果)

else:
重复if len(cnt) > 1 后面的计算,只是不需要再做“判断是否符合当前key的正则表达式”这一操作

整体代码不用心花时间看肯定是看不懂的,需要的就慢慢看吧。。。。