ホームページ >バックエンド開発 >Python チュートリアル >週末コーディング: PDF 給与明細を単一の CSV レポートに変換
今後のブログ投稿でわかるように、私は金融リテラシーの時代にいます。年末が近づいてきたので、自分の数字を確認したいと思いました。自分はいくら税金を支払ったのでしょうか?オンコールシフトの収入はいくらですか?複数の PDF ファイルはこのデータを表示するのに最も快適な方法ではありません。Excel で操作できる単一の CSV ファイルが必要でした。
多くの優秀な開発者と同じように、私も数字を手動で挿入するのが面倒だったので、スクリプトを書きました。プログラミングが好きなら、私と一緒に冒険に出かけましょう!気分が良くない場合は、給与明細の構造に合わせてコードを調整する方法を説明します :D
This script receives a directory with payslip PDFs and returns a CSV file with the desired data |
main.py: # translate 1 pdf to 1 dict # loop over the pdf dir # save all dicts to 1 json file # translate json report to csv report
まず、レポートにどのフィールドが必要かを決定するなど、PDF を読み取るコードを記述します。これは、給与明細の構造に合わせて調整する必要がある部分です。それがわかったら、給与明細ディレクトリ全体を繰り返し処理します。
3 番目のステップでは、PDF と CSV の間に追加のステップ、つまり JSON レポートを追加することにしました。すべてが機能することが確認できたら、そのファイルの使用を削除します。
最後に、JSON データを CSV ファイルに変換します。その CSV は、Google スプレッドシート ([開く] をクリックするだけ) または Excel (手順はこちら) に簡単に変換できます。
それは簡単で素晴らしい計画ですが、どのように進むかはご存知です — 途中で課題が発見されます…どこで物事が複雑になるかわかりますか?
始める前に - 重要な注意事項: 給与明細は非公開にしてください!プロジェクトを GitHub にアップロードする場合は、個人情報を共有しないようにしてください。これには .gitignore を使用できます:
/payslips_pdf pdf_rows.txt report.json report.csv
始めましょうか?
まず、PDF を読み取り、すべての行を印刷します。こうすることで、各行に何が表示されているかがわかります。これは 1 回だけ行う必要があります (レポートはおそらく月に 1 回または年に 1 回作成されます)。これはレポートの一部ではないため、別のファイルに作成します。
まず、新しい Python ファイル (私は pdf_to_txt.py と呼びました) を作成し、pdf を読み取り、結果を .txt ファイルに出力する関数を作成します。
メイン スクリプトでも PDF ファイルを読み取るため、この関数をそこに移動する方が良いでしょう。
PDF が読み取られる構造がわかったので、必要な値を取得できます。私の場合、私が興味を持った情報は次のとおりです:
テーブル内にデータ (毎月異なる可能性のあるカテゴリ) とテーブルの外にデータがあることに注意してください。
テーブル外のデータ:
支払い期間 — 行 19
にあります総支払額 — これは支払いリストの後に表示され、「総支払額」というタイトルがついていないため、ルールを見つけるのが困難でした。
前述したように、支払いと控除は変動する可能性があり、毎月同じであるわけではありません。したがって、総支払額は月ごとに異なる行に表示される場合があります。
これが従業員名の直後にあることに気付きました。それで、それを使用しました。まずハードコーディングして追加し、後で外部から取得します。
Nett Pay: これは簡単です。17 行目に表示されます。
これらの表外の値を関数に集めました:
支払いと控除の詳細: ここが重要な部分です。まず、行配列をカットして、今後の for ループで数ミリ秒を節約します。次に、リスト項目
と他の行を区別する必要がありました。
ファイル全体の中で、このルールに一致するのはリスト項目だけであることに気付きました: 英字 で始まり、 数字 で終わる と にはスペースが含まれています (最後の条件は、間違った行をフィルターで除外することです) 私の
給与明細、必要ないかもしれません)。
たとえば、年金項目を見てみましょう:
main.py: # translate 1 pdf to 1 dict # loop over the pdf dir # save all dicts to 1 json file # translate json report to csv report
残高 (右側の数字) は気にしませんが、コードは気にします (G は、税引き前の総給与から差し引かれることを意味します) — そして N は、税引後の純額から差し引かれることを意味します)。したがって、理想的には、 json_obj["Pension (G)"]=150.00 になります。
スペースを使用して行を分割します。スペースが重複しているのは良いことです。そうすることで、いくつかの単語間のスペース分割と、いくつかのフィールド間のスペース分割を区別できます。
説明:
最初のダブルスペースを見つけて、それによって分割します。
コード:
スペースの量は説明の長さに依存するため、スペースの数を事前に知ることはできません。そのため、lstrip() も使用します。行の残りの部分はスペース以外の文字で始まります。
すべてのリスト項目にコードがあるわけではないため、行がコードで始まるか数字で始まるかを確認する必要があります。コードの場合は、() (左括弧の前のスペースを含む) で囲み、説明文字列に付加します。そうでない場合は、何も追加しません。
金額:
コードがあった場合、削除するスペースがさらに多くなります。そうでない場合、明細行には月次と残高という 2 つの金額が含まれる可能性があります。
私が気づいたケースは 4 つあります:
/payslips_pdf pdf_rows.txt report.json report.csvカテゴリとコードを抽出すると、次のものが残ります:
PENSION G 150.00 587.49ケース 2 ~ 3 をカバーするために、金額を区切るスペースのインデックスを見つけて、末尾をカットします。これは、スペースがない (別名テールなし) という最初のケースにも機能します。
ケース 4 をカバーするために、行に 1 つの金額が含まれる 2 つのタイプのカテゴリの違いに依存しています。最初のタイプは給与のようなもので、金額を保存したい場所であり、2 番目のタイプは次のようなものです。源泉徴収税は無視したいものです。違いは、表の年間残高を追跡するのは控除のみであることです。したがって、私は - をチェックしています。
すべてをまとめると次のようになります:
これは必須の手順ではありません。値をエクスポートせずに JSON オブジェクトを操作できます。少なくともコーディング段階では、それがどのようなものかを確認することを好みます。
このステップに専用のセクションが設けられている唯一の理由は、pdf_to_dict を for ループでラップすると不快な驚きが明らかになるからです。それを実証するために、iterate_over_pdfs():
当初、ファイルの名前を変更する必要があると考えていました (Pay Slip1.pdf -> Pay Slip01.pdf) が、より良い解決策があります:
支払いと控除の項目は給与明細によって異なる場合があるため、このセクションは単なる直訳ではありません。 CSV はリレーショナル データセットです。つまり、支払いと控除のすべてのカテゴリを事前に把握し、給与明細が存在しない場合はエントリを空にしておく必要があります。一方、JSON は非リレーショナルであり、各エントリはそのキーを指定します。
これを念頭に置いて、CSV レポートの最初のステップはカテゴリを収集することです。すべてのカテゴリ。
カテゴリを収集します:
一見すると、そのために Set を使用することを考えるかもしれません。なぜなら、すべてのカテゴリを 1 回だけ表示したいからです。それを試してみました。これの問題は、セットがリストに掲載されていないことであり、元の給与明細に表示される項目の順序と一致させることが重要であることがわかりました。リストを使用する場合は、項目を追加する前にリストに項目が存在するかどうかを必ず確認してください:
必要はありません
が、給与明細報告書には、右側にすべての支払いと左側にすべての控除が混在せずに記載されることを期待します。
最後に、単一のリストを返します。支払いと控除を分離しても意味がないためです。この分割は、支払いが右側に表示され、控除が左側に表示されるようにするためのものです。
CSV テーブルにデータを入力します:
カテゴリーができたので、CSV テーブルへの入力を開始できます。
VS 拡張機能 RainbowCSV (または別の IDE の類似物) をダウンロードすると、読みやすくなります
一度動作することがわかったら、JSON ファイルへの書き込みや読み取りを行う必要はありません。JSON オブジェクトを直接使用できます。
json_object (json_object = json.dumps(json_paylips) のように) を使用する代わりに、json_paylips を直接使用します。
書き込み: report.json に書き込む必要はありません。このセクションは削除できます。
読み取り: json_paylips を json_to_csv() 関数に直接渡します:
スクリプトの準備ができたら、同僚や友人と共有したくなるでしょう。優れたユーザー エクスペリエンスを実現するために、従業員にコードを開いてもらうのではなく、コマンド ラインから従業員名をエクスポートします。
引数の読み取り
ユーザーが従業員名を入力したと仮定して、ハッピー パスから始めて、それを使用するコードを追加します。
pdf_to_dict() では、EMPLOYEE_NAME = "IFAT NEUMANN" をハードコーディングする代わりに、引数から読み取ります:employee_name = sys.argv[1]。 sys をインポートすることを忘れないでください!
次に、他のシナリオについて考えてみましょう:
従業員名が指定されていません
ユーザーが従業員名を入力しなかったらどうなるでしょうか?できるだけ早く見つけて、彼らに通知したいと思います!
そこで、main関数の1行目にチェックを追加します。ここで、直感的には、そこでemployee_name変数を初期化することになりますが、これにより、この変数を使用する関数に到達するまで関数のプロパティが泡立つことになります。そして、私はそれがあまりきれいなアプローチとは思えません。
最後に、このフィールドにアクセスしてみます。フィールドが存在しない場合はキャッチします。
main.py: # translate 1 pdf to 1 dict # loop over the pdf dir # save all dicts to 1 json file # translate json report to csv report
例外を追加するということは、print_warning() 関数が main.py に移動することを意味することに注意してください。そうしないと、エラーが発生します。
引用符なしの従業員名
引用符の要件をスキップし、引数をループしてユーザー名のすべての部分を収集することもできますが、このアプローチでは不必要な複雑さが追加されることがわかりました。
給与明細に従業員の名前が記載されていない
給与明細に記載されている従業員の名前がないと、総給与を見つけることができません。
最後に、メイン関数でエラーをキャッチします。
給与明細の操作に使用できる完全なコードは次のとおりです:
https://cupofcode.blog/ |
以上が週末コーディング: PDF 給与明細を単一の CSV レポートに変換の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。