解析带有rowspan和colspan的表格

15
我有一个需要解析的表格,具体来说,它是一份包含4个时间块和每周5天的学校课程表。我已经尝试过解析它,但由于无法处理rowspan和colspan属性,而这些属性会导致我无法获取所需的数据,因此我目前进展缓慢。
以下是我想要做的事情的示例表格:
<tr>
    <td colspan="2" rowspan="4">#1</td>
    <td rowspan="4">#2</td>
    <td rowspan="2">#3</td>
    <td rowspan="2">#4</td>
</tr>

<tr>
</tr>

<tr>
    <td rowspan="2">#5</td>
    <td rowspan="2">#6</td>
</tr>

<tr>
</tr>

我想将那个表格转换成这个列表:

[[1,1,2,3,4],
 [1,1,2,3,4],
 [1,1,2,5,6],
 [1,1,2,5,6]]

目前我得到的是一个扁平列表,类似于这样:

[1,2,3,4,5,6]

但在字典形式中,需要包含有关其跨越多少列和行、描述以及所属周数的信息。

显然,这需要适用于跨度/列跨度的每种可能性,并且适用于同一表中的多个周。

HTML并不像我描绘的那么简洁,其中有很多属性被省略了,而且文本显然不是像1,2,3,4那样清晰明了,而是由一块块的描述性文本组成。但如果我能解决这个问题,就可以很容易地将其合并到我已经编写的内容中。

我一直在使用lxml.html和Python来实现这一点,但如果有其他模块提供更简单的解决方案,我也愿意尝试。

我希望有人能帮帮我,因为我真的不知道该怎么办。

编辑:

<table>
    <tr>
        <td> </td>
        <td> </td>
        <td> </td>
        <td rowspan="4">Thing</td>
        <td> </td>
    </tr>
    <tr>
        <td> </td>
        <td> </td>
        <td> </td>
        <td> </td>
    </tr>
    <tr>
        <td> </td>
        <td> </td>
        <td> </td>
        <td> </td>
    </tr>
    <tr>
        <td> </td>
        <td> </td>
        <td> </td>
        <td> </td>
    </tr>
</table>
这给我带来了一些问题,这会输出
[' ', ' ', ' ', 'Thing', ' ']
[' ', ' ', ' ', ' ', ' ']
[' ', ' ', ' ', ' ', ' ']
[' ', ' ', ' ', ' ', ' ']

使用reclosedev提供的代码,我需要做哪些修改才能使其输出

[' ', ' ', ' ', 'Thing', ' ']
[' ', ' ', ' ', 'Thing', ' ']
[' ', ' ', ' ', 'Thing', ' ']
[' ', ' ', ' ', 'Thing', ' ']

相反的选择?

编辑2:使用reclosedev的新函数,它接近一个解决方案,但仍有一些情况下无法正确放置单元格:

<table> 
    <tr>
        <td> </td>
        <td rowspan="2"> DMAT Aud. 6 </td>
        <td rowspan="4"> Exam</td>
        <td rowspan="2"> DMAT Aud. 7</td>
        <td> </td>
    </tr>
    <tr>
        <td> </td>
        <td rowspan="2"> CART Aud. 4</td>
    </tr>
    <tr>
        <td> </td>
        <td rowspan="2"> CART Aud. 4</td>
        <td rowspan="2"> OOP Aud. 7</td>
    </tr>
    <tr>
        <td> </td>
        <td> </td>
    </tr>
</table> 

有了这个,原始表格就会显示如下:

[
[' ', ' DMAT Aud. 6 ', ' Exam', ' DMAT Aud. 7', ' '],
[' ', ' DMAT Aud. 6 ', ' Exam', ' DMAT Aud. 7', ' CART Aud. 4'],
[' ', ' CART Aud. 4' , ' Exam', ' OOP Aud. 7' , ' CART Aud. 4'],
[' ', ' CART Aud. 4' , ' Exam', ' OOP Aud. 7' , ' ']
]

但是新的调用输出结果如下:

[
[' ', ' DMAT Aud. 6 ', ' Exam', ' DMAT Aud. 7', ' '],
[' ', ' DMAT Aud. 6 ', ' Exam', ' DMAT Aud. 7', ' CART Aud. 4'],
[' ', ' CART Aud. 4' , ' Exam', ' CART Aud. 4', ' OOP Aud. 7'],
[' ', ' CART Aud. 4' , ' Exam', ' OOP Aud. 7' , ' ']
]

3
请提供您当前正在使用的代码和实际得到的输出,而不是类似于您得到的输出的其他输出。这将极大地帮助我们理解问题并提供更好的帮助。 - Nolen Royalty
2个回答

13

更新(删除了之前的函数)

更新2已修复并简化。

我的第一个函数是错误的。这里有另一个函数,它可以工作但需要测试:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from collections import defaultdict


def table_to_list(table):
    dct = table_to_2d_dict(table)
    return list(iter_2d_dict(dct))


