如何避免Python PDF解析代码中因表结构不匹配而出现重复?

How to avoid duplication in Python PDF parsing code for mismatching table structures?

提问人:Pablo Martín Calvo 提问时间:5/26/2023 最后编辑:jquriousPablo Martín Calvo 更新时间:6/30/2023 访问量:39

问:

我有 100 多个 PDF 是匹配报告,我想从中抓取数据,以便将其存储在数据帧中,以便以后可以使用它。 问题是:这些 PDF 并不总是具有相同的结构,并且从 pdfplumber 读取的表格中的行长度不同,因此几乎不可能不为每种类型的行重复多次代码。 我想找到一种方法来使我的代码更漂亮、更易于阅读和调试。

这是我正在为每个 PDF 阅读的表格:其中一个 PDF 的摘录

我需要从每个 PDF 的两列中获取数据。

这是我从 PDF 中提取表格的代码

directory = os.fsencode('')
matchs_raw = {}
for file in os.listdir(directory):
    
    filename = os.fsdecode(file)
    if '.pdf' not in filename:
        continue
    matchs_raw[filename] = []

    with pdfplumber.open(f'\\{filename}') as pdf:
        for page in pdf.pages:
            tables = page.extract_tables()
            for table in tables:
                for i in table:
                    matchs_raw[filename].append(i)

它正确地存储了我想要的所有数据,每个 PDF 有一个键,所有表中的每一行都是列表的一个元素,这是键的值,matchs_raw。

然后,我尝试从matchs_raw中提取相关数据,将其存储在 pandas DataFrame 中。 我设法用如下所示的代码来做到这一点:

columns = ['file_name','minute','role','numero','nom','recevant_ou_visiteur','equipe',
'description','score_1_ponctuel','score_2_ponctuel']

data = {col: [] for col in columns}

for match in tqdm(matchs_raw):
    if matchs_raw[match][0][0]== "Organisateur":
        for l in range(len(matchs_raw[match])):
            if matchs_raw[match][l][0]=="Déroulé du Match" or matchs_raw[match][l][0]=="DérouléduMatch":
                starting_row = l+3
                break
        for j in matchs_raw[match][starting_row:]:
            
            if len(j)!=13:
                ## LEFT COLUMN
                
                data['file_name'].append(matchs_raw[match][0][20])
                
                try:
                    data['minute'].append(j[0])
                except:
                    data['minute'].append(np.nan)
                try:
                    data['role'].append(re.findall("JR|JV|OR|OV",j[3])[0][0])  
                except:
                    data['role'].append(np.nan)
                
                try:
                    if re.findall("JR|JV|OR|OV",j[3])[0][0] == "J":
                        try:
                            data['numero'].append(re.findall("N[^\x00-\x7F]+\d*",j[3])[0])
                        except:
                            data['numero'].append(np.nan)
                        try:
                            data['nom'].append(re.findall("N[^\x00-\x7F]+\d*(\D+)",j[3])[0])
                        except:
                            data['nom'].append(np.nan)
                        try:
                            data['description'].append(re.findall("(.+?)(JR|JV|OR|OV)N[^\x00-\x7F]",j[3])[0][0])
                        except:
                            data['description'].append(np.nan)
                    
                    else:
                        try:
                            data['numero'].append("Officiel")
                        except:
                            data['numero'].append(np.nan)
                        try:
                            data['nom'].append(re.findall("^(.+?)(OV|OR)(.+)",j[3])[0][2].strip())
                        except:
                            data['nom'].append(np.nan)
                        try:
                            data['description'].append(re.findall("^(.+?)(OV|OR)",j[3])[0][0].strip())
                        except:
                            data['description'].append(np.nan)

