该代码是一个16型人格(MBTI)测试的自动化API调用程序,通过提交用户答案到16personalities网站,获取测试结果并计算出具体的人格类型(包含E/I, S/N, T/F, P/J四个维度以及A/T变体)。
graph TD
A[开始] --> B[构建payload: 复制模板并填充答案]
B --> C[发送POST请求到16personalities测试接口]
C --> D[获取session信息]
D --> E[解析用户scores和traits]
E --> F{计算四个维度得分}
F --> G[调用judge_16()计算最终人格代码]
G --> H[断言两种计算方式一致性]
H --> I[返回结果字典包含各维度得分]
模块级
├── payload_template (全局变量/常量)
├── judge_16() (全局函数)
└── submit_16personality_api() (全局函数)包含56道MBTI测试问题的JSON模板,每道题包含text字段和answer字段(初始为None)
类型:dict
存储计算后的人格代码,如ISTJ、ENFP等
类型:str
16种MBTI人格代码列表,包括ISTJ到ENTJ共16种类型
类型:list
16种MBTI人格角色名称列表,如Logistician、Virtuoso等
类型:list
匹配到的人格代码在列表中的索引位置
类型:int
提交时使用的HTTP请求载荷,包含所有问题和答案
类型:dict
HTTP请求头,包含Content-Type、User-Agent、Origin等字段
类型:dict
用于维持HTTP会话的Session对象,支持cookies
类型:requests.Session
POST提交答案后的响应对象
类型:requests.Response
获取用户测试session结果的GET响应,包含scores和traits信息
类型:requests.Response
用户在各维度的原始得分列表,共5个元素
类型:list
通过直接计算traits得出的人格代码,如INFP
类型:str
通过judge_16函数计算得出的人格代码,用于验证一致性
类型:str
E/I维度得分,值范围0-100,>=50为E否则为I
类型:int
S/N维度得分,值范围0-100,>=50为N否则为S
类型:int
T/F维度得分,值范围0-100,>=50为T否则为F
类型:int
P/J维度得分,值范围0-100,>=50为J否则为P
类型:int
A/T维度得分,值范围0-100,>=50为A否则为T
类型:int
该函数接收一个包含五个整数分数的列表,根据每个分数与阈值50的大小关系分别决定MBTI人格类型的前四个字母(E/I, N/S, T/F, J/P),通过查表映射出对应的角色名称,并根据最后一个分数添加人格变体(A/T),最终返回一个包含完整MBTI代码(如"INFP-A")和角色名称(如"Mediator")的元组。
参数:
score_list:list,长度为5的整数列表,依次代表 Mind (E/I), Energy (N/S), Nature (T/F), Tactics (J/P), Identity (A/T) 五个维度的得分(0-100)。
返回值:tuple,返回包含两个字符串元素的元组。第一个元素为完整的MBTI代码(如 "INTJ-A"),第二个元素为对应的角色名称(如 "Architect")。
flowchart TD
A([Start: score_list]) --> B{score_list[0] >= 50?}
B -- Yes --> C[code = 'E']
B -- No --> D[code = 'I']
C --> E{score_list[1] >= 50?}
D --> E
E -- Yes --> F[code += 'N']
E -- No --> G[code += 'S']
F --> H{score_list[2] >= 50?}
G --> H
H -- Yes --> I[code += 'T']
H -- No --> J[code += 'F']
I --> K{score_list[3] >= 50?}
J --> K
K -- Yes --> L[code += 'J']
K -- No --> M[code += 'P']
L --> N[在 all_codes 中查找 code 对应的索引 cnt]
M --> N
N --> O{score_list[4] >= 50?}
O -- Yes --> P[code += '-A']
O -- No --> Q[code += '-T']
P --> R[role = all_roles[cnt]]
Q --> R
R --> Z([Return (code, role)])
def judge_16(score_list):
# 初始化空字符串用于存储MBTI前四位的字母
code = ''
# 1. 决定 Mind 维度 (E/I)
# 如果得分 >= 50 则为外向 (E),否则内向 (I)
if score_list[0] >= 50:
code = code + 'E'
else:
code = code + 'I'
# 2. 决定 Energy 维度 (N/S)
# 如果得分 >= 50 则为直觉 (N),否则感觉 (S)
if score_list[1] >= 50:
# Intuition: N, Observant: S
code = code + 'N'
else:
code = code + 'S'
# 3. 决定 Nature 维度 (T/F)
# 如果得分 >= 50 则为思考 (T),否则情感 (F)
if score_list[2] >= 50:
code = code + 'T'
else:
code = code + 'F'
# 4. 决定 Tactics 维度 (J/P)
# 如果得分 >= 50 则为判断 (J),否则知觉 (P)
if score_list[3] >= 50:
code = code + 'J'
else:
code = code + 'P'
# 定义所有16种MBTI基础类型代码列表
all_codes = ['ISTJ', 'ISTP', 'ISFJ', 'ISFP', 'INFJ', 'INFP', 'INTJ', 'INTP', 'ESTP', 'ESTJ', 'ESFP', 'ESFJ', 'ENFP', 'ENFJ', 'ENTP', 'ENTJ']
# 定义对应的16种角色名称列表
all_roles = ['Logistician', 'Virtuoso', 'Defender', 'Adventurer', 'Advocate', 'Mediator', 'Architect', 'Logician', 'Entrepreneur', 'Executive', 'Entertainer',
'Consul', 'Campaigner', 'Protagonist', 'Debater', 'Commander']
# 通过遍历查找当前生成的4字母代码在列表中的索引位置
# 注意:此处使用线性查找,效率略低
for i in range(len(all_codes)):
if code == all_codes[i]:
cnt = i
break
# 5. 决定 Identity 维度 (A/T) - 变体
# 如果得分 >= 50 则为坚定型 (A),否则动荡型 (T)
if score_list[4] >= 50:
code = code + '-A'
else:
code = code + '-T'
# 根据索引从角色列表中取出对应的角色名
return code, all_roles[cnt] all_codes:list,定义了16种MBTI基础类型(4字母)的静态列表。all_roles:list,定义了与all_codes对应的16种角色名称的静态列表。score_list:list,函数输入,包含5个整数分数。
- 输入校验缺失:函数未对
score_list的长度、类型及数值范围进行校验。如果传入空列表或长度不足的列表,将会导致IndexError异常。 - 魔法数字:判断维度取向的阈值
50是硬编码的。如果需要调整阈(例如允许更细腻的区分),需要修改函数逻辑,建议将其提取为默认参数或配置常量。 - 查找效率:使用
for循环遍历all_codes列表来查找索引,时间复杂度为 O(N)(N=16)。虽然 N 很小,但可以使用字典(Dictionary)直接映射code -> role,将复杂度降为 O(1)。 - 重复定义:列表
all_codes和all_roles在每次调用函数时都会在内存中重新创建一次。考虑到它们是常量,建议将其提升至模块全局作用域,避免资源浪费。 - 并行列表维护:使用两个独立的列表(
all_codes和all_roles)通过索引关联,如果修改其中一个列表而忘记修改另一个,会导致数据错乱。建议使用字典(Dict)存储映射关系,例如MBTI_ROLES = {'ISTJ': 'Logistician', ...}。
- 设计目标:将连续的数值分数(0-100)离散化为标准的MBTI字符串和角色名称。
- 约束:输入分数通常为整数,且遵循 16Personalities 的评分标准(0-100)。
- 错误处理:目前仅依赖Python内置的索引访问,传入异常数据时程序会直接崩溃,缺乏友好的错误提示。
- 数据流:输入
[mind, energy, nature, tactics, identity]-> 字符串拼接逻辑 -> 查表逻辑 -> 字符串拼接(变体) -> 输出(code, role)。
该函数是提交16型人格测试答案的核心功能,通过将用户答案填充到请求载荷模板中,向16personalities.com网站发送POST请求提交测试,随后通过GET请求获取服务器返回的评分结果,最后根据原始分数和转换后的分数计算并返回E/I、S/N、T/F、P/J四个人格维度的结果字典。
参数:
Answers:list,用户对57道人格测试问题的答案列表,每个元素对应一个问题
返回值:dict,返回包含四个人格维度(E/I、S/N、T/F、P/J)结果的字典,每个维度包含结果字母和对应分数
flowchart TD
A[开始 submit_16personality_api] --> B[深拷贝 payload_template]
B --> C[遍历 Answers 填充问题答案]
C --> D[构建 HTTP 请求头 headers]
D --> E[创建 requests session]
E --> F[POST 提交到 /test-results]
F --> G[GET 获取 session 数据 /api/session]
G --> H[解析 session JSON 获取 scores 和 traits]
H --> I{遍历四个维度<br/>mind/energy/nature/tactics}
I --> J{trait == expected}
J -->|是| K[计算 value = (101+score)//2]
J -->|否| L[计算 value = 100 - (101+score)//2]
K --> M[添加对应字母 E/I N/S T/F P/J]
L --> M
M --> N[处理 identity 维度 A/T]
N --> O[调用 judge_16 计算人格类型]
O --> P[断言 ans1 == ans2 验证一致性]
P --> Q[构建返回字典]
Q --> R[结束 返回结果]
def submit_16personality_api(Answers):
"""
提交16型人格测试答案并返回人格测试结果
该函数完成以下步骤:
1. 将用户答案填充到预设的问题模板中
2. 向16personalities.com发送POST请求提交答案
3. 获取session数据解析评分
4. 计算四个维度的得分和结果
5. 返回包含E/I、S/N、T/F、P/J的字典结果
"""
# 第一步:深拷贝payload模板,避免修改原始模板
payload = copy.deepcopy(payload_template)
# 第二步:将用户答案填入对应问题的answer字段
# payload_template 包含57个问题,与Answers列表一一对应
for index, A in enumerate(Answers):
payload['questions'][index]["answer"] = A
# 第三步:构建完整的HTTP请求头
# 模拟Chrome浏览器的请求头,包括常见的HTTP头信息
headers = {
"accept": "application/json, text/plain, */*",
"accept-encoding": "gzip, deflate, br",
"accept-language": "en,zh-CN;q=0.9,zh;q=0.8",
"content-length": "5708", # 固定内容长度
"content-type": "application/json",
"origin": "https://www.16personalities.com",
"referer": "https://www.16personalities.com/free-personality-test",
"sec-ch-ua": "'Not_A Brand';v='99', 'Google Chrome';v='109', 'Chromium';v='109'",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "Windows",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
'content-type': 'application/json', # 重复定义,会覆盖上面的
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36',
}
# 第四步:创建session并发送POST请求提交答案
# 使用session可以保持cookies和连接
session = requests.session()
# 将payload序列化为JSON字符串发送
r = session.post('https://www.16personalities.com/test-results', data=json.dumps(payload), headers=headers)
# 第五步:获取session数据
# POST响应后,通过GET请求获取用户的评分和人格特质
sess_r = session.get("https://www.16personalities.com/api/session")
# 第六步:解析返回的scores数组
# scores包含5个分数:mind, energy, nature, tactics, identity
scores = sess_r.json()['user']['scores']
# 第七步:计算四个维度的值和字母
# 根据原始分数和trait类型计算最终得分
ans1 = ''
session = requests.session() # 重新创建session(此处逻辑可优化)
if sess_r.json()['user']['traits']['mind'] != 'Extraverted':
# 如果特质不是外向型,则用100减去计算值(内向型)
mind_value = 100 - (101 + scores[0]) // 2
ans1 += 'I' # 添加内向型字母
else:
mind_value = (101 + scores[0]) // 2
ans1 += 'E' # 添加外向型字母
if sess_r.json()['user']['traits']['energy'] != 'Intuitive':
energy_value = 100 - (101 + scores[1]) // 2
ans1 += 'S' # 添加观察型字母
else:
energy_value = (101 + scores[1]) // 2
ans1 += 'N' # 添加直觉型字母
if sess_r.json()['user']['traits']['nature'] != 'Thinking':
nature_value = 100 - (101 + scores[2]) // 2
ans1 += 'F' # 添加情感型字母
else:
nature_value = (101 + scores[2]) // 2
ans1 += 'T' # 添加思考型字母
if sess_r.json()['user']['traits']['tactics'] != 'Judging':
tactics_value = 100 - (101 + scores[3]) // 2
ans1 += 'P' # 添加知觉型字母
else:
tactics_value = (101 + scores[3]) // 2
ans1 += 'J' # 添加判断型字母
# 第八步:处理identity维度(Assertive vs Turbulent)
if sess_r.json()['user']['traits']['identity'] != 'Assertive':
identity_value = 100 - (101 + scores[4]) // 2
else:
identity_value = (101 + scores[4]) // 2
# 第九步:调用judge_16函数验证结果一致性
# judge_16函数基于转换后的分数计算人格类型
code, role = judge_16([mind_value, energy_value, nature_value, tactics_value, identity_value])
# 第十步:提取四字母代码用于断言验证
ans2 = code[:4]
# 第十一步:断言验证两种计算方式的结果一致性
# 如果不一致会抛出AssertionError
assert(ans1 == ans2)
# 第十二步:构建并返回最终结果字典
# 包含每个人格维度的结果字母和详细分数
return {
"E/I": {"result": ans1[0], "score": {"E": mind_value, "I": 100 - mind_value}},
"S/N": {"result": ans1[1], "score": {"S": 100 - energy_value, "N": energy_value}},
"T/F": {"result": ans1[2], "score": {"T": nature_value, "F": 100 - nature_value}},
"P/J": {"result": ans1[3], "score": {"P": 100 - tactics_value, "J": tactics_value}},
} 一个包含60道MBTI人格测试问题的JSON模板,每道题包含问题文本和初始答案为None,用于构建API请求的payload。
根据5个维度的分数(mind, energy, nature, tactics, identity)计算并返回MBTI人格类型编码(如"INTJ-A")和对应的角色名称(如"Architect")。
核心API交互函数,接收用户答案,构造请求payload,发送到16Personalities网站,获取并解析测试结果,返回包含E/I、S/N、T/F、P/J四个维度得分和结果的字典。
使用requests.session()维护会话,构造完整的HTTP请求头(包括Accept、Content-Type、Origin、Referer等),处理响应并解析JSON数据获取用户分数和人格特质。
将API返回的原始分数转换为0-100的值,根据mind/energy/nature/tactics/identity五个维度与对应特质(如Extraverted/Introverted)的比较结果,决定最终的人格类型字母。
使用assert验证本地计算的人格类型编码与API返回的编码一致,确保数据准确性。
包含硬编码的请求头、API端点URL、16种MBTI类型代码及对应角色名称列表。
- 重复调用API响应:多次调用
sess_r.json()获取相同数据(约6次),造成性能浪费和冗余网络请求。 - 硬编码的配置问题:
content-length被硬编码为 "5708",与实际payload长度不匹配,可能导致请求失败。 - 未使用的变量:变量
a(content-type)和b(encoding)被赋值但从未使用。 - 调试代码残留:导入了
pdb模块但未使用,代码中包含大量注释掉的调试print语句。 - magic number 遍布:多处使用魔数(如50、100、101、5708等),缺乏常量定义,可读性和可维护性差。
- 重复代码逻辑:5个性格维度的计算逻辑高度重复(mind/energy/nature/tactics/identity),违反DRY原则。
- 会话管理混乱:在函数中创建了两个
requests.session()对象,逻辑不清晰。 - 缺少错误处理:无任何异常捕获机制,无法处理网络错误、API响应异常等情况。
- 潜在的断言失败风险:末尾的
assert(ans1 == ans2)在生产环境中可能触发AssertionError导致程序崩溃。 - hardcode的payload模板:60个问题的模板直接写在全局变量中,无法灵活配置问题内容。
- 缺乏类型注解:函数参数和返回值均无类型提示,降低代码可读性和IDE支持。
- judge_16函数的逻辑缺陷:遍历查找编码时若未匹配不会设置cnt变量,可能导致后续代码引用未定义变量。
- 将
sess_r.json()的结果缓存到变量中,只调用一次。 - 使用
len(json.dumps(payload))动态计算content-length或直接移除该字段让requests库自动处理。 - 删除未使用的变量
a和b。 - 移除
pdb导入和所有调试用的注释代码。 - 将magic number提取为模块级常量(如
SCORE_THRESHOLD = 50,MAX_SCORE = 100等)。 - 将重复的性格维度计算逻辑抽取为通用函数,如
calculate_trait_score(trait_name, score, reverse_mapping)。 - 简化会话管理,只创建一个session对象并复用。
- 添加try-except块处理网络请求异常和JSON解析错误。
- 将assertion改为条件检查并返回合理的错误信息或降级处理。
- 将payload_template改为从配置文件或数据库加载,或至少拆分为静态问题列表和动态答案结构。
- 为函数添加类型注解和文档字符串。
- 在
judge_16函数中添加else分支处理未匹配到编码的情况,或使用字典直接映射。
本代码的设计目标是自动化完成16Personalities网站的MBTI人格测试,包括提交答案、获取评分并解析出最终的人格类型(基于E/I、S/N、T/F、P/J四个维度以及A/T变体)。主要约束包括:1)依赖外部API(https://www.16personalities.com),需保持与网站接口的兼容性;2)payload_template中硬编码了60道测试问题,问题顺序和数量不可变;3)使用同步requests库进行HTTP通信,不支持异步并发;4)仅支持单次测试提交场景,不支持批量测试。
代码中错误处理机制薄弱,主要存在以下问题:1)网络请求无重试逻辑,连接失败直接抛出异常;2)JSON解析仅在固定路径(sess_r.json()['user']['scores']、sess_r.json()['user']['traits'])取值,若API返回结构变化会导致KeyError;3)submit_16personality_api函数无任何try-except包裹,API返回非200状态码时requests会抛出HTTPError;4)断言assert(ans1 == ans2)用于校验计算一致性,但断言可被Python优化选项禁用。建议:添加requests超时设置、JSON字段存在性检查、API响应状态码验证、捕获requests异常并返回有意义的错误信息。
数据流处理如下:输入Answers列表(60个答案)→复制payload_template→遍历填充answers到questions字段→POST提交到测试结果接口→GET获取session数据→解析scores数组(5个维度分数)→根据原始trait值判断方向并计算最终分数→调用judge_16生成人格代码。状态转换:INIT(初始化payload)→SUBMITTING(发送POST请求)→FETCHING_SESSION(获取session结果)→CALCULATING(计算各维度分数)→MAPPING(映射人格类型)→RETURNING(返回结果)。无独立状态机实现,状态隐含在函数执行流程中。
外部依赖包括:1)requests库用于HTTP通信;2)json库用于序列化payload;3)copy库用于深拷贝模板;4)pdb库(已导入但未使用)。接口契约方面:submit_16personality_api接收Answers参数(类型应为list,内容为各问题答案),返回字典包含E/I、S/N、T/F、P/J四个维度的结果和分数。judge_16函数接收score_list(5个整数分数),返回code(如"INTJ-A")和role(如"Architect")。与16Personalities网站的接口依赖:POST /test-results和GET /api/session,需保持Same-Origin策略和特定的请求头才能正常访问。
存在以下安全隐患:1)payload_template中inviteCode、teamInviteKey、extraData为空字符串,可能泄露用户信息或被恶意利用;2)硬编码的请求头中sec-ch-ua等浏览器指纹信息可能被用于反爬虫检测;3)代码未对用户输入的Answers进行校验,若传入非预期值可能导致payload结构异常;4)session对象未显式关闭,连接资源可能泄漏。建议:对敏感字段进行脱敏处理、添加输入验证、使用上下文管理器管理session、考虑将硬编码的请求头参数化。
性能瓶颈分析:1)每次调用submit_16personality_api会创建新的requests.Session,频繁创建session有性能开销;2)payload_template的deepcopy操作对60个问题的字典进行深拷贝,内存开销较大;3)代码中多次调用sess_r.json()解析同一响应(如6次访问sess_r.json()['user']['traits']),造成重复解析;4)judge_16函数中使用线性搜索遍历all_codes列表(16个元素),可优化为字典映射。建议:复用session对象、缓存json解析结果、使用字典查找替代循环、考虑将payload_template改为每次动态生成仅包含答案的轻量结构。
当前代码配置管理混乱:1)payload_template作为全局变量硬编码在文件顶部,包含60道问题的完整文本,难以维护;2)API endpoint(https://www.16personalities.com/test-results和/api/session)直接写在代码中;3)请求头headers字典包含大量静态配置(accept-language、content-length、sec-ch-ua等),这些技术细节与业务逻辑耦合。建议:将payload_template移至独立JSON配置文件、将API endpoints参数化、把请求头中与业务无关的技术参数提取为配置常量、考虑使用环境变量或配置文件管理不同环境的API地址。
代码完全缺失日志记录功能:1)无任何print语句(注释掉的除外)用于运行时信息输出;2)未使用Python logging模块;3)API请求的响应状态码、响应时间等关键信息未被记录;4)异常发生时的上下文信息无法追溯。建议:在关键节点(请求发送前、响应接收后、分数计算前后)添加logging.info日志;异常捕获时使用logging.exception记录完整堆栈;考虑添加debug级别日志用于记录payload内容和API响应(注意脱敏)。
代码未包含任何测试:1)无unittest或pytest测试文件;2)未对judge_16函数的逻辑进行单元测试(16种人格类型的映射关系、分数阈值的边界情况);3)未对submit_16personality_api进行集成测试;4)缺少对payload构造逻辑的验证。建议:编写judge_16的单元测试覆盖16种人格类型;添加API mock测试验证payload构造;测试网络异常场景下的错误处理;考虑添加性能基准测试。
主要技术债务包括:1)pdb模块被导入但未使用,应删除;2)注释掉的调试代码(print语句)应清理;3)变量命名不一致(如sess_r、r、session混用);4)requests.Session()被创建两次(第二次在函数末尾附近但实际未使用);5)content-length (5708)为硬编码值,与实际payload大小可能不匹配;6)函数docstring完全缺失。建议:删除未使用的导入和注释代码、统一变量命名规范、补充文档字符串、移除重复的session创建、根据实际payload动态计算content-length。