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")

请帮我解决这个问题。谢谢。

编辑:

解释了更多细节。这里我需要添加要附加的第一部分(单声道,多诺等)。

这里的规则是,除非我遇到一个新的“break”类,否则该类中的文本应附加到其下方的任何 tr 中。

4

4 回答 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)

我假设一行将恰好是 1 个“中断”列,或者是 1 个或更多常规列。如果这些假设不成立,则可以修改代码。

另外,如果join函数中的生成器表达式让你感到困惑,同样的事情可以重写为显式循环:打印标题;然后对于每一列,打印该列;然后打印一个换行符。

既然你要求解释'break' in cols[0].get('class', []),我会分解它。

  • cols是当前节点中每个节点list的 BS4对象之一。Tagtdtr
  • cols[0]是第一个。
  • cols[0].get('class', [])Tag对象视为字典,如docs 中所述,并在其上调用熟悉的get(key, defaultvalue)方法。
    • 在 BS4 中(与旧版本不同),按名称查找Tag属性总是返回list. 虽然 BS3 将返回'foo bar'for<td class='foo bar'>'bar'for <td class='foo' class='bar'>,但 BS4 将返回['foo', 'bar']两者。
  • 将它们放在一起,cols[0].get('class', [])['break']适用于<td class='break'>案例,以及[]样本输入中的所有其他案例。

如上所述,我假设一行将恰好是 1 个“中断”列,或者是 1 个或更多常规列。您可以看到我在代码中使用这些假设的位置。但是,如果这些假设中的任何一个被打破,那么您还没有告诉我们足够多的信息来了解在这些情况下您想做什么。

如果您有任何没有列的行,显然cols[0]会引发IndexError. 但是你必须决定在这种情况下该怎么做。它应该什么都不做吗?只打印标题?更改为在我们看到标题行之前什么都不会打印的状态?无论您决定什么,它都应该易于编码。

如果您有任何带有标题的行后跟普通行,则普通行将被忽略。如果您有任何标题不是一行中的第一列,它们将被视为正常值。如果您在同一行中有多个标题,则除第一个之外的所有标题都将被忽略。等等。在每种情况下,这可能是也可能不是。但是在编写代码之前,您必须决定您想要什么。

于 2012-12-21T01:02:57.913 回答
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)
于 2012-12-21T00:12:29.893 回答
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使用该模块比尝试手动格式化它要好得多,但这是一个单独的问题。

于 2012-12-21T00:22:59.873 回答
1

您是否尝试过取消缩进csvfile.write("\n")以使其出现在表格循环的末尾,而不是 tr 循环的末尾?

于 2012-12-21T00:08:42.947 回答