正如您将在即将发布的博客文章中看到的那样,我正处于金融知识时代。临近年底,我想看看我的数据:我缴纳了多少税?我随叫随到的轮班赚了多少钱?多个 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 的代码,包括决定报告中需要哪些字段。这是您需要调整以匹配您的工资单结构的部分。一旦弄清楚,我们将迭代整个工资单目录。
在第三步中,我选择在 PDF 和 CSV 之间添加一个额外的步骤 - JSON 报告。一旦我们看到一切正常,我们将删除该文件的使用。
最后,我们会将 JSON 数据转换为 CSV 文件。然后,该 CSV 可以轻松转换为 Google 表格(只需单击“打开方式”)或 Excel(可以在此处找到说明)。
这是一个简单而美好的计划,但你知道它是如何进行的 - 一路上会发现挑战......你能猜出事情可能会变得复杂吗?
在我们开始之前 - 重要提示:保密您的工资单!如果您将项目上传到 GitHub — 请确保不要分享这些个人详细信息!您可以使用 .gitignore 来实现此目的:
/payslips_pdf pdf_rows.txt report.json report.csv
我们开始吧?
我们将从阅读 PDF 并打印所有行开始。这样我们就会知道每一行中出现的内容。这只需要完成一次(而报告可能会每月或每年创建一次),并且它不是报告的一部分 - 因此我们将在单独的文件中创建它。
首先创建一个新的 Python 文件(我将其命名为 pdf_to_txt.py),然后编写一个函数来读取 pdf 并将结果打印到 .txt 文件中:
我们也会在主脚本中读取PDF文件,所以将此功能移到那里会更好。
现在我们知道了 PDF 的读取结构 - 我们可以获取所需的值。就我而言 - 这是我感兴趣的信息:
请注意,表内有数据(每个月可能会有所不同的类别)和表外的数据。
表外数据:
付款期 — 可以在第 19 行找到
总工资 — 这个规则很难找到规则,因为它出现在付款列表之后并且没有标题“总工资”。
如前所述,付款和扣除可能会有所不同,并且并非每个月都相同。因此,不同月份的总工资可能会出现在不同的行中。
我确实注意到它出现在员工姓名之后 - 所以这就是我使用的。首先通过硬编码添加它,然后我们从外部获取它。
Nett Pay:这个很简单 - 它出现在第 17 行。
我将这些表外值收集到一个函数中:
付款和扣款详情:这是最有趣的部分!我们将从剪切行数组开始,以在接下来的 for 循环中节省几毫秒。然后,我需要区分 列表项
和其他行。
我注意到在整个文件中,列表项是唯一符合此规则的项目:以字母字符 开头, 以数字字符 结尾和包含空格(最后一个条件是过滤掉my
中错误的行工资单,你可能不需要它)。
例如我们来看养老金项目:
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 对象而不导出值。我更喜欢看到它的样子,至少在编码阶段是这样。
缩放至多个 PDF 文件
原本,我以为我必须重命名文件(Payslip1.pdf -> Payslip01.pdf),但有一个更好的解决方案:
由于付款和扣除项目可能因工资单而异,因此本节不仅仅是直接翻译。 CSV 是一个关系数据集,这意味着我们需要提前了解付款和扣除的所有类别,并在不存在工资单的情况下将条目保留为空。另一方面,JSON 是非关系型的,每个条目都指定其键。
考虑到这一点,我们的 CSV 报告的第一步是收集类别。所有类别。
收集类别:
现在,乍一看,您可能会认为使用 Set 来实现这一点 - 因为我们希望所有类别只出现一次。我已经尝试过了。问题是套装未列出,我发现匹配原始工资单中出现的项目顺序很重要。使用列表时,不要忘记在追加之前检查列表中是否存在该项目:
必须
分开,但我希望工资单报告将所有付款放在右侧,所有扣除放在左侧,而不是混合。
最后,我返回一个列表,因为将付款与扣除分开是没有用的 - 分开是为了确保付款将显示在右侧,扣除将显示在左侧。
填充 CSV 表:
现在我们有了类别,我们可以开始填充 CSV 表:
您可以通过下载 VS 扩展 RainbowCSV(或任何其他 IDE 的并行版本)来使其更易于阅读
一旦我们知道一切正常,我们就不需要写入和读取 JSON 文件 - 我们可以直接使用 JSON 对象:
我们将直接使用 json_payslips,而不是使用 json_object(如 json_object = json.dumps(json_payslips)):
写入:无需写入report.json - 我们可以删除此部分。
阅读:将 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。否则,你会得到一个错误:
不带引号的员工姓名
您可以跳过引号要求并循环参数,收集用户名的所有部分 - 但我发现这种方法增加了不必要的复杂性。
员工的名字没有出现在工资单上
如果我们没有工资单上显示的员工姓名,我们将无法找到总工资。
最后,我们在主函数中捕获错误:
以下是您可用于处理工资单的完整代码:
https://cupofcode.blog/ |
以上是周末编码:将 PDF 工资单转换为单个 CSV 报告的详细内容。更多信息请关注PHP中文网其他相关文章!