2つのHTMLヘッダー行をcolspansとマージするよりPython的な方法はありますか?
-
07-07-2019 - |
質問
PythonでBeautifulSoupを使用してHTMLを解析しています。私が扱っている問題の1つは、ヘッダー行間でコルスパンが異なる状況があることです。 (ヘッダー行は、私の専門用語で列見出しを取得するために結合する必要がある行です)つまり、1つの列がその上下にある複数の列にまたがることがあり、その範囲に基づいて単語を追加または追加する必要があります。以下はこれを行うルーチンです。 BeautifulSoupを使用して、colspansを取得し、各行の各セルのコンテンツを取得します。 longHeaderは、最も多くのアイテムを含むヘッダー行のコンテンツです。spanLongは、行内の各アイテムのcolspansを含むリストです。これは機能しますが、あまりPython風には見えません。
Alos-itは、diffが<!> lt; 0の場合は機能しません。これを機能させるために使用したのと同じアプローチで修正できます。しかし、私がやる前に、誰かがこれをすぐに見て、よりPython的なアプローチを提案できるかどうか疑問に思います。私は長い間SASプログラマーであるため、金型を壊すのに苦労しています。SASマクロを書いているかのようにコードを書きます。
longHeader=['','','bananas','','','','','','','','','','trains','','planes','','','','']
shortHeader=['','','bunches','','cars','','trucks','','freight','','cargo','','all other','','']
spanShort=[1,1,3,1,3,1,3,1,3,1,3,1,3,1,3]
spanLong=[1,1,3,1,1,1,1,1,1,1,1,1,3,1,3,1,3,1,3]
combinedHeader=[]
sumSpanLong=0
sumSpanShort=0
spanDiff=0
longHeaderCount=0
for each in range(len(shortHeader)):
sumSpanLong=sumSpanLong+spanLong[longHeaderCount]
sumSpanShort=sumSpanShort+spanShort[each]
spanDiff=sumSpanShort-sumSpanLong
if spanDiff==0:
combinedHeader.append([longHeader[longHeaderCount]+' '+shortHeader[each]])
longHeaderCount=longHeaderCount+1
continue
for i in range(0,spanDiff):
combinedHeader.append([longHeader[longHeaderCount]+' '+shortHeader[each]])
longHeaderCount=longHeaderCount+1
sumSpanLong=sumSpanLong+spanLong[longHeaderCount]
spanDiff=sumSpanShort-sumSpanLong
if spanDiff==0:
combinedHeader.append([longHeader[longHeaderCount]+' '+shortHeader[each]])
longHeaderCount=longHeaderCount+1
break
print combinedHeader
解決
この例では実際に多くのことが行われています。
-
<!> quot;オーバープロセス<!> quot;リストを作成するためのBeautiful Soup Tagオブジェクト。タグとして残します。
-
これらの種類のマージアルゴリズムはすべて困難です。マージされる2つのものを対称的に扱うのに役立ちます。
これは、Beautiful Soup Tagオブジェクトで直接動作するバージョンです。また、このバージョンでは2行の長さについて何も想定していません。
def merge3( row1, row2 ):
i1= 0
i2= 0
result= []
while i1 != len(row1) or i2 != len(row2):
if i1 == len(row1):
result.append( ' '.join(row1[i1].contents) )
i2 += 1
elif i2 == len(row2):
result.append( ' '.join(row2[i2].contents) )
i1 += 1
else:
if row1[i1]['colspan'] < row2[i2]['colspan']:
# Fill extra cols from row1
c1= row1[i1]['colspan']
while c1 != row2[i2]['colspan']:
result.append( ' '.join(row2[i2].contents) )
c1 += 1
elif row1[i1]['colspan'] > row2[i2]['colspan']:
# Fill extra cols from row2
c2= row2[i2]['colspan']
while row1[i1]['colspan'] != c2:
result.append( ' '.join(row1[i1].contents) )
c2 += 1
else:
assert row1[i1]['colspan'] == row2[i2]['colspan']
pass
txt1= ' '.join(row1[i1].contents)
txt2= ' '.join(row2[i2].contents)
result.append( txt1 + " " + txt2 )
i1 += 1
i2 += 1
return result
他のヒント
これは、アルゴリズムの修正バージョンです。 zip は short の長さとヘッダーを反復処理するために使用され、 classオブジェクトは long アイテムだけでなく、ヘッダーを結合します。 while は内側のループにより適しています。 (短すぎる名前は許してください)。
class collector(object):
def __init__(self, header):
self.longHeader = header
self.combinedHeader = []
self.longHeaderCount = 0
def combine(self, shortValue):
self.combinedHeader.append(
[self.longHeader[self.longHeaderCount]+' '+shortValue] )
self.longHeaderCount += 1
return self.longHeaderCount
def main():
longHeader = [
'','','bananas','','','','','','','','','','trains','','planes','','','','']
shortHeader = [
'','','bunches','','cars','','trucks','','freight','','cargo','','all other','','']
spanShort=[1,1,3,1,3,1,3,1,3,1,3,1,3,1,3]
spanLong=[1,1,3,1,1,1,1,1,1,1,1,1,3,1,3,1,3,1,3]
sumSpanLong=0
sumSpanShort=0
combiner = collector(longHeader)
for sLen,sHead in zip(spanShort,shortHeader):
sumSpanLong += spanLong[combiner.longHeaderCount]
sumSpanShort += sLen
while sumSpanShort - sumSpanLong > 0:
combiner.combine(sHead)
sumSpanLong += spanLong[combiner.longHeaderCount]
combiner.combine(sHead)
return combiner.combinedHeader
問題の一部についてzip関数を調べてください:
>>> execfile('so_ques.py')
[[' '], [' '], ['bananas bunches'], [' '], [' cars'], [' cars'], [' cars'], [' '], [' trucks'], [' trucks'], [' trucks'], [' '], ['trains freight'], [' '], ['planes cargo'], [' '], [' all other'], [' '], [' ']]
>>> zip(long_header, short_header)
[('', ''), ('', ''), ('bananas', 'bunches'), ('', ''), ('', 'cars'), ('', ''), ('', 'trucks'), ('', ''), ('', 'freight'), ('', ''), ('', 'cargo'), ('', ''), ('trains', 'all other'), ('', ''), ('planes', '')]
>>>
enumerate
は、カウンターを使用した複雑なインデックス作成の回避に役立ちます。
>>> diff_list = []
>>> for place, header in enumerate(short_header):
diff_list.append(abs(span_short[place] - span_long[place]))
>>> for place, num in enumerate(diff_list):
if num:
new_shortlist.extend(short_header[place] for item in range(num+1))
else:
new_shortlist.append(short_header[place])
>>> new_shortlist
['', '', 'bunches', '', 'cars', 'cars', 'cars', '', 'trucks', 'trucks', 'trucks', '',...
>>> z = zip(new_shortlist, long_header)
>>> z
[('', ''), ('', ''), ('bunches', 'bananas'), ('', ''), ('cars', ''), ('cars', ''), ('cars', '')...
また、より多くのpythonic命名が明確さを追加する可能性があります:
for each in range(len(short_header)):
sum_span_long += span_long[long_header_count]
sum_span_short += span_short[each]
span_diff = sum_span_short - sum_span_long
if not span_diff:
combined_header.append...
私は自分の質問に答えるつもりですが、多くの助けを受けました。すべての助けてくれてありがとう。 S.LOTTの解答は、いくつかの小さな修正を加えて作成しました。 (それらは目に見えないほど小さいかもしれません(冗談の中で))。では、なぜこれがPythonicなのかという疑問があります。私はそれがより密度が低いと思う/派生の代わりに生の入力で動作する/私はそれが読みやすいかどうか判断することはできないと思います--- <!> gt;読みやすいですが
S.LOTTの回答が修正されました
row1=headerCells[0]
row2=headerCells[1]
i1= 0
i2= 0
result= []
while i1 != len(row1) or i2 != len(row2):
if i1 == len(row1):
result.append( ' '.join(row1[i1]) )
i2 += 1
elif i2 == len(row2):
result.append( ' '.join(row2[i2]) )
i1 += 1
else:
if int(row1[i1].get("colspan","1")) < int(row2[i2].get("colspan","1")):
c1= int(row1[i1].get("colspan","1"))
while c1 != int(row2[i2].get("colspan","1")):
txt1= ' '.join(row1[i1]) # needed to add when working adjust opposing case
txt2= ' '.join(row2[i2]) # needed to add when working adjust opposing case
result.append( txt1 + " " + txt2 ) # needed to add when working adjust opposing case
print 'stayed in middle', 'i1=',i1,'i2=',i2, ' c1=',c1
c1 += 1
i1 += 1 # Is this the problem it
elif int(row1[i1].get("colspan","1"))> int(row2[i2].get("colspan","1")):
# Fill extra cols from row2 Make same adjustment as above
c2= int(row2[i2].get("colspan","1"))
while int(row1[i1].get("colspan","1")) != c2:
result.append( ' '.join(row1[i1]) )
c2 += 1
i2 += 1
else:
assert int(row1[i1].get("colspan","1")) == int(row2[i2].get("colspan","1"))
pass
txt1= ' '.join(row1[i1])
txt2= ' '.join(row2[i2])
result.append( txt1 + " " + txt2 )
print 'went to bottom', 'i1=',i1,'i2=',i2
i1 += 1
i2 += 1
print result
さて、答えがあります。私はこれを考えていたので、すべての答えの一部を使用する必要があると決めました。クラスまたは関数が必要かどうかを判断する必要があります。しかし、私はおそらく他のどのアルゴリズムよりもPythonicであると思うアルゴリズムを持っています。しかし、非常に寛大な人々が提供した答えから大きく借りています。私はかなり多くを学んだので、私はそれらをとても感謝しています。
テストケースを作成する時間を節約するために、IDLEで使用してきた完全なコードを貼り付け、HTMLサンプルファイルを使用します。クラス/関数について決定を下す(およびプログラムでこのコードをどのように使用するかを考える必要があります)以外に、コードをよりPythonicにする改善点があれば嬉しいです。
from BeautifulSoup import BeautifulSoup
original=file(r"C:\testheaders.htm").read()
soupOriginal=BeautifulSoup(original)
all_Rows=soupOriginal.findAll('tr')
header_Rows=[]
for each in range(len(all_Rows)):
header_Rows.append(all_Rows[each])
header_Cells=[]
for each in header_Rows:
header_Cells.append(each.findAll('td'))
temp_Header_Row=[]
header=[]
for row in range(len(header_Cells)):
for column in range(len(header_Cells[row])):
x=int(header_Cells[row][column].get("colspan","1"))
if x==1:
temp_Header_Row.append( ' '.join(header_Cells[row][column]) )
else:
for item in range(x):
temp_Header_Row.append( ''.join(header_Cells[row][column]) )
header.append(temp_Header_Row)
temp_Header_Row=[]
combined_Header=zip(*header)
for each in combined_Header:
print each
テストファイルの内容は次のとおりです。これらを添付しようとしましたが、実行できませんでした。
<TABLE style="font-size: 10pt" cellspacing="0" border="0" cellpadding="0" width="100%">
<TR valign="bottom">
<TD width="40%"> </TD>
<TD width="5%"> </TD>
<TD width="3%"> </TD>
<TD width="3%"> </TD>
<TD width="1%"> </TD>
<TD width="5%"> </TD>
<TD width="3%"> </TD>
<TD width="3%"> </TD>
<TD width="1%"> </TD>
<TD width="5%"> </TD>
<TD width="3%"> </TD>
<TD width="1%"> </TD>
<TD width="1%"> </TD>
<TD width="5%"> </TD>
<TD width="3%"> </TD>
<TD width="1%"> </TD>
<TD width="1%"> </TD>
<TD width="5%"> </TD>
<TD width="3%"> </TD>
<TD width="3%"> </TD>
<TD width="1%"> </TD>
</TR>
<TR style="font-size: 10pt" valign="bottom">
<TD> </TD>
<TD> </TD>
<TD> </TD>
<TD> </TD>
<TD> </TD>
<TD> </TD>
<TD> </TD>
<TD> </TD>
<TD> </TD>
<TD> </TD>
<TD nowrap align="right" colspan="2">FOODS WE LIKE</TD>
<TD> </TD>
<TD> </TD>
<TD nowrap align="right" colspan="2"> </TD>
<TD> </TD>
<TD> </TD>
<TD nowrap align="right" colspan="2"> </TD>
<TD> </TD>
</TR>
<TR style="font-size: 10pt" valign="bottom">
<TD> </TD>
<TD> </TD>
<TD nowrap align="CENTER" colspan="6">SILLY STUFF</TD>
<TD> </TD>
<TD> </TD>
<TD nowrap align="right" colspan="2">OTHER THAN</TD>
<TD> </TD>
<TD> </TD>
<TD nowrap align="CENTER" colspan="6">FAVORITE PEOPLE</TD>
<TD> </TD>
</TR>
<TR style="font-size: 10pt" valign="bottom">
<TD> </TD>
<TD> </TD>
<TD nowrap align="right" colspan="2">MONTY PYTHON</TD>
<TD> </TD>
<TD> </TD>
<TD nowrap align="right" colspan="2">CHERRYPY</TD>
<TD> </TD>
<TD> </TD>
<TD nowrap align="right" colspan="2">APPLE PIE</TD>
<TD> </TD>
<TD> </TD>
<TD nowrap align="right" colspan="2">MOTHERS</TD>
<TD> </TD>
<TD> </TD>
<TD nowrap align="right" colspan="2">FATHERS</TD>
<TD> </TD>
</TR>
<TR style="font-size: 10pt" valign="bottom">
<TD nowrap align="left">Name</TD>
<TD> </TD>
<TD nowrap align="right" colspan="2">SHOWS</TD>
<TD> </TD>
<TD> </TD>
<TD nowrap align="right" colspan="2">PROGRAMS</TD>
<TD> </TD>
<TD> </TD>
<TD nowrap align="right" colspan="2">BANANAS</TD>
<TD> </TD>
<TD> </TD>
<TD nowrap align="right" colspan="2">PERFUME</TD>
<TD> </TD>
<TD> </TD>
<TD nowrap align="right" colspan="2">TOOLS</TD>
<TD> </TD>
</TR>
</TABLE>