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