270

Is it possible, using Python, to merge separate PDF files?

Assuming so, I need to extend this a little further. I am hoping to loop through folders in a directory and repeat this procedure.

And I may be pushing my luck, but is it possible to exclude a page that is contained in each of the PDFs (my report generation always creates an extra blank page).

Woody1193
  • 7,252
  • 5
  • 40
  • 90
Btibert3
  • 38,798
  • 44
  • 129
  • 168

15 Answers15

448

You can use pypdfs PdfMerger class.

File Concatenation

You can simply concatenate files by using the append method.

from pypdf import PdfMerger

pdfs = ['file1.pdf', 'file2.pdf', 'file3.pdf', 'file4.pdf']

merger = PdfMerger()

for pdf in pdfs:
    merger.append(pdf)

merger.write("result.pdf")
merger.close()

You can pass file handles instead file paths if you want.

File Merging

If you want more fine grained control of merging there is a merge method of the PdfMerger, which allows you to specify an insertion point in the output file, meaning you can insert the pages anywhere in the file. The append method can be thought of as a merge where the insertion point is the end of the file.

e.g.

merger.merge(2, pdf)

Here we insert the whole PDF into the output but at page 2.

Page Ranges

If you wish to control which pages are appended from a particular file, you can use the pages keyword argument of append and merge, passing a tuple in the form (start, stop[, step]) (like the regular range function).

e.g.

merger.append(pdf, pages=(0, 3))    # first 3 pages
merger.append(pdf, pages=(0, 6, 2)) # pages 1,3, 5

If you specify an invalid range you will get an IndexError.

Note: also that to avoid files being left open, the PdfMergers close method should be called when the merged file has been written. This ensures all files are closed (input and output) in a timely manner. It's a shame that PdfMerger isn't implemented as a context manager, so we can use the with keyword, avoid the explicit close call and get some easy exception safety.

You might also want to look at the pdfly cat command provided by the pypdf developers. You can potentially avoid the need to write code altogether.

The pypdf documentation also includes some example code demonstrating merging.

PyMuPdf

Another library perhaps worth a look is PyMuPdf. Merging is equally simple.

From command line:

python -m fitz join -o result.pdf file1.pdf file2.pdf file3.pdf

and from code

import fitz

result = fitz.open()

for pdf in ['file1.pdf', 'file2.pdf', 'file3.pdf']:
    with fitz.open(pdf) as mfile:
        result.insert_pdf(mfile)
    
result.save("result.pdf")

With plenty of options, detailed in the projects wiki.

note: in older versions of PyMuPDF insert_pdf was insertPDF

Martin Thoma
  • 124,992
  • 159
  • 614
  • 958
