Excel y documentos de trabajo

Combinar PDFs y extraer un intervalo de páginas con pypdf

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.

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 necesitan combinar PDFs o extraer páginas seleccionadas sin modificar los documentos originales.

Preparación
  • Python 3.12 y un comando de terminal que inicie esa versión.
  • pypdf instalado con python -m pip install pypdf.
  • Una carpeta de trabajo donde los scripts puedan crear carpetas dentro de outputs.
  • Espacio suficiente en disco para los source PDFs, merged PDF y extracted PDF.

01Definir las reglas de combinación y extracción

El workflow utiliza tres source PDFs en un orden explícito: alpha.pdf, beta.pdf y después gamma.pdf. Sus pages se añaden a un único merged PDF. Un segundo output extrae después las pages 2 through 5 del merged PDF utilizando la numeración humana habitual, donde la primera página es page 1.

Las sequences de Python utilizan zero-based indexes, pero este script acepta START_PAGE y END_PAGE como one-based inclusive page numbers. Por tanto, pages 2 through 5 corresponden a las Python slice positions 1 through 4, implementadas como merged_reader.pages[START_PAGE - 1:END_PAGE].

02Crear tres PDFs sintéticos

Los PDFs siguientes son sintéticos y fueron creados específicamente para este artículo. Cada page está en blanco, pero todas tienen width y height diferentes. Esto permite comprobar el page order de forma independiente sin necesitar otro PDF-generation package.

python
from pathlib import Path
from pypdf import PdfWriter

SOURCE_DIR = Path("outputs") / "pdf_merge_split_demo"
PDFS = {
    "alpha.pdf": [(100, 200), (110, 210)],
    "beta.pdf": [(120, 220)],
    "gamma.pdf": [(130, 230), (140, 240), (150, 250)],
}

SOURCE_DIR.parent.mkdir(parents=True, exist_ok=True)
SOURCE_DIR.mkdir()  # Stop if the synthetic source already exists.

for filename, page_sizes in PDFS.items():
    writer = PdfWriter()
    for width, height in page_sizes:
        writer.add_blank_page(width=width, height=height)
    target = SOURCE_DIR / filename
    with target.open("xb") as stream:
        writer.write(stream)
text
python create_pdf_demo.py
Source PDFPagesDimensiones de página en orden
alpha.pdf2100×200, 110×210
beta.pdf1120×220
gamma.pdf3130×230, 140×240, 150×250

Los tres source files contienen 6 pages en total: 2 + 1 + 3. Las dimensions se expresan en PDF user-space units. Su finalidad aquí es únicamente dar a cada synthetic page una signature reconocible.

03Determinar el orden esperado de las páginas

Como los inputs se enumeran explícitamente, el merged PDF debería contener primero las dos pages de alpha, después la page de beta y, por último, las tres pages de gamma. Por tanto, el merged result debería contener 6 pages.

Merged pageSourceDimensiones esperadas
1alpha.pdf page 1100×200
2alpha.pdf page 2110×210
3beta.pdf page 1120×220
4gamma.pdf page 1130×230
5gamma.pdf page 2140×240
6gamma.pdf page 3150×250

Por tanto, extraer las merged pages 2 through 5 debería producir exactamente 4 pages con dimensions 110×210, 120×220, 130×230 y 140×240 en ese orden.

04Combinar los archivos y extraer las pages 2 through 5

Guarda el script siguiente como pdf_merge_split.py. Valida cada input file, rechaza encrypted PDFs en este ejemplo sencillo, combina todas las pages, vuelve a abrir el merged output para verificarlo y después crea el page-range file solicitado.

python
from pathlib import Path
from pypdf import PdfReader, PdfWriter

SOURCE_DIR = Path("outputs") / "pdf_merge_split_demo"
INPUTS = ("alpha.pdf", "beta.pdf", "gamma.pdf")
OUTPUT_DIR = Path("outputs") / "pdf_merge_split_result"
MERGED = OUTPUT_DIR / "merged.pdf"
EXTRACTED = OUTPUT_DIR / "pages_2_to_5.pdf"
START_PAGE = 2
END_PAGE = 5


