在Python中将<br/>转换为<p> </p>

时间:2018-01-11 22:05:50

标签: python html beautifulsoup

我正在尝试使用BeautifulSoup在Python中解析许多HTML文档中的文本。在文档中,部分段落和换行格式使用<br/>标记完成,有些则使用<p></p>标记完成。我想完全删除<br/>标记,并在进一步处理之前将前面的文本包装在<p></p>标记中,这样就可以将相同的方法应用于所有文档。

我在Beautifulsoup sibling structure with br tags查看了答案。答案描述了如何完全删除<br>标签,但这会留下未封闭文本的碎片,这些碎片不能轻易被隔离。

我想出了一个使用lxml的部分解决方案,找到所有<br/>标签的先前兄弟,并将它们封闭,但由于<br/>标签未在文档中以任何一致的方式使用,有时候例如,先前的兄弟节点是<b><i>节点或我试图包装的文本的另一个子集。问题是如何以编程方式查找上一个换行符之后的所有文本,以确定开始<p>标记的位置。有没有我想到的解决方案?

我希望通过<br/>或其他标记(与关闭</p>等)隔离与前一个文本片段分开的每个文本片段,删除{ {1}}代码,并在<br/>代码中包装该段文字。

另一种表达问题的方法:每次最终用户看到换行符时,我都希望它前面的文本段是自己的<p></p>包装器。

一些HTML:

<p>

示例输出:

<h3>Program</h3>
<p>Respighi - <i>Trittico Botticelliano</i>, P. 151<br>Barber - Knoxville: <i>Summer of 1915</i>, Op. 24<br>Korngold - <i>Much Ado About Nothing</i>, Op. 11: Suite</p>
<hr>      

更新:由于以上示例非常简单,以下是一些应该可以以相同方式处理的真实HTML示例。

<h3>Program</h3>
<p>Respighi - <i>Trittico Botticelliano</i>, P. 151</p>
<p>Barber - Knoxville: <i>Summer of 1915</i>, Op. 24</p>
<p>Korngold - <i>Much Ado About Nothing</i>, Op. 11: Suite</p>
<hr>      

2 个答案:

答案 0 :(得分:2)

您可以使用re.sub

import re
text = '''<h3>Program</h3>
<p>Respighi - <i>Trittico Botticelliano</i>, P. 151<br>Barber - 
 Knoxville: <i>Summer of 1915</i>, Op. 24<br>Korngold - <i>Much Ado About Nothing</i>, Op. 11: Suite</p>
<hr>'''
new_text = re.sub('\<br\>', '</p>\n<p>', text)

输出:

<h3>Program</h3>
<p>Respighi - <i>Trittico Botticelliano</i>, P. 151</p>
<p>Barber - Knoxville: <i>Summer of 1915</i>, Op. 24</p>
<p>Korngold - <i>Much Ado About Nothing</i>, Op. 11: Suite</p>
<hr>

答案 1 :(得分:1)

重新构建容器标记的一些find - extract魔法可以做一些明显的事情(最初建议的replace_with不能)

import bs4
html="""
<html>
  <body>
    <!-- <p> -->
      Lorem ipsum dolor sit amet,<br>
      consectetur <b>adipiscing</b> elit,<br>
      sed do eiusmod tempor incididunt<br>
      ut labore et dolore magna aliqua.
    <!-- </p> -->
  </body>
</html>"""
soup=bs4.BeautifulSoup(html,"lxml")
print("Before:")
print(soup.prettify())
root=soup.find('br').parent
p=soup.new_tag('p')
for x in list(root.contents):
  if x.name=='br':
    if(p.contents):
      x.insert_before(p)
    p=soup.new_tag('p')
  else:
    p.contents.append(x)
  x.extract()
if(p.contents):
  root.contents.append(p)
if(root.name=='p'):
  root.unwrap()
print()
print("After:")
print(soup.prettify())
print("Re-parsed:")
print(bs4.BeautifulSoup(str(soup),"lxml").prettify())