Paul Rooney
  • 20,879
  • 9
  • 40
  • 61
  • 5
    PyMuPDF was so much faster than PyPDF2 for what I had to do (Concatenate 280 single page pdfs). Thanks! – Skusku Jan 07 '22 at 11:34
  • 2
    PyPDF2 is now also maintained again :-) I've just linked to your answer: https://pypdf2.readthedocs.io/en/latest/user/merging-pdfs.html – Martin Thoma Apr 09 '22 at 13:58
  • 1
    pyPDF2 is working well, but internal links are not working i.e., not moving to specified section on click. Any idea? I tested even with single file, still same issue – Rami May 11 '22 at 17:20
  • I’ve never tried this. This is just a basic merging process, it may be required to go in and fix up the internal links afterwards. I’m happy to tackle it though if you can provide more details. You might even consider asking a new question? – Paul Rooney May 11 '22 at 22:47
  • I had over 2000 pdfs to merge into one, had to use PyMuPdf as PyPDF2 couldn't have that many files open at once. – Discant Jun 02 '22 at 03:05
  • 1
    @Skusku Same for me, I had to combine pages from different PDF documents into a "one-pager". With PyPDF2 (using `merge_page()` and transformations), this took 12 minutes (!), with PyMuPDF only 2 seconds using the approach [here](https://pymupdf.readthedocs.io/en/latest/faq.html#how-to-combine-single-pages). – Splines Jun 16 '22 at 13:22
  • 1
    for PyMuPDF result.insertPDF() is now result.insert_pdf() – B. Bogart Feb 06 '23 at 14:43
  • pip install PyPDF2 – Dinesh Ravi Jul 13 '23 at 22:49
  • 1
    Very very appreciated +1 – Sercan Tırnavalı Jul 15 '23 at 13:00
155

Use Pypdf or its successor PyPDF2:

A Pure-Python library built as a PDF toolkit. It is capable of:

  • splitting documents page by page,
  • merging documents page by page,

(and much more)

Here's a sample program that works with both versions.

#!/usr/bin/env python
import sys
try:
    from PyPDF2 import PdfFileReader, PdfFileWriter
except ImportError:
    from pyPdf import PdfFileReader, PdfFileWriter

def pdf_cat(input_files, output_stream):
    input_streams = []
    try:
        # First open all the files, then produce the output file, and
        # finally close the input files. This is necessary because
        # the data isn't read from the input files until the write
        # operation. Thanks to
        # https://stackoverflow.com/questions/6773631/problem-with-closing-python-pypdf-writing-getting-a-valueerror-i-o-operation/6773733#6773733
        for input_file in input_files:
            input_streams.append(open(input_file, 'rb'))
        writer = PdfFileWriter()
        for reader in map(PdfFileReader, input_streams):
            for n in range(reader.getNumPages()):
                writer.addPage(reader.getPage(n))
        writer.write(output_stream)
    finally:
        for f in input_streams:
            f.close()
        output_stream.close()

if __name__ == '__main__':
    if sys.platform == "win32":
        import os, msvcrt
        msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
    pdf_cat(sys.argv[1:], sys.stdout)
Yul Kang
  • 449
  • 4
  • 9
Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
  • 22
    And now, https://pypi.python.org/pypi/PyPDF2 which is the successor project to PyPDF – David Fraser Aug 22 '13 at 10:04
  • 1
    Works for me only with opening in binary mode (input streams and also output stream). `open(input_file), 'r+b'`, and instead of sys.stdout I use `output_stream = open('result.pdf', 'w+b')`. – Simeon Borko Mar 23 '18 at 12:01
  • @SimeonBorko Drop the `+`, it means “read and write” and neither file is both read and written. I've added Windows support output support based on https://stackoverflow.com/questions/2374427/python-2-x-write-binary-output-to-stdout. – Gilles 'SO- stop being evil' Mar 23 '18 at 18:20
  • PyPDF2/3 is not stable, how can I merge pdf files without PyPDF2/3 . – GoingMyWay Jun 19 '19 at 03:03
  • 2
    I had to use `sys.stdout.buffer` using Python 3.6.8 (Linux) – Greyshack Aug 21 '19 at 13:33
  • you may have a look at https://github.com/cadu-leite/merge2pdf. its simple and accept PFDs and images – Carlos Leite Feb 08 '21 at 11:10
43

Merge all pdf files that are present in a dir

Put the pdf files in a dir. Launch the program. You get one pdf with all the pdfs merged.

import os
from PyPDF2 import PdfMerger

x = [a for a in os.listdir() if a.endswith(".pdf")]

merger = PdfMerger()

for pdf in x:
    merger.append(open(pdf, 'rb'))

with open("result.pdf", "wb") as fout:
    merger.write(fout)

How would I make the same code above today

from glob import glob
from PyPDF2 import PdfMerger



def pdf_merge():
    ''' Merges all the pdf files in current directory '''
    merger = PdfMerger()
    allpdfs = [a for a in glob("*.pdf")]
    [merger.append(pdf) for pdf in allpdfs]
    with open("Merged_pdfs.pdf", "wb") as new_file:
        merger.write(new_file)


if __name__ == "__main__":
    pdf_merge()
PythonProgrammi
  • 22,305
  • 3
  • 41
  • 34
16

The pdfrw library can do this quite easily, assuming you don't need to preserve bookmarks and annotations, and your PDFs aren't encrypted. cat.py is an example concatenation script, and subset.py is an example page subsetting script.

The relevant part of the concatenation script -- assumes inputs is a list of input filenames, and outfn is an output file name:

from pdfrw import PdfReader, PdfWriter

writer = PdfWriter()
for inpfn in inputs:
    writer.addpages(PdfReader(inpfn).pages)
writer.write(outfn)

As you can see from this, it would be pretty easy to leave out the last page, e.g. something like:

    writer.addpages(PdfReader(inpfn).pages[:-1])

Disclaimer: I am the primary pdfrw author.

0 _
  • 10,524
  • 11
  • 77
  • 109
Patrick Maupin
  • 8,024
  • 2
  • 23
  • 42
9

Is it possible, using Python, to merge seperate PDF files?

Yes.

The following example merges all files in one folder to a single new PDF file:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from argparse import ArgumentParser
from glob import glob
from pyPdf import PdfFileReader, PdfFileWriter
import os

def merge(path, output_filename):
    output = PdfFileWriter()

    for pdffile in glob(path + os.sep + '*.pdf'):
        if pdffile == output_filename:
            continue
        print("Parse '%s'" % pdffile)
        document = PdfFileReader(open(pdffile, 'rb'))
        for i in range(document.getNumPages()):
            output.addPage(document.getPage(i))

    print("Start writing '%s'" % output_filename)
    with open(output_filename, "wb") as f:
        output.write(f)

if __name__ == "__main__":
    parser = ArgumentParser()

    # Add more options if you like
    parser.add_argument("-o", "--output",
                        dest="output_filename",
                        default="merged.pdf",
                        help="write merged PDF to FILE",
                        metavar="FILE")
    parser.add_argument("-p", "--path",
                        dest="path",
                        default=".",
                        help="path of source PDF files")

    args = parser.parse_args()
    merge(args.path, args.output_filename)
Martin Thoma
  • 124,992
  • 159
  • 614
  • 958
3

here, http://pieceofpy.com/2009/03/05/concatenating-pdf-with-python/, gives an solution.

similarly:

from pyPdf import PdfFileWriter, PdfFileReader

def append_pdf(input,output):
    [output.addPage(input.getPage(page_num)) for page_num in range(input.numPages)]

output = PdfFileWriter()

append_pdf(PdfFileReader(file("C:\\sample.pdf","rb")),output)
append_pdf(PdfFileReader(file("c:\\sample1.pdf","rb")),output)
append_pdf(PdfFileReader(file("c:\\sample2.pdf","rb")),output)
append_pdf(PdfFileReader(file("c:\\sample3.pdf","rb")),output)

output.write(file("c:\\combined.pdf","wb"))

------ Updated on 25th Nov. ------

------ Seems above code doesn't work anymore------

------ Please use the following:------

from PyPDF2 import PdfFileMerger, PdfFileReader
import os

merger = PdfFileMerger()

file_folder = "C:\\My Ducoments\\"

root, dirs, files = next(os.walk(file_folder))

for path, subdirs, files in os.walk(root):
    for f in files:
        if f.endswith(".pdf"):
            merger.append(file_folder + f)

merger.write(file_folder + "Economists-1.pdf")
Mark K
  • 8,767
  • 14
  • 58
  • 118
3
from PyPDF2 import PdfFileMerger
import webbrowser
import os
dir_path = os.path.dirname(os.path.realpath(__file__))

def list_files(directory, extension):
    return (f for f in os.listdir(directory) if f.endswith('.' + extension))

pdfs = list_files(dir_path, "pdf")

merger = PdfFileMerger()

for pdf in pdfs:
    merger.append(open(pdf, 'rb'))

with open('result.pdf', 'wb') as fout:
    merger.write(fout)

webbrowser.open_new('file://'+ dir_path + '/result.pdf')

Git Repo: https://github.com/mahaguru24/Python_Merge_PDF.git

3

Here's a time comparison for the most common answers for my specific use case: combining a list of 5 large single-page pdf files. I ran each test twice.

(Disclaimer: I ran this function within Flask, your mileage may vary)

TL;DR

pdfrw is the fastest library for combining pdfs out of the 3 I tested.

PyPDF2

start = time.time()
merger = PdfFileMerger()
for pdf in all_pdf_obj:
    merger.append(
        os.path.join(
            os.getcwd(), pdf.filename # full path
                )
            )
formatted_name = f'Summary_Invoice_{date.today()}.pdf'
merge_file = os.path.join(os.getcwd(), formatted_name)
merger.write(merge_file)
merger.close()
end = time.time()
print(end - start) #1 66.50084733963013 #2 68.2995400428772

PyMuPDF

start = time.time()
result = fitz.open()

for pdf in all_pdf_obj:
    with fitz.open(os.path.join(os.getcwd(), pdf.filename)) as mfile:
        result.insertPDF(mfile)
formatted_name = f'Summary_Invoice_{date.today()}.pdf'

result.save(formatted_name)
end = time.time()
print(end - start) #1 2.7166640758514404 #2 1.694727897644043

pdfrw

start = time.time()
result = fitz.open()

writer = PdfWriter()
for pdf in all_pdf_obj:
    writer.addpages(PdfReader(os.path.join(os.getcwd(), pdf.filename)).pages)

formatted_name = f'Summary_Invoice_{date.today()}.pdf'
writer.write(formatted_name)
end = time.time()
print(end - start) #1 0.6040127277374268 #2 0.9576816558837891
koopmac
  • 936
  • 10
  • 27
3

You can use pikepdf too (source code documentation).

Example code could be (taken from the documentation):

from glob import glob

from pikepdf import Pdf

pdf = Pdf.new()

for file in glob('*.pdf'):  # you can change this to browse directories recursively
    with Pdf.open(file) as src:
        pdf.pages.extend(src.pages)

pdf.save('merged.pdf')
pdf.close()

If you want to exclude pages, you might proceed another way, for instance copying pages to a new pdf (you can select which ones you do not copy, then, the pdf.pages object behaving like a list).

It is still actively maintained, which, as of february 2022, does not seem to be the case of PyPDF2 nor pdfrw.

I haven't benchmarked it, so I don't know if it is quicker or slower than other solutions.

One advantage over PyMuPDF, in my case, is that an official Ubuntu package is available (python3-pikepdf), what is practical to package my own software depending on it.

zezollo
  • 4,606
  • 5
  • 28
  • 59
1

A slight variation using a dictionary for greater flexibility (e.g. sort, dedup):

import os
from PyPDF2 import PdfFileMerger
# use dict to sort by filepath or filename
file_dict = {}
for subdir, dirs, files in os.walk("<dir>"):
    for file in files:
        filepath = subdir + os.sep + file
        # you can have multiple endswith
        if filepath.endswith((".pdf", ".PDF")):
            file_dict[file] = filepath
# use strict = False to ignore PdfReadError: Illegal character error
merger = PdfFileMerger(strict=False)

for k, v in file_dict.items():
    print(k, v)
    merger.append(v)

merger.write("combined_result.pdf")
Ogaga Uzoh
  • 2,037
  • 1
  • 10
  • 12
1

I used pdf unite on the linux terminal by leveraging subprocess (assumes one.pdf and two.pdf exist on the directory) and the aim is to merge them to three.pdf

 import subprocess
 subprocess.call(['pdfunite one.pdf two.pdf three.pdf'],shell=True)
user8291021
  • 326
  • 2
  • 9
  • 1
    This would work, however calling subprocess is not preferable over using PdfFileMerger from PyPDF2. Using shell=true introduces a security hazard. – Cloudkollektiv Nov 16 '20 at 14:36
1

You can use PdfFileMerger from the PyPDF2 module.

For example, to merge multiple PDF files from a list of paths you can use the following function:

from PyPDF2 import PdfFileMerger

# pass the path of the output final file.pdf and the list of paths
def merge_pdf(out_path: str, extracted_files: list [str]):
    merger   = PdfFileMerger()
    
    for pdf in extracted_files:
        merger.append(pdf)

    merger.write(out_path)
    merger.close()

merge_pdf('./final.pdf', extracted_files)

And this function to get all the files recursively from a parent folder:

import os

# pass the path of the parent_folder
def fetch_all_files(parent_folder: str):
    target_files = []
    for path, subdirs, files in os.walk(parent_folder):
        for name in files:
            target_files.append(os.path.join(path, name))
    return target_files 

# get a list of all the paths of the pdf
extracted_files = fetch_all_files('./parent_folder')

Finally, you use the two functions declaring.a parent_folder_path that can contain multiple documents, and an output_pdf_path for the destination of the merged PDF:

# get a list of all the paths of the pdf
parent_folder_path = './parent_folder'
outup_pdf_path     = './final.pdf'

extracted_files = fetch_all_files(parent_folder_path)
merge_pdf(outup_pdf_path, extracted_files)

You can get the full code from here (Source): How to merge PDF documents using Python

0

The answer from Giovanni G. PY in an easily usable way (at least for me):

import os
from PyPDF2 import PdfFileMerger

def merge_pdfs(export_dir, input_dir, folder):
    current_dir = os.path.join(input_dir, folder)
    pdfs = os.listdir(current_dir)
    
    merger = PdfFileMerger()
    for pdf in pdfs:
        merger.append(open(os.path.join(current_dir, pdf), 'rb'))

    with open(os.path.join(export_dir, folder + ".pdf"), "wb") as fout:
        merger.write(fout)

export_dir = r"E:\Output"
input_dir = r"E:\Input"
folders = os.listdir(input_dir)
[merge_pdfs(export_dir, input_dir, folder) for folder in folders];
faysou
  • 1,142
  • 11
  • 25
-1

def pdf_merger(path): """Merge the pdfs into one pdf"""

import logging
logging.basicConfig(filename = 'output.log', level = logging.DEBUG, format = '%(asctime)s %(levelname)s %(message)s' )

try:
    import glob, os
    import PyPDF2
    
    os.chdir(path)
    
    pdfs = []
    
    for file in glob.glob("*.pdf"):
        pdfs.append(file)
        
    if len(pdfs) == 0:
        logging.info("No pdf in the given directory")
        
    else:
        merger = PyPDF2.PdfFileMerger()
        
        for pdf in pdfs:
            merger.append(pdf)
            
        merger.write('result.pdf')
        merger.close()
        
except Exception as e:
    logging.error('Error has happened')
    logging.exception('Exception occured' + str(e))
-1

Use right python interpreter:

conda activate py_envs

pip install PyPDF2

Python code:

from PyPDF2 import PdfMerger

#set path files
import os
os.chdir('/ur/path/to/folder/')
cwd = os.path.abspath('')
files = os.listdir(cwd)

def merge_pdf_files():
    merger = PdfMerger()
    pdf_files = [x for x in files if x.endswith(".pdf")]
    [merger.append(pdf) for pdf in pdf_files]
    with open("merged_pdf_all.pdf", "wb") as new_file:
        merger.write(new_file)

if __name__ == "__main__":
    merge_pdf_files()
Azhar Khan
  • 3,829
  • 11
  • 26
  • 32
Kon Li
  • 9
  • 2