首頁 >後端開發 >Python教學 >週末編碼:將 PDF 薪資單轉換為單一 CSV 報告

週末編碼:將 PDF 薪資單轉換為單一 CSV 報告

Susan Sarandon
Susan Sarandon原創
2024-12-25 22:20:17125瀏覽

曾經使用 PDF 檔案進行程式設計嗎?跟我一起寫一個Python腳本吧!

Weekend Coding: Turn PDF Payslips Into a Single CSV Report

正如您將在即將發布的部落格文章中看到的那樣,我正處於金融知識時代。接近年底,我想看看我的數據:我繳了多少稅?我隨時待命的輪班賺了多少錢?多個 PDF 檔案並不是查看這些數據的最舒適方式,我想要一個可以在 Excel 中使用的單一 CSV 檔案。

像許多優秀的開發人員一樣,我懶得手動插入數字,所以我寫了一個腳本。如果你喜歡程式設計——和我一起冒險吧!如果您沒有心情 — 我將向您展示如何調整程式碼以符合您的薪資結構:D

Weekend Coding: Turn PDF Payslips Into a Single CSV Report
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 的程式碼,包括決定報告中需要哪些欄位。這是您需要調整以符合您的薪資結構的部分。一旦弄清楚,我們將迭代整個薪資目錄。

在第三步驟中,我選擇在 PDF 和 CSV 之間新增一個額外的步驟 - JSON 報告。一旦我們看到一切正常,我們將刪除該檔案的使用。

最後,我們會將 JSON 資料轉換為 CSV 檔案。然後,該 CSV 可以輕鬆轉換為 Google 試算表(只需按一下「開啟方式」)或 Excel(可在此處找到說明)。

這是一個簡單而美好的計劃,但你知道它是如何進行的 - 一路上會發現挑戰......你能猜出事情可能會變得複雜嗎?

在我們開始之前 - 重要提示:保密您的薪資單!如果您將專案上傳到 GitHub — 請確保不要分享這些個人詳細資訊!您可以使用 .gitignore 來實現此目的:

/payslips_pdf
pdf_rows.txt
report.json
report.csv

我們開始吧?

Weekend Coding: Turn PDF Payslips Into a Single CSV Report

閱讀PDF文件

我們將從閱讀 PDF 並列印所有行開始。這樣我們就會知道每一行出現的內容。這只需要完成一次(而報告可能會每月或每年創建一次),並且它不是報告的一部分 - 因此我們將在單獨的文件中創建它。

先建立一個新的 Python 檔案(我將其命名為 pdf_to_txt.py),然後編寫一個函數來讀取 pdf 並將結果列印到 .txt 檔案:



這可行,但有 3 個更改使其更加用戶友好:
  • 取得檔案路徑作為命令列參數,以便使用者無需接觸程式碼即可運行它。
  • 加入一些錯誤捕獲指令,以防使用者執行錯誤的命令。 (我添加了警告顏色,但你不必這樣做)

  • 我們也會在主腳本中讀取PDF文件,所以將此功能移到那裡會更好。

試一試吧!嘗試使用正確和錯誤的參數運行命令:)


當您執行 py ./main.py 時,您將在專案目錄中看到一個名為 pdf_rows.txt 的新檔案。
<script></script> <script></script> <script></script>
(別擔心,這不是我的數據——它是基於下圖中的工資單示例)

處理PDF數據

現在我們知道了 PDF 的讀取結構 - 我們可以取得所需的值。就我而言 - 這是我感興趣的訊息:

Weekend Coding: Turn PDF Payslips Into a Single CSV Report

請注意,表內有資料(每個月可能會有所不同的類別)和表外的資料。

表外資料:

付款期間 — 可以在第 19 行找到

總工資 — 這個規則很難找到規則,因為它出現在付款清單之後並且沒有標題「總工資」。

如前所述,付款扣除可能會有所不同,並且並非每個月都相同。因此,不同月份的總工資可能會出現在不同的行中。