代码找到<br>标记,然后在其父级(在内部称为root)上工作,并将所有内容包装到它遇到的<p>标记之间的<br> - s。代码不是很干净,但在某种程度上有效 如果根目录恰好是<p>标记,则会将其解包(您可以通过取消注释<p> - </p>对来测试它) 它保留了子标签(比如这里的大胆的东西),但它也是它的弱点,因为所有这些子标签都会进入最近的<p>。这里的评论不是一个问题,但如果有标题,或者几乎任何块元素,它看起来会很奇怪。

立即输出看起来很难看,所以我只显示重新解析的一个: - )

<html>
 <body>
  <p>
   <!-- <p> -->
   Lorem ipsum dolor sit amet,
  </p>
  <p>
   consectetur
   <b>
    adipiscing
   </b>
   elit,
  </p>
  <p>
   sed do eiusmod tempor incididunt
  </p>
  <p>
   ut labore et dolore magna aliqua.
   <!-- </p> -->
  </p>
 </body>
</html>

一般来说,它也适用于刻意邪恶的例子,2件事情被修改:

  1. 只要遇到更多<br>标记,它就会循环运行。我不得不为每次迭代重新解析HTML,不知怎的,一个简单的find是不够的
  2. 显然是新的&#39;空的&#39; <p>标记已经包含换行符,因此引入了这个奇怪的空洞检查:if("".join([str(y) for y in p.contents]).strip()):(为了增加乐趣,元素的字符串转换也是必要的)
  3. 否则:

    import bs4
    html="""
    <html>
     <body>
     <p>
     </p>
     <p>
      <span class="orangeheadline">
    Masterworks presented by EBSCO &amp; Vulcan Value Partners
      </span>
      <br/>
      <span class="boldtext">
        Justin Brown Returns! Mozart &amp; Beethoven
       <br/>
       <br/>
        Justin Brown, conductor &amp; piano
      </span>
     </p>
    <br/>
    <p>
      <span class="boldtext">
       Nov. 17 &amp; 18, 2017 at 8pm
      <br/>
      </span>
      <br/>
      Alys Stephens Center | Jemison Concert Hall | Map &amp; Directions
    </p>
     <br/>
     <br/>
    <p>
    <strong>
      MOZART:
     </strong>
      Piano Concerto No. 27
      <br/>
     <strong>
     CLYNE:
     </strong>
     The Midnight Hour
     <br/>
     <strong>
     BEETHOVEN:
     </strong>
     Symphony No. 4
     <br/>
     <strong>
     </strong>
     <br/>
    </p>
    <p class="MsoNormal" style="margin-bottom: 0.0001pt;">
      <span style="">
       Justin Brown returns as both conductor and pianist! Maestro Browns virtuosity shines as he leads Mozarts delightful Concerto no. 27 from the keyboard, and takes the podium to conduct Beethovens graceful Fourth Symphony.
      <br/>
      <br/>
      <em>
      Gain insight into the works youre about to hear by joining us at 7pm for Concert Comments in the Reynolds-Kirschbaum Recital Hall. Free.
      </em>
     </span>
     <br/>
     <br/>
     <span class="MsoNormal" style="margin-bottom: 0.0001pt;">
     Click here for information about the Coffee Concert
     </span>
    </p>
      <br/>
      <span class="date">
        Past Event
      </span>
      <br/>
      <br/>
      <br/>
    </body>  
    </html>"""
    soup=bs4.BeautifulSoup(html,"lxml")
    print("Before:")
    print(soup.prettify())
    
    br=soup.find('br')
    while br:
      root=br.parent
      p=soup.new_tag('p')
      for x in list(root.contents):
        if x.name=='br':
          if("".join([str(y) for y in p.contents]).strip()):
            x.insert_before(p)
          p=soup.new_tag('p')
        else:
          p.contents.append(x)
        x.extract()
      if("".join([str(y) for y in p.contents]).strip()):
        root.contents.append(p)
      if(root.name=='p'):
        root.unwrap()
      soup=bs4.BeautifulSoup(str(soup),"lxml")
      br=soup.find('br')
    
    print()
    print("After:")
    print(soup.prettify())
    print("Re-parsed:")
    print(bs4.BeautifulSoup(str(soup),"lxml").prettify())