使用 rowspan 和 colspan 解析表

Posted

技术标签:

【中文标题】使用 rowspan 和 colspan 解析表【英文标题】:Parsing a table with rowspan and colspan 【发布时间】:2012-04-16 05:34:37 【问题描述】:

我有一张需要解析的表,具体来说,它是一个有 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', ' ']

相反?

EDIT2:使用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' , ' ']
]

【问题讨论】:

如果您能向我们展示您当前使用的代码以及实际获得的输出,而不是与您所获得的输出相似的输出,那将真的对我们有所帮助。 【参考方案1】:

更新:这个答案有一个bug(基于reclosedev解决方案)

见How to parse table with rowspan and 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))

这是优化的。这是我为我的一次性脚本编写的。

【讨论】:

if row+i &lt; len(table): table[row+i][col] = value 需要条件以防最后一个行跨度无效地长于表中剩余的行数【参考方案2】:

UPDATE(删除以前的功能)

UPDATE2 已修复和简化。

我的第一个函数是错误的。这是另一个,它正在工作但需要测试:

#!/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']]

【讨论】:

花了一些时间让它完成我需要它做的事情,特别是我需要将整个表拆分为较小的表并删除这些表中的一些行和列,但最后它工作。非常感谢。 reclosedev:我编辑了我的主要帖子,但遇到了关于如何处理特定案例的问题。如果您能回答,我将不胜感激。 @Atheuz,请参阅更新的答案。第一个功能确实是错误的:(,但是这个应该可以工作。 它现在确实有效,我正在手动检查结果,但一切似乎都是正确的。感谢您再次回来并提供帮助,因为没有您我会被困住。您的新功能正好适合我对它所做的调整,这只是一个与另一个交换一个呼叫的问题。非常感谢。 似乎有一两种情况,这不会将单元格放置在正确的位置。我将在 OP 中添加一个示例。

以上是关于使用 rowspan 和 colspan 解析表的主要内容,如果未能解决你的问题,请参考以下文章

html表单元素的colspan和rowspan

减少引导表宽度,colspan 不起作用

如何在 HTML 表格中使用 colspan 和 rowspan?

table合并单元格colspan和rowspan .

html电子邮件中的rowspan和colspan

iText 结合 rowspan 和 colspan - PDFPTable