Python美丽汤合并行

3

这里是HTML代码

<table>
<tr>
<td class="break">mono</td>
</tr>
<tr>
<td>c1</td>
<td>c2</td>
<td>c3</td>
</tr>
<tr>
<td>c11</td>
<td>c22</td>
<td>c33</td>
</tr>
<tr>
<td class="break">dono</td>
</tr>
<tr>
<td>d1</td>
<td>d2</td>
<td>d3</td>
</tr>
<tr>
<td>d11</td>
<td>d22</td>
<td>d33</td>
</tr>
</table>

现在,我希望在 CSV 文件中输出以下内容:
mono c1 c2 c3
mono c11 c22 c33
dono d1 d2 d3
dono d11 d22 d33

但是我得到的输出像这样:
mono
c1 c2 c3
c11 c22 c33
dono
d1 d2 d3
d11 d22 d33

这是我的代码:

import codecs
from bs4 import BeautifulSoup
with codecs.open('dump.csv', "w", encoding="utf-8") as csvfile:


    f = open("input.html","r")

    soup = BeautifulSoup(f)
    t = soup.findAll('table')
    for table in t:
        rows = table.findAll('tr')
        for tr in rows:
            cols = tr.findAll('td')
            for td in cols:
                csvfile.write(str(td.find(text=True)))
                csvfile.write(",")
            csvfile.write("\n")

请帮我解决这个问题。谢谢。
编辑:
更详细地解释一下。我需要添加第一节(mono,dono等)的内容。
规则是:除非我遇到了一个新的“break”类,否则该类内的文本应追加到该类以下的任何tr中。

当一行是真正的新标题时,什么规则适用?还是它是一个延续?当列文本恰好为4个字符长时?或者没有数字?当只有1列时?每3行?所有这些,以及可能的许多其他可能性,都是从您的输入数据中推断出来的合理推论,但是其中所有或除一个之外都不是您想要的。因此,您必须明确决定并告诉我们您想要的规则。 - abarnert
好的。再次编辑过了。感谢您的时间。 - MBanerjee
4个回答

3

鉴于您的新问题实际上与原始问题完全不同,这里有一个完全不同的答案:

for table in t:
    rows = table.findAll('tr')
    for row in rows:
        cols = row.findAll('td')
        if 'break' in cols[0].get('class', []):
            header = cols[0].text
        else:
            print header, ' '.join(col.text for col in cols)

我假设一行要么恰好有一个"break"列,要么有一个或多个常规列。如果这些假设不成立,代码可以进行修改。
此外,如果在join函数中的生成器表达式让您感到困惑,同样的事情可以重写为显式循环:打印标题; 然后对于每一列,打印该列; 然后打印一个换行符。
由于您要求解释'break' in cols[0].get('class', []),我将对其进行分解。
  • cols 是当前 tr 节点中每个 td 节点的 BS4 Tag 对象的列表。
  • cols[0] 是第一个对象。
  • cols[0].get('class', [])Tag 对象视为字典,如 文档 中所述,并在其上调用熟悉的 get(key, defaultvalue) 方法。
    • 在 BS4 中(与旧版本不同),按名称查找 Tag 属性始终返回 list。而 BS3 会为 <td class='foo bar'> 返回 'foo bar',并且对于 <td class='foo' class='bar'> 返回 'bar',但是 BS4 对于这两种情况都将返回 ['foo', 'bar']
  • 总结一下,对于 <td class='break'> 的情况,cols [0] .get('class',[]) 将是 ['break'],而对于您示例输入中的所有其他情况,它将是 []
如上所述,我假设一行要么恰好有1个“break”列,要么有1个或多个常规列。您可以看到我在代码中如何利用这些假设。但是,如果任何一个假设被打破,您没有告诉我们在这些情况下您想要做什么。
如果您有任何没有列的行,显然 cols [0] 会引发 IndexError 。但是您必须决定在这种情况下要做什么。它应该什么都不做吗?只打印标题?更改为状态,在此状态下,直到我们看到标题行,才不会打印任何内容?无论您做出什么决定,都应该很容易编写代码。
如果您有任何带有标头后跟普通行的行,则普通行将被忽略。如果您有任何不是行中第一列的标题,则它们将被视为普通值。如果您在同一行中有多个标题,则除第一个外,其他所有标题都将被忽略。等等。在每种情况下,这可能是或可能不是您想要的。但是您必须先决定您想要什么,然后才能编写代码。

如果在cols [0] .get('class',[])中出现'break':则此行会出现IndexError:列表索引超出范围。 - Monojit
请问cols[0].get('class', [])的作用是什么? - MBanerjee
我会在答案中添加解释。如果它在你的真实数据集上失败了,请将你的真实数据集发布到某个地方,或者更好的是,将其削减到仅足以重现问题,并发布该数据集。 - abarnert
非常感谢您的解释。虽然不是鱼,但现在我知道如何捕鱼了。是的,我解决了与我的案例相关的问题,这段代码很好。:) - MBanerjee
@MBanerjee:记住,这里的答案旨在帮助未来的搜索者以及原始提问者。所以,如果你从我的答案中发现了一些额外的技巧,认为能够帮助其他人,那么更新问题和/或答案可能是值得的。但无论如何,很高兴它正在为您工作。 - abarnert

2
使用内置的csv模块处理CSV文件,比手动操作更容易。
至于你遇到的问题,这是因为你的csvfile.write('\n')缩进太多了,所以数据会像表格中显示的那样被写入。改用生成器应该就可以解决问题:
import csv
from bs4 import BeautifulSoup

def get_fields(soup):
    for td in soup.find_all('td'):
        yield td.get_text().strip()

with open('csvfile.csv', 'w') as csvfile:
    writer = csv.writer(csvfile)

    with open('input.html', 'r') as handle:
        soup = BeautifulSoup(handle.read())

    fields = list(get_fields(soup))

    writer.writerow(fields)

NameError:全局名称'table'未定义。 - MBanerjee
@MBanerjee:将“table”更改为“soup”。 - Blender

1
如果您想要一起运行表中的所有行,为什么不直接忽略这些行呢?
for table in t:
    cols = table.findAll('td')
    for td in cols:
        csvfile.write(str(td.find(text=True)))
        csvfile.write(",")
    csvfile.write("\n")

使用BeautifulSoup而不是严格的解析器的一半原因是让您可以随意处理结构(另一半原因是让您处理那些在生成结构时随意处理的人)。所以,为什么要逐行进行,然后尝试忽略逐行性呢?您可以直接按列进行。

使用csv模块比手动格式化要好得多,但这是一个单独的问题。


@abarnert-"如果您想要一起运行表中的所有行"-但这不是我想要的,请检查编辑后的问题。对于造成的混淆,我很抱歉。 - MBanerjee

1

你尝试过将 csvfile.write("\n") 的缩进取消,使其出现在表格循环的结尾而不是 tr 循环中吗?


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