对于这种特殊情况,它继续一点点(type==“Organisateur”, len(j)!=13, left column)。 我必须对左列和右列的 len(j)==13 以及另一种具有 3 个不同 len 大小写的 pdf 做同样的事情。 for 循环中 j 的索引相互尊重没有任何意义(例如,和 之间并不总是有 3 个级别的差异。data['minute'].append(j[0])data['role'].append(re.findall("JR|JV|OR|OV",j[3])[0][0])

对于如何避免对每种情况重复所有这些 try/except 块,您有什么建议吗?我们将不胜感激。

谢谢!

python for-loop pdf-解析 pdfplumber

评论

0赞 TheTridentGuy supports Ukraine 5/26/2023
一套?w3schools.com/python/python_sets.asp
0赞 Pablo Martín Calvo 5/26/2023
您是否建议我修改代码的第一部分,而不是存储列表,我存储集合,看看这是否允许我更好地检索数据?
0赞 Pablo Martín Calvo 5/26/2023
我认为这行不通,我需要对元素进行排序,因为我需要知道,在一行中,每个动作对应的时间。
0赞 Pablo Martín Calvo 5/26/2023
@TheTridentGuy我喜欢你的想法。尽管集合不起作用,因为它们是无序的,而且我需要知道哪些分钟与哪些操作相关,但我考虑过获得唯一列表,其中我删除了所有空白单元格,这样我就得到了一个更一致的结构。我会努力的。

答:

0赞 jqurious 6/30/2023 #1

您似乎正在解析这些 PDF 文件: https://www.ffhandball.fr/api/s3/fdm/O/A/C/P/OACPSGG.pdf

似乎您可以使用标题来标识列:

temps = page.search(r'Temps (?=Score Action)')

vlines = sorted(set(t['x0'] for t in temps)) + [ page.bbox[-2] ]

im = page.to_image(300)
im.reset().draw_vlines(vlines, stroke_width=10, stroke='black')

im.save('tbl.png')

enter image description here

然后,您可以输出每列:.crop()

enter image description here

您可以在每列中标题的第一个实例 (Temps, Score, Action)

for left, right in itertools.pairwise(vlines):
    crop = page.crop((left, 0, right, page.bbox[-1]))
    left, top, right, bottom = crop.bbox
    
    top = crop.search('Temps Score Action')[0]['top']
    crop = crop.crop((left, top, right, bottom))
    
    print(pl.DataFrame(crop.extract_table(), orient='row'))
shape: (64, 4)
┌──────────┬──────────┬──────────┬───────────────────────────────────┐
│ column_0 ┆ column_1 ┆ column_2 ┆ column_3                          │
│ ---      ┆ ---      ┆ ---      ┆ ---                               │
│ str      ┆ str      ┆ str      ┆ str                               │
╞══════════╪══════════╪══════════╪═══════════════════════════════════╡
│ Temps    ┆ Score    ┆ Action   ┆ null                              │
│ 00:57    ┆ 01 - 00  ┆          ┆ But JR N°44 DEMBELE sitha lauree… │
│ 02:24    ┆ 01 - 00  ┆          ┆ Tir JR N°28 BALLUREAU lea         │
│ 02:29    ┆ 01 - 00  ┆          ┆ Arrêt JV N°12 SCHAMBACHER laura   │
│ …        ┆ …        ┆ …        ┆ …                                 │
│ 25:23    ┆ 16 - 07  ┆          ┆ Arrêt JR N°16 PORTES laura        │
│ 25:32    ┆ 16 - 07  ┆          ┆ Tir JR N°15 AUGUSTINE anne-emman… │
│ 25:35    ┆ 16 - 07  ┆          ┆ 2MN JR N°2 JACQUES emma           │
│          ┆ null     ┆ null     ┆ null                              │
└──────────┴──────────┴──────────┴───────────────────────────────────┘
shape: (67, 4)
┌──────────┬──────────┬──────────┬───────────────────────────────────┐
│ column_0 ┆ column_1 ┆ column_2 ┆ column_3                          │
│ ---      ┆ ---      ┆ ---      ┆ ---                               │
│ str      ┆ str      ┆ str      ┆ str                               │
╞══════════╪══════════╪══════════╪═══════════════════════════════════╡
│ Temps    ┆ Score    ┆ Action   ┆ null                              │
│ 25:50    ┆ 16 - 08  ┆          ┆ But JV N°10 SAID AHMED anais      │
│ 26:28    ┆ 16 - 08  ┆          ┆ Tir JR N°47 DEMBELE mahoua-audre… │
│ 26:30    ┆ 16 - 08  ┆          ┆ Arrêt JV N°61 NAILI yousra        │
│ …        ┆ …        ┆ …        ┆ …                                 │
│ 49:05    ┆ 28 - 17  ┆          ┆ Tir JV N°11 BROUTIN auriane       │
│ 49:09    ┆ 28 - 17  ┆          ┆ Arrêt JR N°16 PORTES laura        │
│ 49:41    ┆ 28 - 17  ┆          ┆ Tir JR N°70 LE BLEVEC julie       │
│ 51:00    ┆ 28 - 18  ┆          ┆ But JV N°9 DIEYE eloise           │
└──────────┴──────────┴──────────┴───────────────────────────────────┘

您可以进一步优化它,但这应该允许您将所有列堆叠在一起以简化分析。