Skip to content

Latest commit

 

History

History
578 lines (363 loc) · 24.4 KB

File metadata and controls

578 lines (363 loc) · 24.4 KB

Chat-Haruhi-Suzumiya\research\personality\code\api_16personality.py 详细设计文档

该代码是一个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[返回结果字典包含各维度得分]
Loading

类结构

模块级
├── payload_template (全局变量/常量)
├── judge_16() (全局函数)
└── submit_16personality_api() (全局函数)

全局变量及字段

payload_template

包含56道MBTI测试问题的JSON模板,每道题包含text字段和answer字段(初始为None)

类型:dict

code

存储计算后的人格代码,如ISTJ、ENFP等

类型:str

all_codes

16种MBTI人格代码列表,包括ISTJ到ENTJ共16种类型

类型:list

all_roles

16种MBTI人格角色名称列表,如Logistician、Virtuoso等

类型:list

cnt

匹配到的人格代码在列表中的索引位置

类型:int

payload

提交时使用的HTTP请求载荷,包含所有问题和答案

类型:dict

headers

HTTP请求头,包含Content-Type、User-Agent、Origin等字段

类型:dict

session

用于维持HTTP会话的Session对象,支持cookies

类型:requests.Session

r

POST提交答案后的响应对象

类型:requests.Response

sess_r

获取用户测试session结果的GET响应,包含scores和traits信息

类型:requests.Response

scores

用户在各维度的原始得分列表,共5个元素

类型:list

ans1

通过直接计算traits得出的人格代码,如INFP

类型:str

ans2

通过judge_16函数计算得出的人格代码,用于验证一致性

类型:str

mind_value

E/I维度得分,值范围0-100,>=50为E否则为I

类型:int

energy_value

S/N维度得分,值范围0-100,>=50为N否则为S

类型:int

nature_value

T/F维度得分,值范围0-100,>=50为T否则为F

类型:int

tactics_value

P/J维度得分,值范围0-100,>=50为J否则为P

类型:int

identity_value

A/T维度得分,值范围0-100,>=50为A否则为T

类型:int

全局函数及方法

judge_16

该函数接收一个包含五个整数分数的列表,根据每个分数与阈值50的大小关系分别决定MBTI人格类型的前四个字母(E/I, N/S, T/F, J/P),通过查表映射出对应的角色名称,并根据最后一个分数添加人格变体(A/T),最终返回一个包含完整MBTI代码(如"INFP-A")和角色名称(如"Mediator")的元组。

参数:

  • score_listlist,长度为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)])
Loading

带注释源码

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_codeslist,定义了16种MBTI基础类型(4字母)的静态列表。
  • all_roleslist,定义了与all_codes对应的16种角色名称的静态列表。
  • score_listlist,函数输入,包含5个整数分数。

潜在的技术债务或优化空间

  1. 输入校验缺失:函数未对 score_list 的长度、类型及数值范围进行校验。如果传入空列表或长度不足的列表,将会导致 IndexError 异常。
  2. 魔法数字:判断维度取向的阈值 50 是硬编码的。如果需要调整阈(例如允许更细腻的区分),需要修改函数逻辑,建议将其提取为默认参数或配置常量。
  3. 查找效率:使用 for 循环遍历 all_codes 列表来查找索引,时间复杂度为 O(N)(N=16)。虽然 N 很小,但可以使用字典(Dictionary)直接映射 code -> role,将复杂度降为 O(1)。
  4. 重复定义:列表 all_codesall_roles 在每次调用函数时都会在内存中重新创建一次。考虑到它们是常量,建议将其提升至模块全局作用域,避免资源浪费。
  5. 并行列表维护:使用两个独立的列表(all_codesall_roles)通过索引关联,如果修改其中一个列表而忘记修改另一个,会导致数据错乱。建议使用字典(Dict)存储映射关系,例如 MBTI_ROLES = {'ISTJ': 'Logistician', ...}

其它项目

  • 设计目标:将连续的数值分数(0-100)离散化为标准的MBTI字符串和角色名称。
  • 约束:输入分数通常为整数,且遵循 16Personalities 的评分标准(0-100)。
  • 错误处理:目前仅依赖Python内置的索引访问,传入异常数据时程序会直接崩溃,缺乏友好的错误提示。
  • 数据流:输入 [mind, energy, nature, tactics, identity] -> 字符串拼接逻辑 -> 查表逻辑 -> 字符串拼接(变体) -> 输出 (code, role)

submit_16personality_api

该函数是提交16型人格测试答案的核心功能,通过将用户答案填充到请求载荷模板中,向16personalities.com网站发送POST请求提交测试,随后通过GET请求获取服务器返回的评分结果,最后根据原始分数和转换后的分数计算并返回E/I、S/N、T/F、P/J四个人格维度的结果字典。

参数:

  • Answerslist,用户对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[结束 返回结果]
Loading

带注释源码

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}},
    }                     

关键组件

payload_template

一个包含60道MBTI人格测试问题的JSON模板,每道题包含问题文本和初始答案为None,用于构建API请求的payload。

judge_16

根据5个维度的分数(mind, energy, nature, tactics, identity)计算并返回MBTI人格类型编码(如"INTJ-A")和对应的角色名称(如"Architect")。

submit_16personality_api

核心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库自动处理。
  • 删除未使用的变量 ab
  • 移除 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。