def page_size(page) -> tuple[int, int]:
    width = int(float(page.mediabox.width))
    height = int(float(page.mediabox.height))
    return width, height


def main() -> None:
    source_root = SOURCE_DIR.resolve(strict=True)
    if not source_root.is_dir():
        raise ValueError("SOURCE_DIR must be a directory.")
    if START_PAGE < 1 or END_PAGE < START_PAGE:
        raise ValueError("Invalid page range.")

    input_paths = [source_root / name for name in INPUTS]
    for path in input_paths:
        if not path.is_file():
            raise FileNotFoundError(f"Missing input PDF: {path}")

    OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
    OUTPUT_DIR.mkdir()  # Refuse to reuse an existing result folder.

    expected_sizes = []
    writer = PdfWriter()
    for path in input_paths:
        reader = PdfReader(path)
        if reader.is_encrypted:
            raise ValueError(f"Encrypted PDF is not supported here: {path.name}")
        for page in reader.pages:
            expected_sizes.append(page_size(page))
            writer.add_page(page)

    with MERGED.open("xb") as stream:
        writer.write(stream)

    merged_reader = PdfReader(MERGED)
    if merged_reader.is_encrypted:
        raise RuntimeError("Unexpected encrypted merged PDF.")
    merged_sizes = [page_size(page) for page in merged_reader.pages]
    if merged_sizes != expected_sizes:
        raise RuntimeError("Merged page count or order does not match the inputs.")

    if END_PAGE > len(merged_reader.pages):
        raise ValueError(
            f"Requested page {END_PAGE}, but merged PDF has only "
            f"{len(merged_reader.pages)} pages."
        )

    selected_pages = merged_reader.pages[START_PAGE - 1:END_PAGE]
    extracted_writer = PdfWriter()
    expected_extract_sizes = []
    for page in selected_pages:
        expected_extract_sizes.append(page_size(page))
        extracted_writer.add_page(page)

    with EXTRACTED.open("xb") as stream:
        extracted_writer.write(stream)

    extracted_reader = PdfReader(EXTRACTED)
    extracted_sizes = [page_size(page) for page in extracted_reader.pages]
    if extracted_sizes != expected_extract_sizes:
        raise RuntimeError("Extracted PDF does not match the selected pages.")

    print(f"Merged {len(INPUTS)} PDFs into {len(merged_reader.pages)} pages.")
    print(
        f"Extracted pages {START_PAGE}-{END_PAGE}: "
        f"{len(extracted_reader.pages)} pages."
    )
    print(f"Merged output: {MERGED.as_posix()}")
    print(f"Extracted output: {EXTRACTED.as_posix()}")


if __name__ == "__main__":
    main()
text
python pdf_merge_split.py

05Comparar los outputs esperados

El primer output, merged.pdf, debería contener 6 pages. El segundo output, pages_2_to_5.pdf, debería contener 4 pages. La extracción incluye ambos extremos porque el script convierte los human page numbers en el Python slice correspondiente.

OutputPages esperadasDimensiones esperadas en orden
merged.pdf6100×200, 110×210, 120×220, 130×230, 140×240, 150×250
pages_2_to_5.pdf4110×210, 120×220, 130×230, 140×240

El console text esperado que aparece a continuación se obtuvo manualmente a partir de los synthetic inputs y el código. No es un execution log.

text
Merged 3 PDFs into 6 pages.
Extracted pages 2-5: 4 pages.
Merged output: outputs/pdf_merge_split_result/merged.pdf
Extracted output: outputs/pdf_merge_split_result/pages_2_to_5.pdf

06Comprobar el resultado antes de utilizar PDFs reales

  1. Abre merged.pdf y confirma que tenga 6 pages.
  2. Abre pages_2_to_5.pdf y confirma que tenga exactamente 4 pages.
  3. Inspecciona page dimensions u otra visible page feature para confirmar que el orden en el archivo extraído sea 110×210, 120×220, 130×230, 140×240.
  4. Confirma que alpha.pdf, beta.pdf y gamma.pdf sigan existiendo sin cambios en la source folder.
  5. Ejecuta de nuevo el script sin cambiar OUTPUT_DIR. Debería detenerse con FileExistsError en lugar de sustituir los PDFs anteriores.

