Merge several PDFs into one file, extract an inclusive page range into a second file, and verify the resulting page counts and order. A synthetic set of blank pages with deliberately different dimensions makes the result checkable by hand.
Content checked 2026.09.20Hand-checked example
Show contents
Who this is forThis guide is for people who need to combine PDFs or extract selected pages without modifying the original documents.
What you need
Python 3.12 and a terminal command that starts that version.
pypdf installed with python -m pip install pypdf.
A working folder where the scripts can create folders beneath outputs.
Enough disk space for the source PDFs, merged PDF, and extracted PDF.
01Define the merge and extraction rules
The workflow uses three source PDFs in an explicit order: alpha.pdf, beta.pdf, then gamma.pdf. Their pages are appended to one merged PDF. A second output then extracts pages 2 through 5 from the merged PDF, using ordinary human page numbering where the first page is page 1.
Python sequences use zero-based indexes, but this script accepts START_PAGE and END_PAGE as one-based inclusive page numbers. Pages 2 through 5 therefore correspond to Python slice positions 1 through 4, implemented as merged_reader.pages[START_PAGE - 1:END_PAGE].
02Create three synthetic PDFs
The PDFs below are synthetic and were created specifically for this article. Each page is blank, but every page has a different width and height. That makes page order independently checkable without requiring another 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 PDF
Pages
Page dimensions in order
alpha.pdf
2
100×200, 110×210
beta.pdf
1
120×220
gamma.pdf
3
130×230, 140×240, 150×250
The three source files contain 6 pages in total: 2 + 1 + 3. The dimensions are expressed in PDF user-space units. Their purpose here is simply to give every synthetic page a recognizable signature.
03Work out the expected page order
Because the inputs are explicitly listed, the merged PDF should contain the two alpha pages first, followed by the beta page, then the three gamma pages. The merged result should therefore contain 6 pages.
Merged page
Source
Expected dimensions
1
alpha.pdf page 1
100×200
2
alpha.pdf page 2
110×210
3
beta.pdf page 1
120×220
4
gamma.pdf page 1
130×230
5
gamma.pdf page 2
140×240
6
gamma.pdf page 3
150×250
Extracting merged pages 2 through 5 should therefore produce exactly 4 pages with dimensions 110×210, 120×220, 130×230, and 140×240 in that order.
04Merge the files and extract pages 2 through 5
Save the following script as pdf_merge_split.py. It validates every input file, rejects encrypted PDFs for this simple example, merges all pages, reopens the merged output to verify it, then creates the requested page-range file.
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
05Compare the expected outputs
The first output, merged.pdf, should contain 6 pages. The second output, pages_2_to_5.pdf, should contain 4 pages. The extraction is inclusive at both ends because the script converts the human page numbers into the corresponding Python slice.
Open pages_2_to_5.pdf and confirm that it has exactly 4 pages.
Inspect page dimensions or another visible page feature to confirm that the order is 110×210, 120×220, 130×230, 140×240 in the extracted file.
Confirm that alpha.pdf, beta.pdf, and gamma.pdf still exist unchanged in the source folder.
Run the script again without changing OUTPUT_DIR. It should stop with FileExistsError instead of replacing the previous PDFs.
For real documents, also inspect page content visually. Matching counts alone cannot reveal every possible issue, such as an unexpected input order or a source PDF whose page labels differ from its physical page positions.
07Recognize common errors
Symptom
What to check
ModuleNotFoundError: No module named pypdf
Install pypdf into the same Python environment with python -m pip install pypdf.
FileNotFoundError
Check SOURCE_DIR, INPUTS, and the terminal's working directory. Run the synthetic setup script first for this example.
FileExistsError
The result folder already exists. Review the previous outputs and choose a new folder rather than overwriting them.
Encrypted PDF is not supported here
Use a permitted decrypted copy or add an explicit password-handling workflow appropriate to your documents.
Requested page is beyond the merged PDF
Check the merged page count and the one-based START_PAGE and END_PAGE values.
Merged or extracted verification mismatch
Treat the generated file as unverified. Check the inputs and use a fresh output folder for the next run.
If an exception happens after OUTPUT_DIR is created, partial files can remain. The script does not automatically delete them. Keep failed outputs separate from verified files.
08Understand the limits
This workflow verifies physical page count and page dimensions, not semantic content. It does not compare rendered pixels, extracted text, annotations, forms, bookmarks, named destinations, attachments, signatures, or accessibility structure.
PDF page labels can differ from physical page positions. A document might display roman numerals or custom labels even though pypdf still accesses its page objects by zero-based sequence position. The START_PAGE and END_PAGE values in this tutorial refer to physical sequence positions, not printed page numbers.
Some complex PDFs contain interactive forms, digital signatures, encrypted content, unusual object relationships, or features that require more deliberate handling. Merging a digitally signed PDF into a new document does not preserve the meaning of the original signature as a signature over the newly created file. For important records, inspect the relevant PDF requirements before treating a merged copy as equivalent to the original.
Execution and verification record
2026-09-20 · hand-checked example · target: Python 3.12 · pypdf required · no execution
Manually counted source page totals of 2, 1, and 3 for 6 merged pages.
Manually traced the expected merged dimensions as 100x200, 110x210, 120x220, 130x230, 140x240, and 150x250.
Manually converted the inclusive human page range 2-5 into Python positions 1 through 4 and confirmed that it contains 4 pages.
Manually derived the extracted dimensions as 110x210, 120x220, 130x230, and 140x240.
Inspected the code to confirm that source PDFs are opened for reading, outputs go to a separate folder, and an existing output folder causes the run to stop.
Inspected the page-count and page-dimension comparison logic and derived the expected console output by hand.
Verification limits
The code was not executed by the author of this response; no PDF files were created, merged, extracted, or opened here.
Encrypted PDFs, forms, annotations, bookmarks, digital signatures, attachments, page labels, and malformed PDFs were not tested.
The page-dimension checks verify order for the synthetic example but do not prove semantic equivalence for arbitrary real PDFs.
The official documentation URLs were provided from known documentation locations but were not checked live.
Merge worksheets that use the same columns into one Excel table while recording which sheet each row came from. A small synthetic workbook lets you verify the row order, counts, and totals by hand.
Convert several known date formats into ISO-style YYYY-MM-DD values while keeping the original CSV unchanged. Values that do not match an approved format or are not valid calendar dates are written to a separate review file.
Summarize order-level CSV data by month with pandas, then reconcile the grouped counts, units, and amounts against the source. A small synthetic example makes every expected result easy to verify by hand.
Find rows with exact matches in all four columns and collect them in a new Excel file with their original row numbers. Review each duplicate group, including its first occurrence.