KeyError: -1 при добавлении нового тега в суп в bs4 - PullRequest
2 голосов
/ 22 марта 2020

У меня есть шаблон htm-файла с таблицей в нем, который имеет один столбец и выглядит следующим образом:

<table>
 <tr>
   <td>ROWNAME1</td>
 </tr>
 <tr>
   <td>ROWNAME2</td>
 </tr>
</table>

Я пытаюсь создать новые теги td, перебирая теги tr в шаблон и добавление нового тд внутри каждого тэга tr, поэтому это будет выглядеть так, если я переберу один из моих входных файлов:

<table>
 <tr>
   <td>ROWNAME1</td>
   <td>ROWNAME1</td>
 </tr>
 <tr>
   <td>ROWNAME2</td>
   <td>ROWNAME2</td>
 </tr>
</table>

Итак, я в основном копирую первый тэг td в tr и добавление его в исходный шаблон html. Это происходит путем перебора строк (тегов tr) моего шаблона html. В каждой строке я заполняю содержимое вновь созданного тд содержимым из отдельного входного файла, в котором есть соответствующий тд тэг с новой информацией. Идея состоит в том, чтобы взять столбец из входного файла и добавить его к таблице в шаблоне html. Каждый файл, который я перебираю, добавляет новый столбец в таблицу в шаблоне.

Это отлично работает для первого файла. Я могу добавить первый новый столбец к моему шаблону html без проблем. Но когда я пытаюсь добавить следующий столбец, скрипт выдает следующую ошибку:

File "C:\Users\Chris\Desktop\Nothing\Henke Skripte\HTML_Tables_transform\Run\temp\Parameter_Tables.py", line 124, in content_replacer
    parent_tag.append(new_td)
  File "C:\Python\Python38\lib\site-packages\bs4\element.py", line 351, in append
    self.insert(len(self.contents), tag)
  File "C:\Python\Python38\lib\site-packages\bs4\element.py", line 315, in insert
    new_child.previous_element = previous_child._last_descendant(False)
  File "C:\Python\Python38\lib\site-packages\bs4\element.py", line 268, in _last_descendant
    last_child = last_child.contents[-1]
  File "C:\Python\Python38\lib\site-packages\bs4\element.py", line 992, in __getitem__
    return self.attrs[key]
KeyError: -1

Очевидно, добавление нового td работает более 20 раз, когда я делаю это для первого столбца / входного файла. Но непосредственно, когда используется следующий входной файл и я пытаюсь добавить второй столбец, функция bs4 пытается получить доступ к словарю attrs недавно добавленного td с помощью ключа «-1», который не существует, и я не Я не знаю, почему он считает, что ключ -1 должен существовать в первую очередь.

Вот мой сокращенный код с дополнительным выводом td, который вызывает проблему:

def content_replacer(parent_tag,content,parent_html):

    '''Takes a td, replaces the content appropriately and returns it.'''

    html_content = BeautifulSoup(content,features='html.parser')

    # Go down the hierachy of children of td to create new td with the full formatting.
    td_tag = parent_tag.select('td')
    if len(td_tag) > 1:
        print(td_tag[0].prettify())
        print(td_tag[0].attrs)
    new_td = parent_html.new_tag('td', attrs=td_tag[0].attrs)
    parent_tag.append(new_td)


    p_tag = parent_tag.select('p')
    new_p = parent_html.new_tag('p', attrs=p_tag[0].attrs)
    new_td.append(new_p)

    span_tag = parent_tag.select('span')                    
    new_span = parent_html.new_tag('span', attrs=span_tag[0].attrs)
    new_p.append(new_span)

    new_span.contents = html_content



# mainloop over input files and tr tags.
all_input_files = glob('*.html')
htm_file = html_parser('template.htm')
nrows = len(htm_file.table.find_all('tr'))

row_name_cleaner = lambda tag: re.sub('[ ]{2,}',' ',tag.text.strip().replace('\n',''))

for i in range(len(all_input_files)):
    # html_parser just reads in htmls and turns them to soup.
    input_file = html_parser(all_input_files[i])
    # content_dictionary creates a dict of input data to be input into the new td tags
    content_dict = content_dictionary(input_file)
    for j in range(nrows):
        # get current row (tr tag)
        htm_row = htm_file.table.select('tr')[j]
        # Get info on current row.
        row_name = row_name_cleaner(htm_row)
        # Translate row name into slicing string for content_dict
        slice_string = slice_string_translator(row_name)
        # Get new content from input file
        new_content = content_dict[slice_string]
        content_replacer(htm_row,new_content,htm_file)

Печать ошибочного td, который должен быть добавлен, выглядит следующим образом:

<td style="width:163.55pt;border-top:solid windowtext 1.0pt;
  border-left:none;border-bottom:solid windowtext 1.0pt;border-right:none;
  mso-border-top-alt:solid windowtext .5pt;mso-border-bottom-alt:solid windowtext .5pt;
  padding:0cm 5.4pt 0cm 5.4pt;height:11.45pt" width="218">
 <p align="center" class="MsoNormal" style="margin-bottom:0cm;margin-bottom:.0001pt;
  text-align:center;line-height:normal">
  <span style='font-size:10.0pt;
  font-family:"CMU Serif"'>
   Compound
   <o:p>
   </o:p>
  </span>
 </p>
</td>

