SQLAlchemy存储DataFrame到PostgreSQL时报can't adapt type 'dict'错误
报错can't adapt type 'dict'的核心原因是PostgreSQL的Python驱动psycopg2无法直接识别Python原生的字典、列表嵌套结构,无法将其转换为合法的SQL参数传入数据库。
pandas的to_sql方法默认不会对DataFrame中的复杂Python对象做自动类型适配,当列中存储的是嵌套字典/列表这类非基础类型(字符串、数字、时间等)时,驱动直接拿到原生dict对象就会抛出适配错误。
另外你代码中使用的DataFrame.append()方法已经在Pandas 2.0及以上版本移除,运行时还会触发弃用警告,建议替换为pd.concat()实现行拼接。
方案1:整组数据存入单列(匹配原始需求)
PostgreSQL原生支持JSON/JSONB类型存储半结构化数据,你只需要提前将列中的Python对象序列化为标准JSON格式,同时在写入时指定对应列的字段类型即可,代码如下:
import pandas as pd import json from sqlalchemy import create_engine, JSON # 构造测试数据 info_teamdata_df = pd.DataFrame(columns=['Team_1_bans']) my_dict = {'Team_1_bans': [ {'championId': 99, 'pickTurn': 1}, {'championId': 17, 'pickTurn': 2}, {'championId': 360, 'pickTurn': 3}, {'championId': 63, 'pickTurn': 4}, {'championId': 238, 'pickTurn': 5} ]} # 替换已弃用的append方法 info_teamdata_df = pd.concat([info_teamdata_df, pd.DataFrame([my_dict])], ignore_index=True) # 将嵌套列表字典序列化为JSON字符串 info_teamdata_df['Team_1_bans'] = info_teamdata_df['Team_1_bans'].apply(json.dumps) # 写入数据库 db = create_engine('postgresql://postgres:pass@host/MatchData_DB') with db.connect() as conn: info_teamdata_df.to_sql( 'Tester', conn, if_exists='append', index=False, # 指定列类型为JSON,驱动会自动做类型适配 dtype={'Team_1_bans': JSON} ) conn.commit()
该方案写入后,Team_1_bans列可以直接使用PostgreSQL的JSON查询语法做字段提取、条件筛选,不需要修改原有数据存储逻辑。如果追求更高的查询性能,可以将dtype指定为JSONB(SqlAlchemy中对应JSONB类型)。
方案2:结构化存储(更优实践)
如果后续需要频繁对ban位的英雄、选位顺序做统计查询,不建议将整组数据塞到单个JSON列中,建议将嵌套结构拆分为结构化的行存储,表结构更清晰,查询性能更高,代码如下:
import pandas as pd from sqlalchemy import create_engine, Integer, String # 原始嵌套数据 raw_bans = [ {'championId': 99, 'pickTurn': 1}, {'championId': 17, 'pickTurn': 2}, {'championId': 360, 'pickTurn': 3}, {'championId': 63, 'pickTurn': 4}, {'championId': 238, 'pickTurn': 5} ] # 拆分为结构化DataFrame structured_df = pd.DataFrame(raw_bans) # 补充所属队伍字段,实际业务中还可以补充对局ID等关联字段 structured_df['team'] = 'Team_1' db = create_engine('postgresql://postgres:pass@host/MatchData_DB') with db.connect() as conn: structured_df.to_sql( 'team_ban_records', conn, if_exists='append', index=False, dtype={ 'championId': Integer, 'pickTurn': Integer, 'team': String(16) } ) conn.commit()
该方案下可以直接对championId、pickTurn等字段加索引,统计类查询的写法更简单,执行效率远高于JSON字段查询。
后续遇到同类can't adapt type 'xxx'报错时,统一按照以下逻辑处理即可:
- 报错本质是psycopg2驱动无法将传入的Python对象转换为PostgreSQL支持的字段类型
- 处理路径二选一:
- 提前将Python对象转换为驱动支持的基础类型,比如将dict/list转JSON字符串、将numpy数值类型转Python原生int/float
- 通过
to_sql的dtype参数显式指定字段类型,告知驱动对应的转换规则
内容的提问来源于stack exchange,提问作者Jack

