### 问题解构与方案推演
针对上述反馈的两个核心问题(仅提取单个文件、提取逻辑偏差),结合 Python 在 Excel 批量处理与数据提取中的优势,进行如下解构与方案重构:
1. **问题 1 分析(仅提取单个文件)**:原 VBA 代码逻辑中可能存在文件遍历循环的中断条件错误,或者结果写入逻辑未正确覆盖循环体内的每一次迭代。在 Python 中,利用 `os.listdir` 或 `glob` 模块可以稳健地遍历文件夹,配合 `pandas` 或 `openpyxl` 逐一读取文件,确保不遗漏任何工作簿 [ref_1][ref_2]。
2. **问题 2 分析(提取整列而非指定行)**:原需求是“筛选通过后,提取指定列的某一特定位置的数据(如首行、末行或指定行)”,而非提取该列的所有数据。代码逻辑应修正为:先定位满足筛选条件的行,再根据 OptionButton 的配置,在该行或相对位置提取目标单元格的值 [ref_6]。
3. **方案推演**:
* **文件遍历**:使用 `os.walk` 或 `glob.glob` 获取目标文件夹下所有 `.xlsx` / `.xls` 文件路径。
* **数据读取**:使用 `pandas.read_excel` 读取指定索引的工作表。Pandas 在处理结构化数据筛选和特定行列提取时效率极高,代码更简洁 [ref_6]。
* **双重筛选**:利用 Pandas 的布尔索引或 `query` 方法实现两级筛选。
* **精准提取**:根据筛选结果,使用 `iloc` 或 `iat` 定位到具体的行列坐标提取单个值,而非整列数据。
* **结果合并**:将提取的单个数据追加到结果列表中,最后统一写入新工作表。
---
### Python 解决方案与代码实现
以下代码使用 `pandas` 和 `openpyxl` 库实现需求。相比 VBA,Python 在处理大批量文件和复杂逻辑时具有更好的可读性和扩展性 [ref_6]。
#### 1. 环境准备
请确保安装了以下库:
```bash
pip install pandas openpyxl
```
#### 2. 完整代码实现
```python
import os
import pandas as pd
from openpyxl import Workbook
from openpyxl.utils import get_column_letter
# ==============================================
# 函数名称: extract_data_from_excel
# 功能描述: 遍历文件夹,提取符合双重筛选条件的指定行数据
# 参数说明:
# folder_path: 包含Excel文件的文件夹路径
# sheet_index: 要处理的工作表索引(从0开始)
# filter_col_1: 第一级筛选列号(字母,如 'A')
# filter_vals_1: 第一级筛选内容列表(如 ['完成', '进行中'])
# filter_col_2: 第二级筛选列号(字母)
# filter_vals_2: 第二级筛选内容列表
# extract_col: 需要提取数据的列号(字母)
# row_type: 行位置类型 ('First', 'Last', 'Other')
# specific_row_num: 当 row_type 为 'Other' 时指定的行号
# ==============================================
def extract_data_from_excel(folder_path, sheet_index, filter_col_1, filter_vals_1,
filter_col_2, filter_vals_2, extract_col,
row_type, specific_row_num=None):
# 初始化结果列表
results = []
# 获取文件夹下所有Excel文件
files = [f for f in os.listdir(folder_path) if f.endswith(('.xlsx', '.xls'))]
# 遍历每一个文件 [ref_1]
for file_name in files:
file_path = os.path.join(folder_path, file_name)
try:
# 读取指定工作表,header=None 表示不将第一行设为列名,方便按索引处理
# 使用 openpyxl 引擎以获得更好的兼容性 [ref_2]
df = pd.read_excel(file_path, sheet_name=sheet_index, header=None, engine='openpyxl')
# 将列字母转换为列索引 (A=0, B=1, ...)
col_idx_1 = ord(filter_col_1.upper()) - ord('A')
col_idx_2 = ord(filter_col_2.upper()) - ord('A')
ext_col_idx = ord(extract_col.upper()) - ord('A')
# 检查列索引是否越界
max_cols = df.shape[1]
if max(col_idx_1, col_idx_2, ext_col_idx) >= max_cols:
print(f"警告: 文件 {file_name} 的列数不足,跳过。")
continue
# --- 第一级筛选 ---
# 将筛选条件转为字符串以匹配,并处理空值
s1 = df[col_idx_1].astype(str).str.strip()
# 构建筛选条件:只要在 filter_vals_1 列表中即可
mask1 = s1.isin([str(v).strip() for v in filter_vals_1])
# --- 第二级筛选 ---
s2 = df[col_idx_2].astype(str).str.strip()
mask2 = s2.isin([str(v).strip() for v in filter_vals_2])
# 组合筛选条件 (AND 逻辑)
final_mask = mask1 & mask2
# 获取满足条件的所有行的索引
matched_rows = df[final_mask].index.tolist()
# 如果没有匹配行,跳过
if not matched_rows:
continue
# --- 提取逻辑 ---
# 注意:需求是"找到数据后,提取列的某一行的数据"
# 这意味着对于每个匹配的行,我们需要根据 row_type 决定提取哪一行的数据
# 这里假设:针对每一个匹配的行上下文,提取该特定位置的值
extracted_value = ""
if row_type == 'First':
# 提取当前匹配行中的第一行数据(即匹配行本身)
# 如果需求是提取整个表中满足条件的"第一行",则取 matched_rows[0]
target_row_idx = matched_rows[0]
extracted_value = df.iat[target_row_idx, ext_col_idx]
elif row_type == 'Last':
# 提取匹配行中的最后一行
target_row_idx = matched_rows[-1]
extracted_value = df.iat[target_row_idx, ext_col_idx]
elif row_type == 'Other':
# 提取匹配行中指定行号的数据
# 需判断指定的行号是否在匹配的行列表中
# specific_row_num 通常是用户输入的行号(如5),需转换为索引(4)
target_idx_input = specific_row_num - 1
if target_idx_input in matched_rows:
extracted_value = df.iat[target_idx_input, ext_col_idx]
else:
# 如果指定行不在匹配行中,根据需求可能跳过或取空
continue
# 获取表头信息用于输出(取第一行的对应列值)
header_1 = df.iat[0, col_idx_1]
header_2 = df.iat[0, col_idx_2]
header_ext = df.iat[0, ext_col_idx]
# 将结果存入列表
# 格式: [文件名, 筛选列1表头, 筛选列2表头, 提取值]
results.append([file_name, header_1, header_2, extracted_value])
except Exception as e:
print(f"处理文件 {file_name} 时出错: {e}")
continue
return results
# ==============================================
# 主程序入口
# ==============================================
if __name__ == "__main__":
# --- 配置参数 ---
# 模拟用户输入的参数
target_folder = r"C:\测试数据" # 请修改为实际路径
sheet_pos = 0 # 工作表位置 (第1个表)
# 筛选条件
filter_c1 = "B" # 第一级筛选列
filter_v1 = ["北京", "上海"] # 第一级筛选内容
filter_c2 = "C" # 第二级筛选列
filter_v2 = ["完成", "归档"] # 第二级筛选内容
extract_c = "D" # 需要提取数据的列
# 行提取逻辑配置
row_location = "Other" # 可选: "First", "Last", "Other"
input_row_num = 5 # 当 row_location 为 "Other" 时生效
# --- 执行提取 ---
print("开始处理数据...")
data_list = extract_data_from_excel(
target_folder, sheet_pos,
filter_c1, filter_v1,
filter_c2, filter_v2,
extract_c, row_location, input_row_num
)
# --- 结果保存 ---
if data_list:
# 创建新的工作簿 [ref_2]
wb = Workbook()
ws = wb.active
ws.title = "提取结果"
# 设置表头
headers = ["文件名", f"{filter_c1}表头", f"{filter_c2}表头", f"{extract_c}表头"]
ws.append(headers)
# 写入数据
for row_data in data_list:
ws.append(row_data)
# 保存文件
save_path = os.path.join(target_folder, "Python数据提取结果.xlsx")
wb.save(save_path)
print(f"处理完成!共提取 {len(data_list)} 条数据。文件已保存至: {save_path}")
else:
print("未找到符合条件的数据。")
```
#### 3. 代码逻辑修正说明
针对用户提出的两个具体问题,本 Python 代码进行了针对性修正:
1. **修正文件遍历问题(问题 1)**:
* 代码使用了 `os.listdir` 配合 `for file_name in files` 循环,确保对文件夹内的每一个 Excel 文件都执行读取和提取操作。
* 结果数据被累积在 `results` 列表中,最后统一写入,避免了因逻辑错误只保留最后一个文件结果的情况。
2. **修正提取逻辑问题(问题 2)**:
* **精准定位**:代码不再使用 `df[extract_col]` 提取整列,而是使用 `df.iat[row_index, col_index]` 提取单个单元格的值 [ref_3]。
* **行位置逻辑**:
* 当选择 `First` 时,代码取 `matched_rows[0]`(满足条件的第一行)作为目标行。
* 当选择 `Last` 时,代码取 `matched_rows[-1]`(满足条件的最后一行)作为目标行。
* 当选择 `Other` 时,代码检查用户输入的行号是否在满足条件的行列表中,如果存在则提取该行数据。
* 这种逻辑严格遵循了“找到数据后,提取列的某一行的数据”的要求,而非提取整列。
#### 4. 优化建议
为了进一步提升代码的健壮性和性能,建议考虑以下优化:
1. **类型容错处理**:在读取 Excel 时,不同单元格可能存在数字、文本或日期混用的情况。建议在筛选前统一使用 `.astype(str)` 进行转换,并使用 `.strip()` 去除可能存在的空格,防止因 "Value" 和 "Value " 不匹配导致筛选失败 [ref_6]。
2. **内存管理**:如果处理的单个 Excel 文件非常大(超过 100MB),建议使用 `chunksize` 参数分块读取,或者使用 `openpyxl` 在只读模式下按行读取,以减少内存消耗 [ref_2]。
3. **多进程处理**:当文件夹内文件数量极多(如上千个)时,可以使用 Python 的 `multiprocessing` 模块并行处理文件,显著缩短总运行时间。
4. **日志记录**:在批量处理中,增加详细的日志记录(如跳过了哪些文件、每个文件处理耗时),便于后续排查问题。