【问题标题】:BeautifulSoup - combine consecutive tagsBeautifulSoup - 组合连续的标签
【发布时间】:2018-04-25 15:38:01
【问题描述】:

我必须使用最混乱的 HTML,其中单个单词被分成单独的标签,如下例所示:

<b style="mso-bidi-font-weight:normal"><span style='font-size:14.0pt;mso-bidi-font-size:11.0pt;line-height:107%;font-family:"Times New Roman",serif;mso-fareast-font-family:"Times New Roman"'>I</span></b><b style="mso-bidi-font-weight:normal"><span style='font-family:"Times New Roman",serif;mso-fareast-font-family:"Times New Roman"'>NTRODUCTION</span></b>

这有点难读,但基本上“介绍”这个词被分成了

<b><span>I</span></b> 

<b><span>NTRODUCTION</span></b>

span 和 b 标记具有相同的内联属性。

将这些结合起来的好方法是什么?我想我会循环查找这样的连续 b 标签,但我一直坚持如何合并连续的 b 标签。

for b in soup.findAll('b'):
    try:
       if b.next_sibling.name=='b':
       ## combine them here??
    except:
        pass

有什么想法吗?

编辑: 预期输出如下

<b style="mso-bidi-font-weight:normal"><span style='font-family:"Times New Roman",serif;mso-fareast-font-family:"Times New Roman"'>INTRODUCTION</span></b>

【问题讨论】:

  • 如果您只想要文本内容,只需执行 node.text 其中 node 是您向我们展示的内容的父级。
  • 我需要重新编写 HTML 来清理它,而不仅仅是获取文本。这是更大努力的一部分。
  • @DannyStaple 是的,b。刚刚编辑,谢谢收看

标签: python html beautifulsoup


【解决方案1】:

我用来解决这个问题的方法是在另一个元素中插入一个元素,然后 unwrap() 它,这将保留所有嵌套的文本和标签——这与使用元素的文本内容的方法不同。

例如:

for b in soup.find_all('b'):
    prev = b.previous_sibling
    if prev and prev.name == 'b':  # Any conditions needed to decide to merge
        b.insert(0, prev)  # Move the previous element inside this one
        prev.unwrap()  # Unwrap <b><b>prev</b> b</b> into <b>prev b</b>

注意使用previous_sibling 而不是next_sibling,这样我们就不会修改即将迭代的汤的后续部分。

然后我们可能想用&lt;span&gt; 重复这个过程以达到最终结果。如果需要合并条件,也可以检查b['style'] == prev['style']

