Combinar hojas de Excel en una sola tabla con una columna de hoja de origen
Combina hojas de cálculo que utilizan las mismas columnas en una única tabla de Excel, registrando de qué hoja procede cada fila. Un pequeño libro sintético permite verificar manualmente el orden de las filas, los recuentos y los totales.
Contenido revisado 2026.09.20Incluye archivos de ejemplo
Ver el índice
La traducción se ha realizado con IA. Comprueba el código, las unidades y los valores junto con el original. La revisión por hablantes nativos de cada idioma aún no se ha completado. English
Para quién esEsta guía está dirigida a personas que reciben varias hojas de Excel con una estructura similar y necesitan un único conjunto de datos consolidado sin modificar el libro original.
Preparación
Python 3.12 y un comando de terminal que inicie esa versión.
openpyxl instalado con python -m pip install openpyxl.
Una carpeta de trabajo donde los scripts puedan crear carpetas dentro de outputs.
Las hojas que se van a combinar deben utilizar los mismos nombres de columna en el mismo orden.
01Definir exactamente qué se combinará
El flujo de trabajo combina hojas seleccionadas de un libro en un libro nuevo. Cada hoja de origen debe tener exactamente la misma fila de encabezados, incluido el orden de las columnas y el uso de mayúsculas y minúsculas. La salida añade source_sheet como primera columna para que cada registro conserve su procedencia.
Las filas en blanco se ignoran. Las filas no vacías se copian siguiendo el orden de las hojas y, dentro de cada hoja, su orden original. El libro de origen se abre en modo de solo lectura y el resultado se escribe en una carpeta independiente dentro de outputs.
02Crear un libro sintético
El libro que aparece a continuación es sintético y fue creado específicamente para este artículo. Guarda el script de preparación como create_excel_merge_demo.py. Crea tres hojas llamadas East, West y Online. Cada una utiliza las columnas order_id, customer y amount.
python
from pathlib import Path
from openpyxl import Workbook
SOURCE_DIR = Path("outputs") / "excel_merge_demo"
SOURCE = SOURCE_DIR / "regional_orders.xlsx"
HEADERS = ["order_id", "customer", "amount"]
DATA = {
"East": [
["E001", "Ana", 120],
["E002", "Ben", 80],
],
"West": [
["W001", "Cara", 150],
["W002", "Dan", 70],
],
"Online": [
["O001", "Emi", 90],
[None, None, None],
["O002", "Finn", 110],
],
}
SOURCE_DIR.parent.mkdir(parents=True, exist_ok=True)
SOURCE_DIR.mkdir() # Stop if the synthetic source folder already exists.
workbook = Workbook()
for index, (sheet_name, rows) in enumerate(DATA.items()):
if index == 0:
sheet = workbook.active
sheet.title = sheet_name
else:
sheet = workbook.create_sheet(sheet_name)
sheet.append(HEADERS)
for row in rows:
sheet.append(row)
workbook.save(SOURCE)
text
python create_excel_merge_demo.py
Hoja
Filas de datos no vacías
Total de amount
East
2
200
West
2
220
Online
2
200
Hay 6 registros de datos no vacíos en total. Sus valores de amount suman 620: 120 + 80 + 150 + 70 + 90 + 110. Online contiene deliberadamente una fila completamente vacía entre sus dos registros para demostrar que la lógica de combinación omite las filas en blanco.
03Determinar manualmente el orden esperado de las filas combinadas
El script enumera explícitamente las hojas como East, West y Online. Ese orden determina el orden de los registros combinados. Dentro de cada hoja, los registros mantienen su orden original de arriba abajo.
source_sheet
order_id
customer
amount
East
E001
Ana
120
East
E002
Ben
80
West
W001
Cara
150
West
W002
Dan
70
Online
O001
Emi
90
Online
O002
Finn
110
Por tanto, la salida tiene 4 columnas y 7 filas de hoja al incluir el encabezado: 1 fila de encabezado más 6 filas de datos. Los valores de source_sheet también sirven para una comprobación manual útil: East aparece dos veces, West aparece dos veces y Online aparece dos veces.
04Combinar las hojas en un libro nuevo
Guarda el siguiente script como excel_merge_sheets.py. Abre el libro de origen en modo de solo lectura, valida los nombres y encabezados de las hojas seleccionadas, ignora las filas completamente vacías y rechaza las celdas con fórmulas para evitar que las expresiones se copien silenciosamente fuera del contexto de su libro original.
python
from pathlib import Path
from openpyxl import Workbook, load_workbook
SOURCE = Path("outputs") / "excel_merge_demo" / "regional_orders.xlsx"
OUTPUT_DIR = Path("outputs") / "excel_merge_result"
OUTPUT = OUTPUT_DIR / "merged_orders.xlsx"
SHEETS = ("East", "West", "Online")
OUTPUT_SHEET = "Combined"
SOURCE_COLUMN = "source_sheet"
def read_sheet_rows(sheet, expected_header=None):
first_row = next(
sheet.iter_rows(min_row=1, max_row=1, values_only=True),
None,
)
if first_row is None:
raise ValueError(f"Sheet is empty: {sheet.title}")
header = tuple(first_row)
if any(not isinstance(name, str) or not name.strip() for name in header):
raise ValueError(f"Invalid header in sheet: {sheet.title}")
if len(set(header)) != len(header):
raise ValueError(f"Duplicate header name in sheet: {sheet.title}")
if SOURCE_COLUMN in header:
raise ValueError(f"Reserved column already exists: {SOURCE_COLUMN}")
if expected_header is not None and header != expected_header:
raise ValueError(f"Header mismatch in sheet: {sheet.title}")
rows = []
for excel_row, values in enumerate(
sheet.iter_rows(min_row=2, max_col=len(header), values_only=True),
start=2,
):
if all(value is None for value in values):
continue
if any(isinstance(value, str) and value.startswith("=") for value in values):
raise ValueError(
f"Formula found in {sheet.title} row {excel_row}; "
"this example merges literal values only."
)
rows.append(tuple(values))
return header, rows
def main() -> None:
source_path = SOURCE.resolve(strict=True)
if not source_path.is_file():
raise ValueError("SOURCE must be an Excel file.")
if len(set(SHEETS)) != len(SHEETS):
raise ValueError("SHEETS contains a duplicate sheet name.")
source_workbook = load_workbook(
source_path,
read_only=True,
data_only=False,
)
try:
missing = [name for name in SHEETS if name not in source_workbook.sheetnames]
if missing:
raise ValueError(f"Missing sheets: {missing}")
expected_header = None
combined_rows = []
for sheet_name in SHEETS:
sheet = source_workbook[sheet_name]
header, rows = read_sheet_rows(sheet, expected_header)
if expected_header is None:
expected_header = header
for row in rows:
combined_rows.append((sheet_name, *row))
finally:
source_workbook.close()
OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
OUTPUT_DIR.mkdir() # Refuse to reuse an existing output folder.
output_workbook = Workbook()
output_sheet = output_workbook.active
output_sheet.title = OUTPUT_SHEET
output_sheet.append([SOURCE_COLUMN, *expected_header])
for row in combined_rows:
output_sheet.append(row)
output_workbook.save(OUTPUT)
check_workbook = load_workbook(OUTPUT, read_only=True, data_only=False)
try:
if check_workbook.sheetnames != [OUTPUT_SHEET]:
raise RuntimeError("Unexpected output worksheet structure.")
check_sheet = check_workbook[OUTPUT_SHEET]
written = list(check_sheet.iter_rows(values_only=True))
finally:
check_workbook.close()
expected = [(SOURCE_COLUMN, *expected_header), *combined_rows]
if written != expected:
raise RuntimeError("Saved workbook does not match the expected rows.")
print(f"Merged {len(SHEETS)} sheets into {len(combined_rows)} data rows.")
print(f"Verified {len(expected_header) + 1} columns and {len(combined_rows)} data rows.")
print(f"Output: {OUTPUT.as_posix()}")
if __name__ == "__main__":
main()
text
python excel_merge_sheets.py
05Comparar el resultado esperado
Para el libro sintético, la salida debería contener exactamente una hoja llamada Combined. Su primera fila debe ser source_sheet, order_id, customer, amount, seguida de los 6 registros calculados anteriormente.
La salida de consola esperada que aparece a continuación se obtuvo manualmente a partir del código y los datos sintéticos. No es un registro capturado de una ejecución.
text
Merged 3 sheets into 6 data rows.
Verified 4 columns and 6 data rows.
Output: outputs/excel_merge_result/merged_orders.xlsx
Comprobación
Resultado esperado
Hojas de origen seleccionadas
3
Filas de datos combinadas
6
Columnas de salida
4
Filas de East
2
Filas de West
2
Filas de Online
2
Total de amount
620
06Comprobar el libro antes de utilizar datos reales
Abre merged_orders.xlsx y confirma que haya una sola hoja llamada Combined.
Confirma que source_sheet sea la primera columna y que las tres columnas originales aparezcan después en el mismo orden.
Cuenta los registros por source_sheet. East, West y Online deberían aparecer exactamente dos veces cada uno.
Suma manualmente la columna amount o hazlo con Excel. El total esperado es 620.
Confirma que la fila en blanco de Online no se haya convertido en un registro vacío en la salida combinada.
Ejecuta el script otra vez sin cambiar OUTPUT_DIR. Debería detenerse con FileExistsError en lugar de sustituir el resultado anterior.
En libros reales, modifica SHEETS explícitamente en lugar de combinar automáticamente todas las hojas. Esto reduce la posibilidad de incluir accidentalmente tablas de consulta, notas, hojas ocultas o hojas de resumen que estén almacenadas en el mismo archivo.
07Reconocer errores comunes
Síntoma
Qué comprobar
ModuleNotFoundError: No module named openpyxl
Instala openpyxl en el mismo entorno de Python con python -m pip install openpyxl.
FileNotFoundError
Comprueba SOURCE y el directorio de trabajo de la terminal. Para este ejemplo, ejecuta primero la preparación del libro sintético.
Missing sheets
Comprueba exactamente los nombres indicados en SHEETS, incluidos los espacios y el uso de mayúsculas y minúsculas.
Header mismatch
Verifica que todas las hojas seleccionadas utilicen nombres de columna idénticos en el mismo orden.
Reserved column already exists
Cambia el nombre de la columna de origen source_sheet o modifica SOURCE_COLUMN por un nombre que no entre en conflicto.
Formula found
Decide cómo deben tratarse las fórmulas antes de combinar. Este tutorial se detiene deliberadamente en lugar de copiar expresiones de fórmulas sin contexto.
FileExistsError
La carpeta de salida ya existe. Revisa el resultado anterior y utiliza un destino nuevo en lugar de sobrescribirlo.
Si el guardado o la verificación fallan después de crear OUTPUT_DIR, la carpeta o el libro pueden quedar como un resultado parcial. Considéralos no verificados y utiliza un destino nuevo después de corregir el problema.
08Comprender los límites
El script está pensado para hojas de datos rectangulares con una única fila de encabezados. No maneja encabezados de varias filas, celdas de encabezado combinadas, tablas dinámicas, gráficos, imágenes ni hojas donde varias tablas no relacionadas compartan la misma hoja.
Solo se consolidan los valores de las celdas. No se reproducen formatos numéricos, fuentes, rellenos, formato condicional, reglas de validación, hipervínculos, comentarios, fórmulas, alturas de fila, anchos de columna ni configuraciones de nivel de hoja. Los valores literales de fecha y numéricos pueden conservarse como valores, pero su formato de visualización original no se copia.
El script también carga todas las tuplas de filas combinadas en la memoria de Python antes de crear el libro de salida. Esto resulta cómodo para este pequeño flujo de trabajo verificado, pero puede no ser adecuado para libros muy grandes. En archivos grandes, un diseño de streaming con salida write-only y validación incremental independiente reduciría el uso de memoria.
Registro de ejecución y verificación
2026-09-20 · ejemplo revisado manualmente · objetivo: Python 3.12 · se requiere openpyxl · sin ejecución
Se contaron manualmente 2 registros no vacíos en East, 2 en West y 2 en Online, para un total de 6 registros combinados.
Se calcularon manualmente los totales de amount de cada hoja como 200, 220 y 200, dando un total general de 620.
Se obtuvo manualmente el orden esperado de salida: primero las filas de East, después West y luego Online, omitiendo la fila completamente vacía de Online.
Se revisó el código para confirmar que el libro de origen se abre en modo de solo lectura y el resultado se escribe en una carpeta outputs independiente.
Se revisaron las comprobaciones de encabezados, la comprobación reservada de source_sheet, el tratamiento de filas en blanco, el rechazo de fórmulas y la comparación de filas después de guardar.
Se obtuvieron manualmente las 4 columnas de salida, las 6 filas de datos y los mensajes esperados de consola.
Límites de la verificación
El código no fue ejecutado por el autor de esta respuesta; aquí no se crearon ni abrieron archivos XLSX.
No se probaron libros con fórmulas, celdas combinadas, formato, fechas, hipervínculos, hojas ocultas, archivos dañados ni libros muy grandes.
El comportamiento de openpyxl con el libro sintético se dedujo a partir del código, pero no se confirmó en un entorno de ejecución de Python.
Las URL de la documentación oficial se proporcionaron a partir de ubicaciones de documentación conocidas, pero no se comprobaron en línea.
Las explicaciones y los ejemplos son de elaboración propia. Puedes consultar los comportamientos y conceptos relacionados en las siguientes fuentes oficiales.
Encuentra filas con coincidencia exacta en las cuatro columnas y recopílalas en un nuevo archivo de Excel junto con sus números de fila originales. Revisa cada grupo de duplicados, incluida su primera aparición.
Valida las fechas y las horas de trabajo de un CSV y después guarda los recuentos mensuales de registros y las horas totales como un CSV y un gráfico de barras.
Antes de convertir una tabla en texto, alinea el objeto, el periodo, el denominador y la unidad. Usa una tabla sintética de resultados mensuales para comprobar un valor que cambió de 80 % a 82 % y una conversión de unidades, y después crea una lista de comprobación reutilizable.
Combina varios PDFs en un solo archivo, extrae un intervalo inclusivo de páginas en un segundo archivo y verifica el número y el orden de las páginas resultantes. Un conjunto sintético de páginas en blanco con dimensiones deliberadamente diferentes permite comprobar el resultado manualmente.