En documentos reales, inspecciona también visualmente el page content. Que los counts coincidan por sí solo no permite detectar todos los posibles problemas, como un input order inesperado o un source PDF cuyos page labels difieran de sus physical page positions.

07Reconocer errores comunes

SíntomaQué comprobar
ModuleNotFoundError: No module named pypdfInstala pypdf en el mismo Python environment con python -m pip install pypdf.
FileNotFoundErrorComprueba SOURCE_DIR, INPUTS y el working directory de la terminal. Para este ejemplo, ejecuta primero el synthetic setup script.
FileExistsErrorLa result folder ya existe. Revisa los outputs anteriores y elige una nueva folder en lugar de sobrescribirlos.
Encrypted PDF is not supported hereUtiliza una decrypted copy permitida o añade un password-handling workflow explícito y adecuado para tus documents.
Requested page is beyond the merged PDFComprueba el merged page count y los valores one-based START_PAGE y END_PAGE.
Merged or extracted verification mismatchConsidera el generated file como unverified. Comprueba los inputs y utiliza una output folder nueva en la siguiente ejecución.

Si ocurre una exception después de crear OUTPUT_DIR, pueden quedar partial files. El script no los elimina automáticamente. Mantén los failed outputs separados de los verified files.

08Comprender los límites

Este workflow verifica physical page count y page dimensions, no semantic content. No compara rendered pixels, extracted text, annotations, forms, bookmarks, named destinations, attachments, signatures ni accessibility structure.

Los PDF page labels pueden diferir de las physical page positions. Un document puede mostrar roman numerals o custom labels aunque pypdf siga accediendo a sus page objects mediante zero-based sequence position. Los valores START_PAGE y END_PAGE de este tutorial se refieren a physical sequence positions, no a printed page numbers.

Algunos PDFs complejos contienen interactive forms, digital signatures, encrypted content, unusual object relationships u otras features que requieren un tratamiento más cuidadoso. Combinar un digitally signed PDF en un nuevo document no conserva el significado de la signature original como una firma sobre el nuevo file creado. Para records importantes, revisa los PDF requirements relevantes antes de considerar una merged copy equivalente al original.

Registro de ejecución y verificación

2026-09-20 · ejemplo revisado manualmente · objetivo: Python 3.12 · se requiere pypdf · sin ejecución

  • Se contaron manualmente los source page totals de 2, 1 y 3, para un total de 6 merged pages.
  • Se siguieron manualmente las merged dimensions esperadas como 100x200, 110x210, 120x220, 130x230, 140x240 y 150x250.
  • Se convirtió manualmente el human page range inclusivo 2-5 en las Python positions 1 through 4 y se confirmó que contiene 4 pages.
  • Se obtuvieron manualmente las extracted dimensions como 110x210, 120x220, 130x230 y 140x240.
  • Se revisó el código para confirmar que los source PDFs se abren para lectura, los outputs se escriben en una carpeta separada y una output folder existente hace que la ejecución se detenga.
  • Se revisó la lógica de comparación de page-count y page-dimension y se obtuvo manualmente la salida de consola esperada.
Límites de la verificación
  • El código no fue ejecutado por el autor de esta respuesta; aquí no se crearon, combinaron, extrajeron ni abrieron PDF files.
  • No se probaron encrypted PDFs, forms, annotations, bookmarks, digital signatures, attachments, page labels ni malformed PDFs.
  • Las page-dimension checks verifican el orden en el ejemplo sintético, pero no demuestran semantic equivalence para PDFs reales arbitrarios.
  • Las URL de la documentación oficial se proporcionaron a partir de ubicaciones de documentación conocidas, pero no se comprobaron en línea.

Criterios de redacción y verificación de todo el sitio

Fuentes de referencia

Las explicaciones y los ejemplos son de elaboración propia. Puedes consultar los comportamientos y conceptos relacionados en las siguientes fuentes oficiales.