问题
怎样在AWS Glue Python Shell任务中读取Athena数据库中的数据
通过awswrangler读取
Python
python
import pandas as pd
import boto3
import awswrangler as wr
....
def read_data_via_athena(database, table_name, start_ts, end_ts):
"""
使用 Athena 解决大数据量下的内存问题
"""
# 1. 编写 SQL。在服务端完成过滤,只拉取需要的列和行
# 建议显式写出需要的列名,如 SELECT id, created_at, user_id ... 替代 SELECT *
sql = f"""
SELECT * FROM "{table_name}"
WHERE created_at >= {start_ts}
AND created_at < {end_ts}
"""
logger.info(f"正在执行 Athena 查询,筛选表 {table_name} 的数据...")
try:
# 2. 执行查询并直接获取 DataFrame
batch_iterator = wr.athena.read_sql_query(
sql=sql,
database=database,
ctas_approach=False,
chunksize=5000,
keep_files=False # 查询完自动清理临时文件
)
return batch_iterator
except Exception as e:
logger.error(f"Athena 查询异常: {str(e)}")
raise
调用使用代码(这里就通过aw从数据湖里面读出了pandas数据):
python
# 从 Glue Catalog 读取三个表的数据(表名完全由参数控制)
tables = {
table_summit: 'summit',
table_summit_device: 'summit_device',
table_summit_user: 'summit_user'
}
total_pushed_count = 0
batch_size_push = 100 # API 每次接收的记录数
for glue_table, type_name in tables.items():
logger.info(f"开始处理表: {glue_table}")
# 1. 获取 Athena 迭代器 (每批从 Athena 取 50000 行)
batch_iterator = read_data_via_athena(database_name, glue_table, start_ts, end_ts)
for idx, df_chunk in enumerate(batch_iterator, start=1):
if df_chunk.empty:
continue
logger.info(f"表 {glue_table} - 正在处理 Athena 第 {idx} 批数据,行数:{len(df_chunk)}")
# 2. 将这 5000 行转换为记录列表
chunk_records = convert_dataframe_to_records(df_chunk, type_name)
# --- 💡 新增:打印前 10 条数据进行查看 ---
if idx == 1: # 只在每个表的第一批次打印,避免日志刷屏
logger.info(f"🔍 检查表 {glue_table} 的前 10 条数据样本:")
sample_data = chunk_records[:10]
# 使用 json.dumps 让打印出的 JSON 格式更漂亮(缩进2格)
print(json.dumps(sample_data, indent=2, ensure_ascii=False))
# ---------------------------------------
# 3. 针对这一批记录,再切分成 100 条一组的小批次推送到 API
for i in range(0, len(chunk_records), batch_size_push):
mini_batch = chunk_records[i : i + batch_size_push]
# 计算当前进度
current_batch_end = i + len(mini_batch)
try:
# TODO 此处模拟推送数据到第三方系统
logger.info(f"[{glue_table}] 正在处理 Athena 第 {idx} 块: 进度 {current_batch_end}/{len(chunk_records)} 条 | 总计已推送: {total_pushed_count + len(mini_batch)}")
total_pushed_count += len(mini_batch)
# 控制推送频率
if send_delay > 0:
time.sleep(send_delay)
except Exception as e:
logger.error(f"表 {glue_table} 数据推送失败: {e}")
raise # 根据业务需求决定是跳过还是中断
# 💡 重要:手动清理当前 chunk 占用的内存(可选,但推荐)
del chunk_records
del df_chunk
elapsed = (datetime.now() - start_time).total_seconds()
logger.info(f"作业全部完成!总计成功推送: {total_pushed_count} 条,总耗时 {elapsed:.2f} 秒")
数据处理函数(这部分代码根据你的业务来处理):
python
import datetime
import decimal
def convert_dataframe_to_records(df: pd.DataFrame, table_type: str) -> List[dict]:
"""将 DataFrame 转换为字典列表,处理所有不可 JSON 序列化的类型"""
records = df.to_dict(orient='records')
for rec in records:
rec['add_table'] = table_type
for k, v in list(rec.items()):
if pd.isna(v):
rec[k] = None
# 💡 增加对 datetime.date 和 datetime.datetime 的统一处理
elif isinstance(v, (pd.Timestamp, datetime.datetime, datetime.date)):
rec[k] = v.isoformat()
# 💡 针对 Decimal 类型(如 gps_latitude)也建议处理一下,防止后续报错
elif hasattr(v, 'to_eng_string'):
rec[k] = float(v)
return records