Я использую BeautifulSoup в Python для синтаксического анализа HTML. Одна из проблем, с которой я имею дело, заключается в том, что у меня бывают ситуации, когда colspans различаются в строках заголовков. (Строки заголовков - это строки, которые необходимо объединить, чтобы получить заголовки столбцов на моем жаргоне). То есть один столбец может охватывать несколько столбцов выше или ниже него, и слова необходимо добавлять или добавлять в начале в зависимости от диапазона. Ниже приведен порядок действий для этого. Я использую BeautifulSoup для извлечения столбцов и содержимого каждой ячейки в каждой строке. longHeader - это содержимое строки заголовка с наибольшим количеством элементов, spanLong - это список с colspans каждого элемента в строке. Это работает, но выглядит не очень Pythonic.
Алос - это не сработает, если разница <0, я могу исправить это тем же подходом, который я использовал, чтобы заставить это работать. Но прежде чем я это сделаю, мне интересно, сможет ли кто-нибудь быстро взглянуть на это и предложить более питонический подход. Я долгое время являюсь программистом 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






Возможно, посмотрите на функцию 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', '')...
Также ясность может добавить более питоническое именование:
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...
Используемые имена PEP 8, измените a = a + 1 на a + = 1, просто приведите его в соответствие с рекомендуемым стилем
@bvmou: Вот в чем суть - это длинный пост об этом небольшом изменении - изменении настолько маленьком, что я не мог его обнаружить и должен был спросить.
Вот модифицированная версия вашего алгоритма. застегивать используется для перебора длин и заголовков короткая, а объект класса используется для подсчета и перебора элементов длинная, а также объединения заголовков. пока больше подходит для внутреннего цикла. (простите за слишком короткие имена).
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
В этом примере на самом деле много чего происходит.
Вы «чрезмерно переработали» объекты Beautiful Soup Tag, чтобы составить списки. Оставьте их как теги.
Все эти виды алгоритмов слияния сложны. Это помогает симметрично относиться к двум объединяемым объектам.
Вот версия, которая должна работать напрямую с объектами Beautiful Soup Tag. Кроме того, эта версия ничего не предполагает о длине двух строк.
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
Думаю, я собираюсь ответить на свой вопрос, но мне действительно очень помогли. Спасибо за помощь. Я заставил ответ S.LOTT работать после нескольких небольших исправлений. (Они могут быть настолько маленькими, что их не будет видно (внутренняя шутка)). Итак, теперь вопрос в том, почему это больше Pythonic? Я думаю, что вижу, что он менее плотный / работает с необработанными входными данными вместо производных / я не могу судить, легче ли его читать ---> хотя это легко читать
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
1. Не стесняйтесь использовать определения функций, чтобы облегчить чтение. 2. Примите ответ.
Я пока не собираюсь принимать ответ, так как он не особенно хорош, хотя я многому научился из предоставленных ответов. Отличный ответ сработает в общем случае, и он сработает с первого раза из коробки. Мне все еще нужны строки> 2. Я хочу попробовать два других ответа
Что ж, теперь у меня есть ответ. Я обдумывал это и решил, что мне нужно использовать части каждого ответа. Мне все еще нужно выяснить, нужен ли мне класс или функция. Но у меня есть алгоритм, который, на мой взгляд, более питоничен, чем любой другой. Но он во многом заимствован из ответов некоторых очень щедрых людей. Я очень ценю их, потому что я многому научился.
Чтобы сэкономить время на создание тестовых примеров, я собираюсь вставить полный код, с которым я работал, в 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>
Какие «соглашения» вы изменили при репосте исходного кода?