我確實注意到它出現在員工姓名之後 - 所以這就是我使用的。首先將其硬編碼添加,稍後我們將從外部獲取它。

Nett Pay:這個很簡單 - 它出現在第 17 行。

我將這些表外值收集到一個函數中:


表格內的數據

付款和扣款詳情:這是最有趣的部分!我們將從剪切行數組開始,以在接下來的 for 循環中節省幾毫秒。然後,我需要區分 列表項目

和其他行。

我注意到在整個文件中,列表項目是唯一符合此規則的項目:以字母字元 開頭, 以數字字元 結尾包含空格(最後一個條件是過濾掉my
中錯誤的行工資單,你可能不需要它)。

<script></script> <script></script>
現在我們有了這組行,讓我們處理它們以保存在 JSON 物件中。此時,我們不介意是付款還是扣除。

例如我們來看退休金項目:

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() 的原因。現在該行的其餘部分以非空格字元開頭。

並非所有清單項目都有程式碼,因此我們要檢查該行是否以程式碼或數字開頭。如果它是代碼 - 我將其包裝在 () 中(包括左括號前的空格),並將其附加到描述字串。如果沒有 - 不添加任何內容。

金額:

如果有程式碼-我們就會有更多的空間可以刪除。如果沒有,我們的行可能包含兩個金額:每月金額和餘額。

我注意到了 4 個案例:

/payslips_pdf
pdf_rows.txt
report.json
report.csv

提取類別和代碼後,我們剩下:

PENSION     G   150.00   587.49

為了涵蓋情況 2-3,我們將找到分隔金額的空格索引並剪掉尾部。它也適用於第一種情況,即沒有空格(也稱為沒有尾部)。

為了涵蓋案例4,我依賴行中具有單一金額的兩種類型類別之間的差異:第一種類型就像工資- 我們要在其中保存金額,第二種類型就像預扣稅——我們想忽略它。不同之處在於,只有扣除額會記錄表中的年度餘額 - 所以我正在檢查 -.

總而言之,這就是它的樣子:

寫入 JSON 文件


這不是強制性步驟 - 我們可以使用 JSON 物件而不匯出值。我更喜歡看到它的樣子,至少在編碼階段是這樣。

縮放至多個 PDF 文件

此步驟獲得專用部分的唯一原因是因為將 pdf_to_dict 包裝在 for 循環中會帶來令人不快的驚喜。為了示範它,我建立了一個名為 iterate_over_pdfs() 的函式:
<script></script> <script></script> <script></script>
發生這種情況是因為文件清單是按字母順序排序的,因此 10 出現在 2 之前。按時間順序排列報告條目可以被認為是至關重要的,而不僅僅是一個可有可無的功能。因此,我們需要修復它!

原本,我以為我必須重新命名檔案(Payslip1.pdf -> Payslip01.pdf),但有一個更好的解決方案:


一旦我們按長度對檔案名稱清單進行排序,10 將出現在 2 之後。在最後一行,我對名稱進行了解碼以去掉 b''預設結構。

建立 CSV 報告

由於付款和扣除項目可能因工資單而異,因此本節不僅僅是直接翻譯。 CSV 是一個關係資料集,這意味著我們需要提前了解付款和扣除的所有類別,並在不存在工資單的情況下將條目保留為空。另一方面,JSON 是非關係型的,每個條目都指定其鍵。

考慮到這一點,我們的 CSV 報告的第一步是收集類別。所有類別。

收集分類:


現在,乍一看,您可能會認為使用 Set 來實現這一點 - 因為我們希望所有類別只出現一次。我已經嘗試過了。問題是套裝未列出,我發現匹配原始薪資單中出現的項目順序很重要。使用清單時,不要忘記在追加之前檢查清單中是否存在該項目:


現在我們已經弄清楚了:還記得之前我們說過我們不關心哪個項目清單是付款、哪個是扣除嗎?好吧,我們現在很關心!我們不必

必須
分開,但我希望薪資報告將所有付款放在右側,所有扣除額放在左側,而不是混合。

