在数据工程与数据分析场景中,经常需要将本地 Parquet 文件与 Amazon Redshift 中的查询结果合并,并输出为单个 Excel 文件的不同 Sheet。本文基于 Python 生态中的 pandas、redshift_connector、awswrangler 和 openpyxl,实现一套完整的从本地读取、Redshift UNLOAD 至 S3、下载合并、排序再到 Excel 多 Sheet 写入的自动化流程。
## 环境与依赖
首先安装必要的 Python 包:- pip install pandas pyarrow redshift_connector awswrangler openpyxl boto3
复制代码 导入时依次引入:- import os
- import pandas as pd
- import redshift_connector
- import awswrangler as wr
- import boto3
- from datetime import datetime
- from pathlib import Path
复制代码
## 1. 从本地 Parquet 读取 DataFrame(df_1)
使用 pandas 的 read_parquet 方法,引擎指定为 pyarrow(默认),对大列式存储文件性能更优。可通过 use_threads=True 启用多线程读取,memory_map=True 使用内存映射降低 I/O 开销。- parquet_path = "./data/source_data.parquet"
- df_1 = pd.read_parquet(parquet_path, engine="pyarrow")
复制代码
## 2. 从 Amazon Redshift 通过 UNLOAD 导出数据至 S3 并加载为 DataFrame(df_2)
此环节整合了数据库连接、UNLOAD 操作、S3 读取三个步骤。
### 2.1 建立 Redshift 连接
使用 redshift_connector.connect 方法,从环境变量中读取主机、数据库、端口、用户名和密码:- conn = redshift_connector.connect(
- host=os.getenv("REDSHIFT_HOST"),
- database=os.getenv("REDSHIFT_DATABASE"),
- port=int(os.getenv("REDSHIFT_PORT", 5439)),
- user=os.getenv("REDSHIFT_USER"),
- password=os.getenv("REDSHIFT_PASSWORD")
- )
复制代码
### 2.2 执行 UNLOAD 到 S3
利用 awswrangler 的 wr.redshift.unload_to_files 封装 Redshift 的 UNLOAD 命令。为避免路径覆盖,在 S3 路径中嵌入时间戳:- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
- s3_unload_path = f"s3://your-bucket/redshift_unload/{timestamp}/"
- sql_query = """
- SELECT id, column_a, column_b, created_at
- FROM your_schema.your_table
- WHERE created_at >= '2026-01-01'
- """
- wr.redshift.unload_to_files(
- sql=sql_query,
- path=s3_unload_path,
- con=conn,
- iam_role=os.getenv("REDSHIFT_IAM_ROLE"),
- unload_format="PARQUET",
- parallel=True,
- region=os.getenv("AWS_REGION", "us-east-1")
- )
复制代码 关键参数说明:
- unload_format 选 PARQUET 可获得更高压缩率,后续读取更快。
- parallel=True 利用 Redshift 集群切片并行导出,显著提升大数据量场景下的速度。
- iam_role 必须具有 s3:PutObject 和 s3:ListBucket 权限,否则会遇到 AccessDenied。
- region 需与 S3 桶区域一致;若 Redshift 与 S3 在不同区域,务必显式传递 region 参数。
### 2.3 从 S3 读取为 DataFrame
使用 boto3.Session 配置区域,再通过 awswrangler.s3.read_parquet 自动识别 S3 路径下的所有 Parquet 文件并合并读取:- session = boto3.Session(region_name=os.getenv("AWS_REGION", "us-east-1"))
- df_2 = wr.s3.read_parquet(path=s3_unload_path, boto3_session=session)
复制代码 如果 df_2 数据量极大,可使用 chunked=True 分块读取以控制内存。
### 2.4 关闭连接
## 3. 统一按 id 列排序
为保证 Excel 中两个 Sheet 的数据顺序一致,对两个 DataFrame 均按 id 升序排列,并重置索引:- df_1 = df_1.sort_values(by="id", ascending=True, ignore_index=True)
- df_2 = df_2.sort_values(by="id", ascending=True, ignore_index=True)
复制代码 ignore_index=True 避免旧索引写入 Excel。
## 4. 写入同一 Excel 文件的不同 Sheet
使用 pd.ExcelWriter 搭配 openpyxl 引擎(对 .xlsx 格式支持良好),通过 with 语句确保文件自动保存关闭:- output_path = Path("./output/mult_sheets_1.xlsx")
- output_path.parent.mkdir(parents=True, exist_ok=True)
- with pd.ExcelWriter(output_path, engine="openpyxl") as writer:
- df_1.to_excel(writer, sheet_name="df_1", index=False)
- df_2.to_excel(writer, sheet_name="df_2", index=False)
复制代码 index=False 防止行索引写入。若需更多 Sheet 继续调用 to_excel 即可。
## 5. 完整代码整合
将上述片段合并为可独立运行的脚本:- import os
- import pandas as pd
- import redshift_connector
- import awswrangler as wr
- import boto3
- from datetime import datetime
- from pathlib import Path
- # 配置
- parquet_path = "./data/source_data.parquet"
- output_path = Path("./output/mult_sheets_1.xlsx")
- output_path.parent.mkdir(parents=True, exist_ok=True)
- sql_query = """
- SELECT id, column_a, column_b, created_at
- FROM your_schema.your_table
- WHERE created_at >= '2026-01-01'
- """
- # 1. 读取本地 Parquet
- df_1 = pd.read_parquet(parquet_path, engine="pyarrow")
- # 2. 连接 Redshift
- conn = redshift_connector.connect(
- host=os.getenv("REDSHIFT_HOST"),
- database=os.getenv("REDSHIFT_DATABASE"),
- port=int(os.getenv("REDSHIFT_PORT", 5439)),
- user=os.getenv("REDSHIFT_USER"),
- password=os.getenv("REDSHIFT_PASSWORD")
- )
- # 3. UNLOAD 到 S3
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
- s3_unload_path = f"s3://your-bucket/redshift_unload/{timestamp}/"
- wr.redshift.unload_to_files(
- sql=sql_query,
- path=s3_unload_path,
- con=conn,
- iam_role=os.getenv("REDSHIFT_IAM_ROLE"),
- unload_format="PARQUET",
- parallel=True,
- region=os.getenv("AWS_REGION", "us-east-1")
- )
- # 4. 从 S3 读取
- df_2 = wr.s3.read_parquet(path=s3_unload_path, boto3_session=boto3.Session(region_name=os.getenv("AWS_REGION", "us-east-1")))
- # 5. 关闭连接
- conn.close()
- # 6. 排序
- df_1 = df_1.sort_values(by="id", ascending=True, ignore_index=True)
- df_2 = df_2.sort_values(by="id", ascending=True, ignore_index=True)
- # 7. 写入 Excel
- with pd.ExcelWriter(output_path, engine="openpyxl") as writer:
- df_1.to_excel(writer, sheet_name="df_1", index=False)
- df_2.to_excel(writer, sheet_name="df_2", index=False)
- print(f"Excel 已生成:{output_path}")
复制代码
## 6. 常见问题与调试建议
### 6.1 UNLOAD 权限错误
若遇到 AccessDenied,需检查 IAM 角色的 s3:PutObject 和 s3:ListBucket 权限是否授予。
### 6.2 区域不匹配
Redshift 与 S3 桶跨区域时,通过 region 参数指定桶所在区域。
### 6.3 时间戳路径覆盖问题
每次运行生成新时间戳子目录,避免使用 ALLOWOVERWRITE 覆盖历史数据。建议在脚本末尾清理过期 S3 临时文件(可用 wr.s3.delete_objects)。
### 6.4 性能优化
- 大数据量时 parallel=True 有效,若需严格顺序则设 parallel=False 配合 ORDER BY。
- 读取超大 df_2 时使用 wr.s3.read_parquet(..., chunked=True) 分块处理。
- 本地 Parquet 读取可加 memory_map=True 降低内存。
## 7. 扩展建议
- 将 Redshift 连接参数、S3 桶名、SQL 等抽取为配置文件或环境变量,提高可移植性。
- 添加 logging 记录 UNLOAD 耗时、数据行数。
- 对 UNLOAD 和 S3 读取加入重试机制应对网络波动。
- 使用 openpyxl 或 xlsxwriter 自定义 Excel 单元格样式(列宽、字体、颜色)。
此方案覆盖了从本地数据、云端仓库、对象存储到最终 Excel 输出的全链路,适用于数据质量比对、多源融合报表等场景。代码已在 Python 3.9+ 及对应库版本下测试通过。 |