def table_to_2d_dict(table):
    result = defaultdict(lambda : defaultdict(unicode))
    for row_i, row in enumerate(table.xpath('./tr')):
        for col_i, col in enumerate(row.xpath('./td|./th')):
            colspan = int(col.get('colspan', 1))
            rowspan = int(col.get('rowspan', 1))
            col_data = col.text_content()
            while row_i in result and col_i in result[row_i]:
                col_i += 1
            for i in range(row_i, row_i + rowspan):
                for j in range(col_i, col_i + colspan):
                    result[i][j] = col_data
    return result


def iter_2d_dict(dct):
    for i, row in sorted(dct.items()):
        cols = []
        for j, col in sorted(row.items()):
            cols.append(col)
        yield cols


if __name__ == '__main__':
    import lxml.html
    from pprint import pprint

    doc = lxml.html.parse('tables.html')
    for table_el in doc.xpath('//table'):
        table = table_to_list(table_el)
        pprint(table)

tables.html:

<table border="1">
    <tr>
        <td>1 </td>
        <td>1 </td>
        <td>1 </td>
        <td rowspan="4">Thing</td>
        <td>1 </td>
    </tr>
    <tr>
        <td>2 </td>
        <td>2 </td>
        <td>2 </td>
        <td>2 </td>
    </tr>
    <tr>
        <td>3 </td>
        <td>3 </td>
        <td>3 </td>
        <td>3 </td>
    </tr>
    <tr>
        <td>4 </td>
        <td>4 </td>
        <td>4 </td>
        <td>4 </td>
    </tr>
</table>

<table border="1">
<tr>
    <td colspan="2" rowspan="4">#1</td>
    <td rowspan="4">#2</td>
    <td rowspan="2">#3</td>
    <td rowspan="2">#4</td>
</tr>
<tr></tr>
<tr>
    <td rowspan="2">#5</td>
    <td rowspan="2">#6</td>
</tr>
<tr></tr>
</table>

输出:

[['1 ', '1 ', '1 ', 'Thing', '1 '],
 ['2 ', '2 ', '2 ', 'Thing', '2 '],
 ['3 ', '3 ', '3 ', 'Thing', '3 '],
 ['4 ', '4 ', '4 ', 'Thing', '4 ']]
[['#1', '#1', '#2', '#3', '#4'],
 ['#1', '#1', '#2', '#3', '#4'],
 ['#1', '#1', '#2', '#5', '#6'],
 ['#1', '#1', '#2', '#5', '#6']]

它花了一些时间才能按照我需要的方式工作,具体来说,我需要将整个表格分成较小的表格,并删除这些表格中的某些行和列,但最终它成功了。非常感谢。 - Atheuz
reclosedev:我编辑了我的主贴,提出了如何处理特定情况的问题。如果您能回答它,我将不胜感激。 - Atheuz
现在它确实可以工作了,虽然我正在手动检查结果,但所有东西似乎都是正确的。感谢您再次的回来和帮助,如果没有您的帮忙,我就会陷入困境。您的新功能完美地融入了我已经进行的调整中,只需要用另一个调用替换一个调用就行了。非常感谢您。 - Atheuz
做到了。在我看来,像BeautifulSoup或lxml这样的库应该能够做到这一点,因为这对我来说太难了。 - Atheuz
谢谢 - 我发现这个片段很有用。我需要的唯一更改是支持无效的非数字colspan / rowspan属性。 - hoju
显示剩余2条评论

3

更新:此答案存在一个错误(基于reclosedev的解决方案)

请参见如何解析具有rowspan和colspan的表格

旧版

对于那些想要Python 3和BeautifulSoup解决方案的人,

def table_to_2d(table_tag):
    rows = table_tag("tr")
    cols = rows[0](["td", "th"])
    table = [[None] * len(cols) for _ in range(len(rows))]
    for row_i, row in enumerate(rows):
        for col_i, col in enumerate(row(["td", "th"])):
            insert(table, row_i, col_i, col)
    return table


def insert(table, row, col, element):
    if row >= len(table) or col >= len(table[row]):
        return
    if table[row][col] is None:
        value = element.get_text()
        table[row][col] = value
        if element.has_attr("colspan"):
            span = int(element["colspan"])
            for i in range(1, span):
                table[row][col+i] = value
        if element.has_attr("rowspan"):
            span = int(element["rowspan"])
            for i in range(1, span):
                table[row+i][col] = value
    else:
        insert(table, row, col + 1, element)

使用方法:

soup = BeautifulSoup('<table><tr><th>1</th><th>2</th><th>5</th></tr><tr><td rowspan="2">3</td><td colspan="2">4</td></tr><tr><td>6</td><td>7</td></tr></table>', 'html.parser')
print(table_to_2d(soup.table))

这并不是优化过的。我为我的一次性脚本编写了这个。


如果 row+i < len(table),则需要该条件,以防最后一个 rowspan 无效地长于表中剩余的行数。 - Sparr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接