雖然每張薪資單可能有不同的清單項目 - 有些將永遠存在(因為你總是會繳稅;))。我們可以利用這一點為我們帶來好處——並將 PAYE 標記為扣除的開始! (我很確定 PAYE 僅適用於愛爾蘭,因此您需要更改它以匹配您的工資單)

最後,我返回一個列表,因為將付款與扣除分開是沒有用的 - 分開是為了確保付款將顯示在右側,扣除將顯示在左側。

填充 CSV 表:


現在我們有了類別,我們可以開始填充 CSV 表:

每張薪資單都是一行,每行都有以逗號分隔的特定順序的欄位。我發現將字段組織在列表中然後將它們連接起來更容易。出現在類別中但未出現在薪資單中的欄位 — 將保留為空白:


<script></script> 最後,我們將寫入 CSV 檔案:<script></script> <script></script> <script></script> <script></script>
之後,您將獲得一份包含所有工資單的精美 CSV 報告!

您可以透過下載 VS 擴充 RainbowCSV(或任何其他 IDE 的平行版本)來使其更易於閱讀

刪除 .json 檔案的使用

一旦我們知道一切正常,我們就不需要寫入和讀取 JSON 檔案 - 我們可以直接使用 JSON 物件:

我們將直接使用 json_payslips,而不是使用 json_object(如 json_object = json.dumps(json_payslips)):

  1. 寫入:無需寫入report.json - 我們可以刪除此部分。

  2. 閱讀:將 json_payslips 直接傳遞給 json_to_csv() 函數:

取得員工的姓名作為參數

一旦您準備好腳本 - 您就會想與您的同事和朋友分享!為了獲得良好的使用者體驗,我們將從命令列匯出員工姓名,而不是要求他們開啟程式碼。

閱讀論證

我們將從快樂路徑開始 - 假設使用者輸入員工姓名 - 並新增使用它的程式碼:

在 pdf_to_dict() 中,我們將從參數中讀取它:employee_name = sys.argv[1],而不是硬編碼 EMPLOYEE_NAME = "IFAT NEUMANN"。不要忘記導入 sys!

現在我們會考慮其他場景:

未提供員工姓名

如果使用者沒有輸入任何員工姓名怎麼辦?我們希望盡快抓住它,並通知他們!

因此,我們將在 main 函數的第一行新增一個檢查。現在,直覺是在那裡初始化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。否則,你會得到一個錯誤:

不帶引號的員工姓名


我們要求使用者用引號將名稱括起來,因為命令列參數是用空格分隔的。我們期望的唯一參數是員工姓名,因此如果有另一個參數 - 我們知道他們沒有使用引號,我們可以通知他們:



您可以跳過引號要求並循環參數,收集用戶名的所有部分 - 但我發現這種方法增加了不必要的複雜性。

員工的名字沒有出現在薪資單上


如果我們沒有薪資單上顯示的員工姓名,我們將無法找到總薪資。

最早發現不符的地方是我們閱讀 pdf 時。因此,我們在 pdf_to_dict() 函數的開頭新增檢查:
<script></script> <script></script> <script></script> <script></script>
請注意,我將employee_name = sys.argv[1] 移至此函數,因為它更易於閱讀(而不是如果sys.argv[1] 不在文字中)。之後,我將其傳遞給 get_fixed_values()。

最後,我們在主函數中捕獲錯誤:


總而言之,不要忘記使用新說明更新您的自述文件。

完整腳本


以下是您可用於處理薪資單的完整代碼:


Weekend Coding: Turn PDF Payslips Into a Single CSV Report
https://cupofcode.blog/
希望你喜歡這個!對另一個週末編碼專案有興趣嗎?查看我的自動電子郵件發送部落格文章!

寫部落格是我的愛好,所以我很樂意在上面花時間和金錢。如果您喜歡這篇博文,請在我的小費罐裡放入 1 歐元即可讓我知道:) 感謝您的支持! <script></script> <script></script>

以上是週末編碼:將 PDF 薪資單轉換為單一 CSV 報告的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn