φυβλαςのβλογ
บล็อกของ phyblas



การใช้ PyPDF2 เพื่อตัดต่อและทำอะไรหลายๆอย่างกับไฟล์ pdf
เขียนเมื่อ 2023/03/02 08:56
แก้ไขล่าสุด 2024/02/17 16:16
 

มอดูล PyPDF2 ในไพธอนมีไว้สำหรับจัดการกับไฟล์ pdf โดยทำอะไรได้หลายๆอย่าง เช่นดึงเอาบางหน้าในไฟล์ หรือเอา pdf หลายไฟล์มาผสมเป็นไฟล์เดียว หรือแยกไฟล์เดียวเป็นหลายไฟล์ หรือทำการแก้ตัวไฟล์ในส่วนภาพรวมที่ไม่ใช่การเข้าไปแก้องค์ประกอบโดยละเอียด เช่นข้อมูล metadata เอาหน้ามาซ้อนทับกัน ทำเป็นลายน้ำ หรือจับหน้ากระดาษมาหมุนหรือย่อขยายขนาด

เพียงแต่ว่างานละเอียดอย่างเช่นการเข้าไปเติมหรือลบหรือย้ายข้อความหรือรูปภาพลงในหน้าของ pdf นั้นจะเกินขอบเขตความสามารถของ PyPDF2

การสร้างไฟล์ pdf ขึ้นมาจากศูนย์ หรือแปลงจากไฟล์ต่างหรือแปลง pdf ไปเป็นไฟล์ต่างๆก็ไม่สามารถทำได้เช่นกัน

แม้จะใช้ทำงานละเอียดลึกๆไม่ได้ แต่สิ่งที่ PyPDF2 ทำได้นั้นเป็นอะไรที่มักใช้งานบ่อย อีกทั้งใช้งานง่ายด้วย จึงถูกใช้อย่างกว้างขวาง

บทความนี้จะอธิบายการใช้ PyPDF2 ตั้งแต่เบื้องต้น




การติดตั้งและใช้งาน

PyPDF2 เป็นมอดูลที่ไม่ได้มีอยู่ในไพธอนตั้งแต่ต้น ต้องติดตั้งเพิ่ม ซึ่งก็สามารถติดตั้งได้ง่ายโดยใช้ pip
pip install PyPDF2

ส่วนในการใช้งานนั้นโดยทั่วไปแค่เริ่มจากเรียกใช้ตัวมอดูลโดยพิมพ์
import PyPDF2

เท่านี้ก็สามารถใช้งานทุกสิ่งทุกอย่างภายในมอดูลนี้ได้แล้ว ไม่ได้มีความจำเป็นจะต้องมา import แยกส่วนให้ยุ่งยาก ถือว่าใช้งานได้ง่าย




การดูข้อมูลต่างๆของไฟล์ pdf

ขอเริ่มที่การเปิดไฟล์ pdf ขึ้นมาเพื่อดูข้อมูลต่างๆคร่าวๆภายในนั้น

การอ่านไฟล์ pdf ขึ้นมาทำได้โดยสร้างออบเจ็กต์ PyPDF2.PdfFileReader คือออบเจ็กต์ตัวอ่านอข้อมูลไฟล์

เช่นมีไฟล์ชื่อ รายการอาหาร.pdf อยู่ เราอาจใช้ PyPDF2 เพื่อเปิดขึ้นมาอ่านดูข้อมูลพื้นฐานต่างๆได้ดังนี้
import PyPDF2

pdf = PyPDF2.PdfFileReader('รายการอาหาร.pdf')
print(pdf.numPages) # ดูจำนวนหน้า
print(pdf.documentInfo) # ดู metadata
print(pdf.isEncrypted) # ดูว่าไฟล์นี้ต้องป้อนรหัสผ่านเพื่อเปิดหรือไม่




การเข้าถึงแต่ละหน้าภายใน pdf

ไฟล์ pdf นั้นจะประกอบด้วยหน้ากระดาษหลายๆหน้า เวลาที่จัดการไฟล์ pdf เรามักจะต้องจัดการแยกเป็นหน้าๆ ดังนั้นก่อนจะทำอะไรต้องเริ่มจากเข้าถึงออบเจ็กต์ตัวหน้ากระดาษของหน้านั้นๆ

วิธีการเข้าถึงนั้นอาจทำได้โดยใช้ .getPage(เลขหน้า) หรือ .pages[เลขหน้า] ก็ได้ โดยที่เลขหน้าในที่นี้จะเริ่มไล่จาก 0 ดังนั้นหน้าแรกคือเลข 0 หน้าที่สองเป็นเลข 1 ตามลำดับ เช่นถ้าต้องการเอาหน้าแรกก็อาจเขียนเป็น
na = pdf.getPage(0)
na = pdf.pages[0]

เพื่อให้เขียนสั้น ในตัวอย่างต่อจากนี้ไปก็จะเขียนเป็น .pages[เลขหน้า] ทั้งหมด

