任意のページごとに分解したPDFの名前に日付を振る


そもそも

任意のページごとにPDFファイルを分解する (https://qiita.com/nabe3ch/items/e1638544546ab1f4b18f) の続き。

日ごとに分かれたPDFの統計資料(1カ月ごとにまとめて出る)を、上司の指示で日ごと(2ページがワンセット)に分割してほしいと言われた同僚が、ChromeでPDFを開いて2ページ(日付)ごとに保存しているのを不憫に思い、任意のページごとに分割するプログラムをPythonで作り、自動化でできた空き時間で昼飯に行った。

その際「ついでにファイル名を日付にするってできる?」と聞かれた。そこまで気が回らなかった、すまぬという気持ち。「そもそも分割するの止めたらええやんか」といったが、「今までそうしてきたから、今さら変えられないんだ…」とか。ニッチさを増す社畜からの解放プログラムに需要があるのか疑問だが、備忘録がてらに残しておく。

こんな感じに変えた

「test.pdf」という、2020/01/01~2020/02/29までのデータがあるファイルがあるとする。1日あたり2ページがワンセットなので、計120ページになるのかしら。それを2ページずつに分解し、「2020年01月01日.pdf」「2020年01月02日.pdf」…という同僚の保存名フォーマットに従い保存していく。

pdf_separate.py
import PyPDF2
from datetime import datetime
from datetime import timedelta

f = 'test.pdf' #分解したいpdfファイル
page_sep = 2 #何ページごとに分解するか
start_day = '2020/01/01' #最初のデータの日付

#PDFのページ数の把握
reader = PyPDF2.PdfFileReader(f)
page_num = reader.getNumPages()

#最初の日付をdatetime用に変換
start_datetime = datetime.strptime(start_day, '%Y/%m/%d')

#処理回数をゼロにセット
count = 0

#「0(1ページ目」から総ページ数「page_num」の一個手前まで「page_sep」(2つ刻み)で数字を派生しforで回す
for page in range(0, page_num, page_sep):
    merger = PyPDF2.PdfFileMerger()
    #切り出すページの最初はどこでしょう
    start = page
    #切り出すページの最後はどこでしょう
    end = start + page_sep
    #切り出す
    merger.append(f, pages=(start,end))

    #開始日+処理回数=ファイル名を作り出す処理に入ります
    file_date = start_datetime + timedelta(days=count)
    #日付フォーマットを「2020年01月01日」みたいにして、
    file_date_format = file_date.strftime('%Y{0}%m{1}%d{2}').format(*'年月日')
    #ファイル名を確定
    file_name = file_date_format + '.pdf'
    merger.write(file_name)
    merger.close
    #処理回数を1つカウントします
    count += 1

print ('今晩、呑みに行こうよ')

最初のころ「+=」だっけ「=+」だっけ、と忘れることがあり、彗星(+=)は太陽(東、北から見て左)の方に向かう、と覚えたのはさておき、生成後のフォルダ(上の方は省略)はこうなった。

1/31→2/1の月替わりのところもちゃんと変換された。今年はオリンピックがあるということはうるう年。「2020年02月29日」というファイルもちゃんとできた。これで今晩、同僚と呑みに行けるぞ。

datetimeを使う

日付処理に「datetime」を使っている。Pythonの標準ライブラリなので特にインストールの作業は必要ないおなじみ過ぎるライブラリだが、Pythonを触り始めたころを思い出し、初めて触る人向けにどんなものかわかりやすいように書いてみる。

まず、どういう値が返ってくるかを把握してみよう。たとえば今の日付を取得する場合、

from datetime import datetime

now_date = datetime.now()
print (now_date)
#2020-02-01 11:09:38.124982

という感じ。今まで同僚が保存していたファイル名の日付形式(例:2020年02月01日)にしたい場合はいろいろな方法があり好みは分かれるだろうが、その日付を「.strftime」に渡して、「.format」で定義するのが、私は好き。


from datetime import datetime

now_date = datetime.now()
format_now_date = now_date.strftime('%Y{0}%m{1}%d{2}').format(*'年月日')

print(format_now_date)
#2020年02月02日

これをベースに「2020/02/01」とか「2020-02-01」にしたい場合は、

format_now_date = now_date.strftime('%Y{0}%m{1}%d').format(*'//')
format_now_date = now_date.strftime('%Y{0}%m{1}%d').format(*'--')

とする。「%d{2}」の「{2}」を消し忘れないように。

足したり引いたりする

最初のニッチなプログラムは、指定した開始日(start_day)に処理回数(count)を足していくことでファイル名を生成する仕組み。つまり最初に切り出されたファイルに当てられる日付は「2020/01/01 + 0」で2020年01月01日に、次は「2020/01/01 + 1」で2020年01月02日なるといったぐあい。

Pythonに限らず、プログラミングって基本的な言葉と仕組みでなんとかなるものだが、基準となる日付から足したり引いたりするときは、オブジェクト「timedelta」を使う。月替わりとか、うるう年とかも考慮してくれるありがたいおまじない。たとえば基準日「start_date」から1日後は、

file_date = start_date + timedelta(days= 1)

とする。1日前は「1」のところを「-1」にする。1週間後の場合は「weeks= 1」とすればいい。ということは1年後は「years= 1」で、1カ月後は「months= 1」なのかというと、Pythonを触り始めたころの甘酸っぱい記憶がよみがえる。そう単純ではなく、7種類まで。

意味
weeks=10 10週間後
days=10 10日後
hours=10 10時間後
minutes=10 10分後
seconds=10 10秒後
milliseconds=10 10ミリ秒
microseconds=10 10マイクロ秒後

ミリ秒とかマイクロ秒なんて使うほど繊細な仕事をしていないので、年とか月も用意してほしいな、と思う。

countなんてダサい、ファイルを切り出すスタートページと日付をセットにした多次元配列を生成すればいい、とも思った。面倒だし、あとあとプログラムを改良したいけどPython初心者ですって人が現れたとき、こちらの方が読みやすいかな、というやさしさ。そんな奇特な人、現れないだろうけれど。