{'width': '218', 'style': 'width:163.55pt;border-top:solid windowtext 1.0pt;\n  border-left:none;border-bottom:solid windowtext 1.0pt;border-right:none;\n  mso-border-top-alt:solid windowtext .5pt;mso-border-bottom-alt:solid windowtext .5pt;\n  padding:0cm 5.4pt 0cm 5.4pt;height:11.45pt'}
Traceback (most recent call last):
  File "C:\Users\Chris\Desktop\Nothing\Henke Skripte\HTML_Tables_transform\Run\temp\Parameter_Tables.py", line 165, in <module>
    content_replacer(htm_row,new_content,htm_file)
  File "C:\Users\Chris\Desktop\Nothing\Henke Skripte\HTML_Tables_transform\Run\temp\Parameter_Tables.py", line 124, in content_replacer
    parent_tag.append(new_td)
  File "C:\Python\Python38\lib\site-packages\bs4\element.py", line 351, in append
    self.insert(len(self.contents), tag)
  File "C:\Python\Python38\lib\site-packages\bs4\element.py", line 315, in insert
    new_child.previous_element = previous_child._last_descendant(False)
  File "C:\Python\Python38\lib\site-packages\bs4\element.py", line 268, in _last_descendant
    last_child = last_child.contents[-1]
  File "C:\Python\Python38\lib\site-packages\bs4\element.py", line 992, in __getitem__
    return self.attrs[key]
KeyError: -1

Вот полный тег tr, в котором возникает ошибка.

<tr style="mso-yfti-irow:0;mso-yfti-firstrow:yes;height:11.45pt">
<td style="width:163.55pt;border-top:solid windowtext 1.0pt;
  border-left:none;border-bottom:solid windowtext 1.0pt;border-right:none;
  mso-border-top-alt:solid windowtext .5pt;mso-border-bottom-alt:solid windowtext .5pt;
  padding:0cm 5.4pt 0cm 5.4pt;height:11.45pt" width="218">
<p align="center" class="MsoNormal" style="margin-bottom:0cm;margin-bottom:.0001pt;
  text-align:center;line-height:normal"><span style='font-size:10.0pt;
  font-family:"CMU Serif"'>Compound<o:p></o:p></span></p>
</td>
<td style="width:163.55pt;border-top:solid windowtext 1.0pt;
  border-left:none;border-bottom:solid windowtext 1.0pt;border-right:none;
  mso-border-top-alt:solid windowtext .5pt;mso-border-bottom-alt:solid windowtext .5pt;
  padding:0cm 5.4pt 0cm 5.4pt;height:11.45pt" width="218">
<p align="center" class="MsoNormal" style="margin-bottom:0cm;margin-bottom:.0001pt;
  text-align:center;line-height:normal"><span style='font-size:10.0pt;
  font-family:"CMU Serif"'>Compound<o:p></o:p></span></p>
<p align="center" class="MsoNormal" style="margin-bottom:0cm;margin-bottom:.0001pt;
  text-align:center;line-height:normal"><span style='font-size:10.0pt;
  font-family:"CMU Serif"'>PK59zni_co_100K_red</span></p></td></tr>

1 Ответ

4 голосов
/ 28 марта 2020

Позвольте мне go сначала для легкого исправления, а затем по причине крипт c сообщения, которое, мы надеемся, загорается.

Решение

В строке

new_span.contents = html_content

замените его на

new_span.contents = html_content.contents

Ссылаясь на документацию , атрибут contents является списком дочерних элементов. Присваивая ему что-то, что не является списком, внутренний код библиотеки ломается при следующем посещении. Вот почему он работает для первого файла, но не для второго.

На самом деле, может быть лучше использовать

new_span.append(html_content)

, поскольку он более semanti c и будет заботиться о метаданных. , Библиотека может позаботиться об этом с помощью установщика, но, похоже, это не так.

Почему сообщение crypti c?

В следующем коде используется BeautifulSoup v.4.8.2 , особенно файл element.py.

Вызов функции .append() аналогичен вставке в конец структуры.

def append(self, tag):
  self.insert(position=len(self.contents), new_child=tag)

def insert(self, position, new_child):
  """Insert a new PageElement in the list of this PageElement's children.
     This works the same way as `list.insert`.
     :param position: The numeric position that should be occupied
        in `self.children` by the new PageElement. 
     :param new_child: A PageElement.
  """
  ...

Внутри кода insert() объект previous_child в нашем случае определяется как последняя структура в списке contents.

previous_child = self.contents[position - 1]

Помните, new_child is новый тег, который мы вставляем. Следующая строка - это ключ

new_child.previous_element = previous_child._last_descendant(False)

. Это создает атрибут, который ссылается на последнего потомка предыдущего потомка.

Это можно увидеть интуитивно для следующего минимального примера

tree = BeautifulSoup(
  '<tr><td><p>string1</p><p><span>string2</span>string3</p></td></tr>',
  features='html.parser',
)
tree._last_descendant()  # 'string3'

Давайте проверим, как определяется _last_descendant.

def _last_descendant(self, is_initialized=True, accept_self=True):
  """Finds the last element beneath this object to be parsed."""
  ...
  while isinstance(last_child, Tag) and last_child.contents:
    last_child = last_child.contents[-1]
  ...

Таким образом, алгоритм будет примерно действовать следующим образом

tree.contents
# [(Tag): <tr><td><p>string1</p><p><span>string2</span>string3</p></td></tr>]
tree.contents[-1]
# (Tag): <tr><td><p>string1</p><p><span>string2</span>string3</p></td></tr>
tree.contents[-1].contents
# [(Tag): <td><p>string1</p><p><span>string2</span>string3</p></td>]
tree.contents[-1].contents[-1]
# (Tag): <td><p>string1</p><p><span>string2</span>string3</p></td>
tree.contents[-1].contents[-1].contents
# [(Tag): <p>string1</p>, (Tag): <p><span>string2</span>string3</p>]
...
tree.contents[-1].contents[-1].contents[-1].contents[-1]
# 'string3'
# No longer an instance of Tag, but a NavigableString. Stop.

Предполагается, что атрибут contents всегда является списком. Проблема возникает, когда это не так, получая вам KeyError.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...