合并行的Python美丽的汤

时间:2012-12-21 00:04:59

标签: python beautifulsoup

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

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

编辑:

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

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

4 个答案:

答案 0 :(得分: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)

答案 1 :(得分:2)

由于您的新问题实际上是与原始问题完全不同的问题,因此这是一个完全不同的答案:

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“break”列,或者是1个或更多个常规列。如果这些假设不成立,则可以修改代码。

此外,如果join函数中的生成器表达式混淆了您,则可以将同一事物重写为显式循环:打印标题;然后为每列打印该列;然后打印换行符。

由于您要求'break' in cols[0].get('class', [])的解释,我会将其分解。

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

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

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

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

答案 2 :(得分:1)

您是否尝试过非缩进csvfile.write("\n"),以便它在表循环结束时发生,而不是tr循环?

答案 3 :(得分: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模块比尝试手动格式化要好得多,但这是一个单独的问题。