ออบเจ็กต์ตัวหน้ากระดาษแต่ละหน้านั้นเป็นออบเจ็กต์ชนิด PageObject
print(type(pdf.pages[0])) # ได้ PyPDF2._page.PageObject

หากเอาออบเจ็กต์ชนิดนี้มาสั่ง print ก็จะแสดงโครงสร้างส่วนประกอบภายในหน้าเป็นโค้ดให้เห็น

เช่น ขอยกตัวอย่างโดยลองสร้าง pdf หน้าเปล่าๆขึ้นมาจากไฟล์ไมโครซอฟท์เวิร์ดด้วยมอดูล docx2pdf (รายละเอียดอ่านได้ใน https://phyblas.hinaboshi.com/20230207) โดยไม่ได้ใส่ข้อความหรือรูปภาพอะไรลงไปเลย แล้วเอามาเปิดด้วย PyPDF2 ดู
pdf = PyPDF2.PdfFileReader('หน้าเปล่า.pdf')
print(pdf.pages[0])

ก็จะขึ้นโครงสร้างแบบนี้มาให้เห็น
{'/Type': '/Page',
 '/Parent': {'/Type': '/Pages',
  '/Count': 1,
  '/Kids': [IndirectObject(3, 0, 1951717119360)]},
 '/Resources': {'/Font': {'/F1': {'/Type': '/Font',
    '/Subtype': '/TrueType',
    '/Name': '/F1',
    '/BaseFont': '/BCDEEE+YuMincho-Regular',
    '/Encoding': '/WinAnsiEncoding',
    '/FontDescriptor': {'/Type': '/FontDescriptor',
     '/FontName': '/BCDEEE+YuMincho-Regular',
     '/Flags': 32,
     '/ItalicAngle': 0,
     '/Ascent': 880,
     '/Descent': -120,
     '/CapHeight': 880,
     '/AvgWidth': 969,
     '/MaxWidth': 2243,
     '/FontWeight': 400,
     '/XHeight': 250,
     '/Leading': 315,
     '/StemV': 96,
     '/FontBBox': [-1000, -120, 1243, 880],
     '/FontFile2': {'/Filter': '/FlateDecode', '/Length1': 170008}},
    '/FirstChar': 32,
    '/LastChar': 32,
    '/Widths': [260]}},
  '/ExtGState': {'/GS7': {'/Type': '/ExtGState', '/BM': '/Normal', '/ca': 1},
   '/GS8': {'/Type': '/ExtGState', '/BM': '/Normal', '/CA': 1}},
  '/ProcSet': ['/PDF', '/Text', '/ImageB', '/ImageC', '/ImageI']},
 '/MediaBox': [0, 0, 595.32, 841.92],
 '/Contents': {'/Filter': '/FlateDecode'},
 '/Group': {'/Type': '/Group', '/S': '/Transparency', '/CS': '/DeviceRGB'},
 '/Tabs': '/S',
 '/StructParents': 0}

ซึ่งตรงนี้จะเห็นว่าแม้จะเป็นหน้าเปล่าก็มีอะไรขนาดนี้แล้ว ดูแล้วค่อนข้างซับซ้อน มีอะไรมากมาย ขอไม่อธิบายในรายละเอียด

ที่สามารถจะทำได้ง่ายๆก็เช่นการดึงข้อความภายในหน้ากระดาษ หากต้องการดึงเอาข้อความที่อยู่ภายในหน้าก็สามารถทำได้โดยเมธอด .extractText()
pdf = PyPDF2.PdfFileReader('รายการอาหาร.pdf')
na = pdf.pages[0]
print(na.extractText()) # ได้ข้อความในหน้านั้นมา

หรือหากอยากรู้ว่าหน้ากระดาษนี้มีขนาดกว้างหรือสูงเท่าไหร่ก็ดูได้ที่แอตทริบิวต์ .mediabox.width และ .mediabox.height
pdf = PyPDF2.PdfFileReader('รายการอาหาร.pdf')
na = pdf.pages[0]
print(na.mediabox.width) # ความกว้างหน้า
print(na.mediabox.height) # ความสูงหน้า




การดึงเอาเฉพาะบางหน้าของ pdf

ไฟล์ pdf ที่เปิดขึ้นมาไว้ในออบเจ็กต์ PyPDF2.PdfFileReader นั้นทำได้แค่ดูข้อมูล แต่ไม่สามารถไปแก้ไขอะไรได้ แต่เราสามารถนำข้อมูลในแต่ละหน้าของมันมาใช้เพื่อสร้างไฟล์ pdf ไฟล์ใหม่ได้

การสร้างไฟล์ pdf ใหม่ขึ้นมาทำได้โดยสร้างออบเจ็กต์ PyPDF2.PdfFileWriter เป็นออบเจ็กต์ตัวเขียนไฟล์ เอาไว้ใช้สำหรับรวบรวมหน้าเพื่อจะนำมาบันทึกเป็นไฟล์ pdf ใหม่ขึ้น โดยเมื่อสร้างขึ้นมาใหม่มันจะว่างเปล่า ให้ใช้เมธอด .addPage() เพื่อทำการใส่หน้ากระดาษที่ต้องการลงไป จากนั้นก็ใช้เมธอด .write() เพื่อบันทึกลงไฟล์ใหม่

เช่นถ้าต้องการดึงหน้าแรกจากไฟล์ pdf ที่มีอยู่เดิมก็ทำได้โดยใช้ PyPDF2.PdfFileReader เปิดไฟล์นั้นขึ้นมา แล้วสร้าง PyPDF2.PdfFileWriter แล้วนำหน้าแรกนั้นใส่ลงไป แบบนี้
pdf_0 = PyPDF2.PdfFileReader('รายการอาหาร.pdf') # เปิดไฟล์เอกสารเดิม
na = pdf_0.pages[0] # ดึงเอาหน้าแรกมา

pdf_x = PyPDF2.PdfFileWriter() # สร้างออบเจ็กต์ตัวเขียนไฟล์ขึ้นมาใหม่
pdf_x.addPage(na) # เพิ่มหน้าที่ดึงมานั้นลงไป
pdf_x.write('หน้าแรกของรายการอาหาร.pdf') # บันทึกลงไฟล์

เท่านี้เราก็จะได้ไฟล์ pdf ใหม่ที่มีหน้าเดียวหรือหน้าแรกของไฟล์เดิมนั้น

หากต้องการดึงหลายหน้าก็ .addPage() หน้าที่ต้องการไปเรื่อยๆ เช่นถ้าจะเอาแค่หน้าที่ 2 ถึง 4 ก็เขียนแบบนี้
pdf_0 = PyPDF2.PdfFileReader('รายการอาหาร.pdf')
pdf_x = PyPDF2.PdfFileWriter()
na_raek = 2 # หน้าแรกที่จะเอา
na_sutthai = 4 # หน้าสุดท้ายที่จะเอา
# วนซ้ำเพื่อเพิ่มไปทีละหน้า
for i in range(na_raek-1,na_sutthai):
    na = pdf_0.pages[i]
    pdf_x.addPage(na)
pdf_x.write('หน้า 2-4 ของรายการอาหาร.pdf') # บันทึกลงไฟล์




การแยกแต่ละหน้าเป็น pdf คนละไฟล์

ถ้าต้องการจะเอาไฟล์ pdf หลายหน้าที่มีอยู่เดิมมาแยกแต่ละหน้าให้กลายเป็น pdf หลายไฟล์ก็อาจเขียนได้ดังนี้
pdf_0 = PyPDF2.PdfFileReader('รายการอาหาร.pdf')
for i in range(pdf_0.numPages):
    pdf_x = PyPDF2.PdfFileWriter()
    pdf_x.addPage(pdf_0.pages[i])
    pdf_x.write('รายการอาหารหน้า %d.pdf'%(i+1))




การใส่หน้าเปล่าเข้าไป

PyPDF2 นั้นไม่มีความสามารถในการสร้างหน้ากระดาษใหม่ที่มีข้อมูลขึ้นมา แต่ถ้าแค่หน้าเปล่าๆละก็สามารถสร้างได้อยู่ ทำได้โดยใช้ฟังก์ชัน .addBlankPage() ที่ตัวออบเจ็กต์ PyPDF2.PdfFileWriter ซึ่งก็คล้ายกับ .addPage() แค่สิ่งที่ใส่เข้าไปนั้นเป็นหน้าเปล่าๆเท่านั้น

ในการใส่หน้าเปล่าเข้าไปนั้นสิ่งที่จะต้องกำหนดก็มีเพียงขนาดความกว้างยาวของหน้าเปล่านั่นเท่านั้น

เช่น เราอาจลองสร้างไฟล์ pdf เปล่าที่มีหน้ากระดาษแผ่นเดียวขนาด 400×300 ได้ดังนี้
pdf_x = PyPDF2.PdfFileWriter()
pdf_x.addBlankPage(400,300)
pdf_x.write('หน้าว่างเปล่า.pdf')

ตัวอย่างการใช้เพิ่มเติมอีกอย่าง เช่น หากต้องการเอาไฟล์ pdf มาแทรกหน้าว่างเปล่าคั่นทีละหน้าก็อาจเขียนได้แบบนี้
pdf_0 = PyPDF2.PdfFileReader('รายการอาหาร.pdf')
pdf_x = PyPDF2.PdfFileWriter()
for i in range(pdf_0.numPages):
    na = pdf_0.pages[i]
    pdf_x.addPage(na)
    pdf_x.addBlankPage(na.mediabox.width,na.mediabox.height) # เพิ่มหน้าเปล่าที่มีขาดเท่ากับหน้าก่อนหน้า
pdf_x.write('รายการอาหารที่แทรกหน้าเปล่า.pdf')




การหมุนหน้า pdf

ออบเจ็กต์หน้ากระดาษสามารถนำมาหมุนพลิกแนวตั้งแนวนอนได้โดยใช้เมธอด .rotateClockwise() สำหรับหมุนตามเข็มนาฬิกา และ .rotateCounterClockwise() สำหรับหมุนทวนเข็มนาฬิกา

โดยเมธอดนี้ต้องใส่ค่ามุมลงไป โดยอาจใส่เป็น 90, 180, 270 ก็ได้ แต่ใส่ค่าอื่นไม่ได้เพราะการพลิกต้องพลิกเป็นมุนฉากเท่านั้น จะพลิกเอียงๆครึ่งๆกลางๆไม่ได้

ตัวอย่างการใช้ เช่น หากต้องการนำ pdf ที่มีอยู่มาหมุนตามเข็มไป 90 องศาหมดทุกหน้า
pdf_0 = PyPDF2.PdfFileReader('รายการอาหาร.pdf')
pdf_x = PyPDF2.PdfFileWriter()
for i in range(pdf_0.numPages):
    na = pdf_0.pages[i]
    na.rotateClockwise(90)
    pdf_x.addPage(na)
pdf_x.write('รายการอาหารตะแคง.pdf')




การย่อขยายหน้า pdf

หน้ากระดาษสามารถนำมาย่อขยายเปลี่ยนขนาดได้โดยใช้เมธอด .scale() โดยใส่ค่าสัดส่วนขนาดในแนวตั้งและแนวนอนลงไป ค่าที่ใส่เป็นจำนวนเท่าของขนาดเดิม ถ้าใส่ 1 คือขนาดเดิม

เช่นลองสร้าง pdf ใหม่โดยเอา pdf เดิมมาทำการขยายให้ความกว้างเป็น ๒ เท่าก็อาจเขียนได้ดังนี้
pdf_0 = PyPDF2.PdfFileReader('รายการอาหาร.pdf')
pdf_x = PyPDF2.PdfFileWriter()
for i in range(pdf_0.numPages):
    na = pdf_0.pages[i]
    na.scale(2,1)
    pdf_x.addPage(na)
pdf_x.write('รายการอาหาาาาาาร.pdf')

หรืออาจเปลี่ยนขนาดโดยกำหนดความกว้างและสูงใหม่โดยใช้เมธอด .scale_to() เช่นถ้าต้องการได้หน้ากว้าง 500 สูง 400 ก็เขียน
na.scale_to(500,400)




การรวมไฟล์ pdf หลายไฟล์เข้าด้วยกัน

ใน PyPDF2 นั้นมีออบเจ็กต์ PyPDF2.PdfFileMerger ซึ่งเอาไว้ใช้รวมไฟล์ pdf หลายไฟล์เข้าด้วยกัน

ตัวอย่างการใช้ เช่น ถ้าต้องการเอาหน้าทั้งหมดจาก pdf ๒ ไฟล์มาต่อรวมกันก็เขียนได้แบบนี้
pdf_x = PyPDF2.PdfFileMerger()
pdf_x.append('รายการอาหารเช้า.pdf')
pdf_x.append('รายการอาหารเย็น.pdf')
pdf_x.write('รายการอาหารเช้าเย็น.pdf')

การรวมไฟล์ในลักษณะเดียวกันนี้ก็อาจทำได้โดยใช้ PyPDF2.PdfFileWriter เช่นกัน โดยอาจใช้เมธอด .appendPagesFromReader() เพื่อทำการใส่หน้าทั้งหมดจากตัวออบเจ็กต์ PyPDF2.PdfFileReader เขียนได้ดังนี้
pdf_a = PyPDF2.PdfFileReader('รายการอาหารเช้า.pdf')
pdf_b = PyPDF2.PdfFileReader('รายการอาหารเย็น.pdf')
pdf_x = PyPDF2.PdfFileWriter()
pdf_x.appendPagesFromReader(pdf_a)
pdf_x.appendPagesFromReader(pdf_b)
pdf_x.write('รายการอาหารเช้าเย็น.pdf')

จะเห็นว่าเขียนยาวขึ้นหน่อยเพราะต้องเปิดไฟล์ด้วย PyPDF2.PdfFileReader ก่อน แต่ผลที่ได้ก็ไม่ต่างกัน

แต่ว่าเมธอด .append() ของ PyPDF2.PdfFileMerger นั้นยังสามารถใส่คีย์เวิร์ด pages เข้าไปเพื่อกำหนดให้อาเฉพาะหน้าที่ต้องการได้ โดยใส่เป็นทูเพิลของช่วง (หน้าแรก,หน้าสุดท้าย) ช่นถ้าต้องการแค่ ๒ หน้าแรกของทั้ง ๒ ไฟล์ก็อาจเขียนแบบนี้
pdf_x = PyPDF2.PdfFileMerger()
pdf_x.append('รายการอาหารเช้า.pdf',pages=(0,1))
pdf_x.append('รายการอาหารเย็น.pdf',pages=(0,1))
pdf_x.write('รายการอาหารรวมบางหน้า.pdf')

กรณีนี้ถ้าใช้ PyPDF2.PdfFileWriter ก็อาจต้องใช้เมธอด .addPage() เพื่อยัดใส่ทีละหน้าแบบนี้
pdf_a = PyPDF2.PdfFileReader('รายการอาหารเช้า.pdf')
pdf_b = PyPDF2.PdfFileReader('รายการอาหารเย็น.pdf')
pdf_x = PyPDF2.PdfFileWriter()
pdf_x.addPage(pdf_a.pages[0])
pdf_x.addPage(pdf_a.pages[1])
pdf_x.addPage(pdf_b.pages[0])
pdf_x.addPage(pdf_b.pages[1])
pdf_x.write('รายการอาหารรวมบางหน้า.pdf')




การซ้อนหน้าเข้าด้วยกัน

PyPDF2 สามารถนำเอาหน้ากระดาษใน pdf ๒ หน้ามาซ้อนรวมเข้าด้วยกันได้ ทำได้โดยใช้เมธอด .mergePage() ที่ตัวหน้าที่ต้องการให้เป็นพื้น โดยใส่หน้าที่ต้องการจะเอามาทับลงไป คือเขียนเป็น หน้าที่เป็นพื้น.mergePage(หน้าที่ต้องการทับ)

เช่น มีไฟล์ pdf ที่มี ๒ หน้า มีอักษร "ฌ" กับ "ฬ" อยู่แบบนี้ แล้วต้องการเอามาซ้อนรวมกัน



เขียนโค้ดได้ดังนี้
pdf_0 = PyPDF2.PdfFileReader('ฌฬ.pdf')
na_0 = pdf_0.pages[0]
na_0.mergePage(pdf_0.pages[1])
pdf_x = PyPDF2.PdfFileWriter()
pdf_x.addPage(na_0)
pdf_x.write('ฌxฬ.pdf')

แล้วก็จะได้ไฟล์ pdf ที่มีส่วนประกอบของ ๒ หน้ามาซ้อนทับกัน โดยหน้าหลังวางทับหน้าแรก






การใส่ลายน้ำ

เมธอด .mergePage() นั้นสามารถนำมาใช้เพื่อทำลายน้ำได้ โดยภาพที่ต้องการทำเป็นลายน้ำนั้นต้องเตรียมไว้อยู่ในไฟล์ pdf (ถ้าเป็นไฟล์รูปหรือไฟล์ชนิดอื่นให้แปลงเป็น pdf ก่อน)

ตัวอย่างเช่นเตรียมภาพสำหรับทำลายน้ำไว้ในไฟล์ ลายน้ำ.pdf แล้วต้องการใส่ลายน้ำนี้ลงไปในทุกหน้าของไฟล์ รายการอาหาร.pdf ก็อาจเขียนได้ดังนี้
pdf_0 = PyPDF2.PdfFileReader('รายการอาหาร.pdf')
pdf_x = PyPDF2.PdfFileWriter()
for i in range(pdf_0.numPages):
    na_i = pdf_0.pages[i]
    pdf_lainam = PyPDF2.PdfFileReader('ลายน้ำ.pdf')
    na_lainam = pdf_lainam.pages[0]
    na_lainam.scale_to(float(na_i.mediabox.width),
                       float(na_i.mediabox.height))
    na_lainam.mergePage(na_i)
    pdf_x.addPage(na_lainam)
pdf_x.write('รายการอาหารใส่ลายน้ำ.pdf')

ในที่นี้หน้าลายน้ำถูกใช้เป็นพื้น เพราะต้องการให้ซ้อนอยู่ด้านล่างข้อความของไฟล์หลัก แต่ถ้าเผลอทำสลับกันละก็ลายน้ำจะทับอยู่ด้านบนบังข้อความไป เพราะฉะนั้นต้องระวัง

ส่วน.scale_to() นั้นใส่เพื่อทำให้ขนาดของหน้าลายน้ำมีขนาดเท่าหน้าของไฟล์หลัก เพียงแต่ว่าจริงๆแล้วควรจะเตรียมไฟล์ที่มีขนาดหน้าเท่านั้นไว้ตั้งแต่แรกดีกว่า แบบนั้นก็ไม่ต้องมาปรับขนาดอีก ก็ตัดบรรทัดนี้ทิ้งไปได้




การใส่รหัสผ่านให้ไฟล์ pdf

ไฟล์ pdf นั้นสามารถตั้งรหัสเพื่อให้เฉพาะคนที่รู้รหัสเท่านั้นจึงจะอ่านเนื้อหาภายในได้

การตั้งรหัสนั้นก็สามารถทำได้โดยใช้เมธอด .encrypt() ที่ตัวออบเจ็กต์ PyPDF2.PdfFileWriter

หากต้องการทำให้ไฟล์ pdf ที่มีอยู่เดิมกลายเป็นไฟล์ที่ต้องใส่รหัสผ่าน อาจทำได้ดังนี้
pdf_0 = PyPDF2.PdfFileReader('รายการอาหาร.pdf')
pdf_x = PyPDF2.PdfFileWriter()
pdf_x.appendPagesFromReader(pdf_0)
pdf_x.encrypt('xxxx')
pdf_x.write('รายการอาหารลับ.pdf')

เท่านี้เมื่อจะเปิดไฟล์นี้ก็จะต้องพิมพ์รหัส xxxx ลงไปจึงจะอ่านได้






การเปิดอ่านไฟล์ pdf ที่ต้องป้อนรหัสผ่าน

ไฟล์ที่ต้องป้อนรหัสจึงจะอ่านได้นั้นถ้าเอามาอ่านโดย PyPDF2.PdfFileReader โดยไม่ได้ทำอะไรก่อนก็จะเกิดข้อผิดพลาดขึ้น

เช่นลองเอาไฟล์ที่เข้ารหัสจากตัวอย่างที่แล้วมาเปิด แล้วลองพยายามดูจำนวนหน้า
pdf_0 = PyPDF2.PdfFileReader('รายการอาหารที่ใส่รหัส.pdf')
pdf_0.numPages # ได้ FileNotDecryptedError: File has not been decrypted

การที่จะทำให้สามารถอ่านข้อมูลจากไฟล์นี้ได้โดยไม่เกิดข้อผิดพลาดจะต้องใช้เมธอด .decrypt() โดยป้อนรหัสลงไป แบบนี้จึงจะสามารถเปิดอ่านได้ตามปกติ
pdf_0 = PyPDF2.PdfFileReader('รายการอาหารลับ.pdf')
pdf_0.decrypt('xxxx') # ใส่รหัสเพื่อปลดล็อก
pdf_0.numPages # ไม่เกิดข้อผิดพลาด

หากต้องการเอาไฟล์ที่เข้ารหัสไว้มาปลดรหัสแก้เป็นไฟล์ที่ไม่ต้องใส่รหัสก็อ่านได้ละก็อาจเขียนดังนี้
pdf_0 = PyPDF2.PdfFileReader('รายการอาหารลับ.pdf')
pdf_0.decrypt('xxxx') # ใส่รหัสเพื่อปลดล็อก
pdf_x = PyPDF2.PdfFileWriter()
pdf_x.appendPagesFromReader(pdf_0)
pdf_x.write('รายการอาหารไม่ลับ.pdf')

ไฟล์ใหม่ที่ได้นั้นจะสามารถเปิดอ่านได้โดยไม่ต้องป้อนรหัสผ่านแล้ว




การจัดการ metadata

metadata คือข้อมูลที่ติดมากับไฟล์ pdf ซ่อนอยู่ภายในไฟล์ ไม่ได้แสดงให้ผู้ใช้ที่เปิดอ่านไฟล์ทั่วไปได้เห็น ใช้บอกข้อมูลคร่าวๆเช่นว่าไฟล์นี้ถูกสร้างขึ้นมาด้วยโปรแกรมอะไร เมื่อไหร่ ใครเป็นคนสร้างขึ้นมาตั้งแต่แรก เป็นต้น

เมื่อใช้ PyPDF2.PdfFileReader เพื่ออ่านไฟล์เข้ามาเราสามารถดูข้อมูล metadata ได้ที่แอตทริบิวต์ .documentInfo โดยจะได้มาเป็นออบเจ็กต์ดิกชันนารีที่บรรจุข้อมูลต่างๆไว้

ข้อมูลใน metadata หลักๆที่สำคัญมีประมาณนี้

/Author ผู้ที่สร้างไฟล์ขึ้นมา
/Creator โปรแกรมที่ใช้สร้างไฟล์
/CreationDate เวลาที่ไฟล์ถูกสร้างขึ้น
/ModDate เวลาที่ไฟล์ถูกแก้ไขล่าสุด
/Producer โปรแกรมที่ใช้ดัดแปลงไฟล์มา
/Title ไตเติล

นอกจากนี้จริงๆแล้วจะใส่อะไรก็ได้ ตามแต่ที่ผู้สร้างไฟล์อยากจะใส่

ตัวอย่างเช่นเปเปอร์ที่โหลดจากเว็บ Identifying Exoplanets with Deep Learning. V. Improved Light-curve Classification for TESS Full-frame Image Observations

เมื่อลองโหลดเข้ามาแล้วเปิดดู metadata
pdf_0 = PyPDF2.PdfFileReader('Tey_2023_AJ_165_95.pdf')
print(pdf_0.documentInfo)

ก็จะออกมาแบบนี้
{'/Creator': 'IOPP',
 '/CrossMarkDomains#5B1#5D': 'iop.org',
 '/ModDate': "D:20230206150834+05'30'",
 '/robots': 'noindex',
 '/doi': '10.3847/1538-3881/acad85',
 '/Keywords': 'Neural networks; Transit photometry; Exoplanet detection methods; Exoplanet catalogs',
 '/Title': 'Identifying Exoplanets with Deep Learning. V. Improved Light-curve Classification for TESS Full-frame Image Observations',
 '/CreationDate': "D:20230206150814+05'30'",
 '/crossmarkMajorVersionDate': '2023-02-09',
 '/Subject': 'The Astronomical Journal, 165(2023) 95. doi:10.3847/1538-3881/acad85',
 '/Author': 'Evan Tey'}

จะมีข้อมูลตรงนี้ถูกใส่ไว้เยอะแค่ไหนก็อาจขึ้นอยู่กับสำนักพิมพ์และโปรแกรมที่ใช้ บางทีข้อมูลอาจจะไม่ถูกใส่อยู่เลยก็ได้

ปกติเมื่อใช้ PyPDF2.PdfFileWriter สร้างไฟล์ pdf ขึ้นมาใหม่จะไม่มีข้อมูล metadata อยู่ภายในนั้นเลย

แต่ว่าในตัวอย่างหลายตัวอย่างที่ยกมาข้างต้นเช่นการเอาไฟล์ pdf มาหมุนหรือย่อขยายขนาดนั้นหรือใส่รหัสผ่านนั้น ตัวไฟล์ที่บันทึกนั้นไม่ใช่ตัวไฟล์เดิมจริงๆ ไม่ได้บรรจุข้อมูล metadata เหมือนอย่างในไฟล์เดิม แบบนั้นถ้าไฟล์เดิมมี metadata อยู่ก็ย่อมจะหายไปหมด

หากต้องการจะรักษา metadata จากไฟล์เดิมไว้ก็ทำได้โดยใช้เมธอด .addMetadata() โดยใส่ metadata จากไฟล์เก่า แค่นี้ก็ได้ไฟล์ใหม่ที่มี metadata เหมือนกับไฟล์เดิมแล้ว

เช่นโค้ดที่ทำการใส่รหัสให้ไฟล์ pdf นั้นอาจเขียนใหม่ได้เป็น
pdf_0 = PyPDF2.PdfFileReader('รายการอาหาร.pdf')
pdf_x = PyPDF2.PdfFileWriter()
pdf_x.appendPagesFromReader(pdf_0)
pdf_x.addMetadata(pdf_0.documentInfo) # แค่เพิ่มบรรทัดนี้เข้าไป
pdf_x.encrypt('xxxx')
pdf_x.write('รายการอาหารลับ.pdf')

เท่านี้ก็จะได้ไฟล์ใหม่ที่มี metadata เหมือนเดิม แค่เพิ่มการใส่รหัสเข้าไป

นอกจากนี้ .addMetadata() นั้นยังใช้เพื่อแก้ข้อมูล metadata หรือเพิ่มใหม่ได้ด้วย โดยใส่เป็นดิกชันนารีของค่าที่ต้องการแก้หรือเพิ่มเข้าไปได้เลย ถ้าค่านั้นมีอยู่แล้วก็จะเป็นการเขียนทับ ถ้ายังไม่มีก็จะถูกเพิ่มเข้าไปใหม่

เช่นถ้าต้องการเอาไฟล์มาแก้ข้อมูล metadata ในส่วนของ /Author ซึ่งหมายถึงผู้ที่เป็นเจ้าของไฟล์ (คนที่สร้างไฟล์เป็นคนแรก) ก็ทำได้โดย
pdf_0 = PyPDF2.PdfFileReader('รายการอาหาร.pdf')
pdf_x = PyPDF2.PdfFileWriter()
pdf_x.appendPagesFromReader(pdf_0)
pdf_x.addMetadata(pdf_0.documentInfo)
pdf_x.addMetadata({'/Author':'ข้าพเจ้าเอง'}) # ส่วนที่ต้องการแก้หรือเพิ่ม
pdf_x.write('รายการอาหารของฉันเอง.pdf')

หรือหากต้องการจะลบ metadata บางส่วนที่มีอยู่ก็ทำได้ เช่นถ้าต้องการลบ /Author ออกก็อาจเขียนแบบนี้
pdf_0 = PyPDF2.PdfFileReader('รายการอาหาร.pdf')
info = pdf_0.documentInfo # ดึงดิกของตัวข้อมูล metadata ออกมา
del info['/Author'] # ลบตัวที่ไม่ต้องการทิ้ง
pdf_x = PyPDF2.PdfFileWriter()
pdf_x.appendPagesFromReader(pdf_0)
pdf_x.addMetadata(info) # ใส่ metadata ที่ลบบางส่วนทิ้งแล้วเข้าไป
pdf_x.write('รายการอาหารที่ไม่มีเจ้าของ.pdf')




สรุปแอตทริบิวต์และเมธอดของ PdfFileReader

สุดท้ายนี้ขอสรุปแอตทริบิวต์และเมธอดที่สำคัญภายในออบเจ็กต์ PyPDF2.PdfFileReader ที่จริงยังมีอีกมากมายที่ไม่ได้พูดถึง แต่ในที่นี้จะขอสรุปเฉพาะที่ได้เขียนถึงมาทั้งหมด

.numPages จำนวนหน้าของไฟล์
.documentInfo ข้อมูล metadata
.pages ลิสต์ของออบเจ็กต์หน้ากระดาษทั้งหมด
.getPage(x) เอาออบเจ็กต์หน้ากระดาษ x
.isEncrypted ดูว่าไฟล์ถูกขึ้นรหัสอยู่หรือเปล่า
.decrypt(x) ป้อนรหัสผ่านเพื่อปลดล็อกไฟล์ที่ถูกตั้งรหัสผ่านไว้

x ในวงเล็บหลังชื่อเมธอดในที่นี้หมายถึงสิ่งที่ต้องใส่ลงไปขณะใช้เมธอดนั้นๆ รายละเอียดดูที่เนื้อหาด้านบน




สรุปเมธอดของ PdfFileWriter

.write(x) บันทึกไฟล์
.addPage(x) ใส่หน้าใหม่เข้าไป
.addBlankPage() ใส่หน้าเปล่าเข้าไป
.appendPagesFromReader(x) ใส่หน้าทั้งหมดจากออบเจ็กต์ PdfFileReader
.addMetadata(x) ใส่ metadata เข้าไป
.encrypt(x) ตั้งรหัสผ่านที่ต้องป้อนเวลาจะเปิดไฟล์




สรุปแอตทริบิวต์และเมธอดของ PageObject

.extractText() เอาข้อความตัวหนังสือทั้งหมดในหน้านั้น
.mediabox.width ความกว้างของหน้า
.mediabox.height ความสูงของหน้า
.rotateClockwise(x) หมุนหน้าตามเข็มนาฬิกาเป็นมุม x
.rotateCounterClockwise(x) หมุนหน้าทวนเข็มนาฬิกาเป็นมุม x
.scale(x,y) ปรับขนาดของหน้าเป็นกว้าง x เท่าสูง y เท่าจากเดิม
.scale_to(x,y) ปรับขนาดของหน้าเป็นกว้าง x สูง y
.mergePage(x) ซ้อนหน้า x ลงไป




อ้างอิง



-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python

ไม่อนุญาตให้นำเนื้อหาของบทความไปลงที่อื่นโดยไม่ได้ขออนุญาตโดยเด็ดขาด หากต้องการนำบางส่วนไปลงสามารถทำได้โดยต้องไม่ใช่การก๊อปแปะแต่ให้เปลี่ยนคำพูดเป็นของตัวเอง หรือไม่ก็เขียนในลักษณะการยกข้อความอ้างอิง และไม่ว่ากรณีไหนก็ตาม ต้องให้เครดิตพร้อมใส่ลิงก์ของทุกบทความที่มีการใช้เนื้อหาเสมอ

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
มอดูลต่างๆ
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
ภาษา javascript
ภาษา mongol
ภาษาศาสตร์
maya
ความน่าจะเป็น
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
-- บันทึกในฮ่องกง
-- บันทึกในมาเก๊า
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  ค้นหาบทความ

  บทความล่าสุด

นั่งรถไฟตามสายอิชิโนมากิมาลงที่สถานีทากางิโจวเมืองมัตสึชิมะเปลี่ยนรถไฟไปยังสายหลักโทวโฮกุ
โนบิรุ ซากสถานีรถไฟเก่าและสวนอธิษฐานฟื้นฟูภัยพิบัติแผ่นดินไหวครั้งใหญ่ในญี่ปุ่นตะวันออกเมืองฮิงาชิมัตสึชิมะ
เดินเล่นชมนกนางนวลแถวลานกว้างริมฝั่งทะเลเมืองโอนางาวะ ดูซากป้อมยามเก่าที่ถูกทำลายจากคลื่นทสึนามิ แล้วนั่งรถไฟกลับจากสถานีโอนางาวะ
ซีพัลเพียร์โอนางาวะ ณ เมืองเล็กๆริมชายฝั่งอ่าวสุดทางรถไฟสายอิชิโนมากิซึ่งฟื้นฟูขึ้นหลังเหตุการณ์คลื่นทสึนามิ
การทำให้สายอักขระใน dart สามารถแปลงข้อความแบบ sprintf ได้เหมือนใน python หรือ ruby

  บทความแนะนำ

ตัวอักษรกรีกและเปรียบเทียบการใช้งานในภาษากรีกโบราณและกรีกสมัยใหม่
ที่มาของอักษรไทยและความเกี่ยวพันกับอักษรอื่นๆในตระกูลอักษรพราหมี
การสร้างแบบจำลองสามมิติเป็นไฟล์ .obj วิธีการอย่างง่ายที่ไม่ว่าใครก็ลองทำได้ทันที
รวมรายชื่อนักร้องเพลงกวางตุ้ง
ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
การใช้ unix shell เบื้องต้น ใน linux และ mac
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ

บทความแต่ละเดือน

2024年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2023年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2022年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2021年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2020年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

ค้นบทความเก่ากว่านั้น

ไทย

日本語

中文