【讨论】:

    【解决方案2】:

    下面的解决方案将所有选定的 &lt;b&gt; 标记中的文本组合到您选择的一个 &lt;b&gt; 中,并分解其他的。

    如果您只想合并来自连续标签的文本,请遵循Danny's 方法。

    代码:

    from bs4 import BeautifulSoup
    
    html = '''
    <div id="wrapper">
      <b style="mso-bidi-font-weight:normal">
        <span style='font-size:14.0pt;mso-bidi-font-size:11.0pt;line-height:107%;font-family:"Times New Roman",serif;mso-fareast-font-family:"Times New Roman"'>I</span>
      </b>
      <b style="mso-bidi-font-weight:normal">
        <span style='font-family:"Times New Roman",serif;mso-fareast-font-family:"Times New Roman"'>NTRODUCTION</span>
      </b>
    </div>
    '''
    
    soup = BeautifulSoup(html, 'lxml')
    container = soup.select_one('#wrapper')  # it contains b tags to combine
    b_tags = container.find_all('b')
    
    # combine all the text from b tags
    text = ''.join(b.get_text(strip=True) for b in b_tags)
    
    # here you choose a tag you want to preserve and update its text
    b_main = b_tags[0]  # you can target it however you want, I just take the first one from the list
    b_main.span.string = text  # replace the text
    
    for tag in b_tags:
        if tag is not b_main:
            tag.decompose()
    
    print(soup)
    

    感谢任何 cmets。

    【讨论】:

    • 您可以使用for tag in b_tags[1:]: tag.decompose(),而不是每次都检查if tag is not b_main:
    • 另外,这仅在您要加入的部分有一个带有id 的父级时才有效。否则,它将连接来自 HTML 不同部分的所有文本。因此,这里最好的方法是使用previous_siblingnext_sibling
    • @KeyurPotdar 至于第一条评论,是的,但仅限于这种特定情况。我希望代码更通用,因此如果您选择要以索引以外的其他方式保留的标签,它仍然可以工作;)至于id,我认为没有必要,因为任何包装 div 或任何东西你可以定位就足够了。
    • id 通常是唯一的。但是,如果您使用一个类,它将连接来自具有相同类名的所有不同 &lt;div&gt; 标记的文本。如果不存在具有相同类/ id 的多个标签,则不会有问题。
    • 一个简单的例子是&lt;div class="wrapper"&gt;&lt;b&gt;t1&lt;/b&gt;&lt;b&gt;t2&lt;/b&gt;&lt;/div&gt;&lt;p&gt;some other text&lt;/p&gt;&lt;div class="wrapper"&gt;&lt;b&gt;x1&lt;/b&gt;&lt;b&gt;x2&lt;/b&gt;&lt;/div&gt;。您的程序会将文本返回为t1t2x1x2,它应该是t1t2 和另一个x1x2。未测试,如有错误请指正。
    【解决方案3】:

    也许您可以检查b.previousSibling 是否是b 标记,然后将当前节点的内部文本附加到该标记中。完成此操作后 - 您应该能够使用 b.decompose 从树中删除当前节点。

    【讨论】:

      【解决方案4】:

      相邻的答案只结合text标签,不保留嵌套标签,如&lt;i&gt;。 下面的代码就是这样做的。

      例如,对于这个 html:

      <div>
          <p>A<i>b</i>cd1, <i>a</i><b><i>b</i></b><i>cd2</i> abcd3 <i>ab</i></p>
          <p>cd4 <i>a</i><i>bsd5</i> <i>ab<span>cd6</span></i></p>
      </div>
      

      结果将是:

      <div>
          <p>A<i>b</i>cd1, <i>a<b>b</b>cd2</i> abcd3 <i>ab</i></p>
          <p>cd4 <i>absd5 ab<span>cd6</span></i></p>
      </div>
      

      ignoring_tags_names 变量中,您可以设置合并时哪些标签被视为嵌套和忽略。任何其他标签都会破坏合并链。

      re_symbols_ignore 变量中,您可以设置在连接时忽略相同标签之间的文本字符。任何其他字符都会破坏合并链。

      您还可以指定检查标记属性的身份。但是他们的订单没有被检查。 {class: ['a', 'b']}{class: ['b', 'a']} 被认为是不同的,标签不会合并。

      import re
      from bs4 import BeautifulSoup, NavigableString
      
      
      def find_and_combine_tags(soup, init_tag_name: str, init_tag_attrs: dict = None or {}):
          def combine_tags(tag, tags: list):
              # appending the tag chain to the first tag
              for t in tags:
                  tag.append(t)
      
              # unwrapping them
              for t in tag.find_all(init_tag_name):
                  if t.name == init_tag_name and t.attrs == init_tag_attrs:
                      t.unwrap()
      
          def fill_next_siblings(tag, init_tag_name: str, ignoring_tags_names: list) -> list:
              next_siblings = []
              for t in tag.next_siblings:
                  if isinstance(t, NavigableString) and not re_symbols_ignore.match(t):
                      next_siblings.append(t)
                  elif isinstance(t, NavigableString) and re_symbols_ignore.match(t):
                      next_siblings.append(t)
                  elif t.name in ignoring_tags_names and t.attrs == init_tag_attrs:  # also checking the tag attrs
                      next_siblings.append(t)
                  else:
                      # filling `next_siblings` until another tag met
                      break
      
              has_other_tag_met = False
              for t in next_siblings:
                  if t.name == init_tag_name and t.attrs == init_tag_attrs:
                      has_other_tag_met = True
                      break
      
              # removing unwanted tags on the tail of `next_siblings`
              if has_other_tag_met:
                  while True:
                      last_tag = next_siblings[-1]
                      if isinstance(last_tag, NavigableString):
                          next_siblings.pop()
                      elif last_tag.name != init_tag_name and last_tag.attrs != init_tag_attrs:
                          next_siblings.pop()
                      else:
                          break
                  return next_siblings
      
          # Ignore nested tags names
          if init_tag_name in ['i', 'b', 'em']:
              ignoring_tags_names = ['i', 'b', 'em']
          elif init_tag_name in ['div']:
              # A block tags can have many nested tags
              ignoring_tags_names = ['div', 'p', 'span', 'a']
          else:
              ignoring_tags_names = []
      
          # Some symbols between same tags can add into them. Because they don't changing of font style.
          if init_tag_name == 'i':
              # Italic doesn't change the style of some characters (spaces, period, comma), so they can be combined
              re_symbols_ignore = re.compile(r'^[\s.,-]+$')
          elif init_tag_name == 'b':
              # Bold changes the style of all characters
              re_symbols_ignore = re.compile(r'^[\s]+$')
          elif init_tag_name == 'div':
              # Here should be careful with merging, because a html can have some `\n` between block tags (like `div`s)
              re_symbols_ignore = re.compile(r'^[\s]+$')
          else:
              re_symbols_ignore = None
      
          all_wanted_tags = soup.find_all(init_tag_name)
          if all_wanted_tags:
              tag_groups_to_combine = []
              tag = all_wanted_tags[0]
              last_tag = tag
              while True:
                  tags_to_append = fill_next_siblings(tag, init_tag_name, ignoring_tags_names)
                  if tags_to_append:
                      tag_groups_to_combine.append((tag, tags_to_append))  # the first tag and tags to append
      
                  # looking for a next tags group
                  last_tag = tags_to_append[-1] if tags_to_append else tag
                  for tag in all_wanted_tags:
                      if tag.sourceline > last_tag.sourceline \
                              or (tag.sourceline == last_tag.sourceline and tag.sourcepos > last_tag.sourcepos):
                          break
                  if last_tag.sourceline == all_wanted_tags[-1].sourceline and last_tag.sourcepos == last_tag.sourcepos:
                      break
                  last_tag = tag
      
              for first_tag, tags_to_append in tag_groups_to_combine:
                  combine_tags(first_tag, tags_to_append)
      
          return soup
      
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-03-10
        • 2013-12-02
        • 1970-01-01
        • 1970-01-01
        • 2023-03-07
        相关资源
        最近更新 更多