在完全平坦的 HTML 层次结构上使用 BeautifulSoup

Utilizing BeautifulSoup on a completely flat HTML hierarchy

所以我是一个网络抓取菜鸟,运行 变成了一些我以前从未见过的 HTML 格式。我需要的所有信息都在一个完全扁平的层次结构中。我需要抓住 Date/MovieName/Location/Amenities。

它的布局是这样的(就像这样):

<div class="caption">
  <strong>July 1</strong>
  <br>
  <em>Top Gun</em>
  <br>
  "Location: Millennium Park"
  <br>
  "Amenities: Please be a volleyball tournament..."
  <br>
  <em>Captain Phillips</em>
  <br>
  "Location: Montgomery Ward Park"
  <br>
  <br>
  <strong>July 2</strong>
  <br>
  <em>The Fantastic Mr. Fox </em>

我希望最终在字典或列表中具有格式,以便能够使用 csvwriter 或 Dictwriter 将其写为 CSV 文件;所以输出像

[7 月 1 日,壮志凌云,千禧公园,"Please be a volleyball tournament..."], [7月1日,飞利浦船长,蒙哥马利沃德公园,]等

如您所见,令人讨厌的是,当两部电影在同一日期放映时,日期仅显示在第一部电影之前;然后列出的所有电影直到下一个 <strong>somedate<strong> 都属于该初始日期。

有什么建议吗?我怎样才能让多部电影都在上面的标签中指定的日期之内呢?可能想 find_next_siblings 包括检查标签是否是 <strong> 标签?

这是一个非常丑陋的解决方案,在您使用它之前应该使其更加健壮,但像这样的东西应该可以工作:

from bs4 import BeautifulSoup
import re
import csv

doc = """<div class="caption">
  <strong>July 1</strong>
  <br>
  <em>Top Gun</em>
  <br>
  "Location: Millennium Park"
  <br>
  "Amenities: Please be a volleyball tournament..."
  <br>
  <em>Captain Phillips</em>
  <br>
  "Location: Montgomery Ward Park"
  <br>
  <br>
  <strong>July 2</strong>
  <br>
  <em>The Fantastic Mr. Fox </em>
  <br>
  "Location: Somewhere"
  <br>
  "Amenities: Something something"
  <br>"""

soup = BeautifulSoup(doc.replace("<br>", "<br/>"))

data = []

for date in soup.find_all("strong"):
    sibling = date.next_sibling
    while sibling and sibling.name != "strong":
        if sibling.name == "em":
            title = sibling
            location = title.find_next("br").next
            extra = location.find_next("br").next

            row = []
            row.append(date.text)
            row.append(title.text)
            row.append(re.findall('(?<=:)[^"]*', location)[0])
            extra_val = re.findall('(?<=:)[^"]*', extra)
            if len(extra_val):
                row.append(extra_val[0])

            data.append(row)

        sibling = sibling.next_sibling

with open('foo.csv', 'wb') as csvfile:
    writer = csv.writer(csvfile)
    writer.writerows(data)

请注意 doc.replace("<br>", "<br/>"),因为 BeautifulSoup 否则会将 <br> 标签解释为包含文档的所有其余部分。

进一步解释 <br><br/> 部分:

<p></p><em></em>

上面的HTML emp的同胞。

<p><em></em></p>

在这个 HTML 中 emp 的 child。现在让我们看看 BeautifulSoup 如何解析一些 HTML:

>>> from bs4 import BeautifulSoup
>>> BeautifulSoup('<br><p>Hello<br></p>', 'html.parser')
<br><p>Hello<br/></p></br>
>>> BeautifulSoup('<br><p>Hello<br></p>', 'html5lib')
<html><head></head><body><br/><p>Hello<br/></p></body></html>

html.parser 是 Pythons built-in HTML-parser,这是你默认得到的。如您所见,它添加了一个结束 </br> 标记并将一个 <br> 转换为 </br>。简而言之,如果不关闭标签,它就不能很好地完成工作。这搞乱了哪些元素应该是兄弟姐妹。

另一方面,

html5lib 试图匹配浏览器的行为,使用它代替 doc.replace("<br>", "<br/>") 也可以。然而,它要慢得多,而且它没有 Python 或 BeautifulSoup,所以它需要另一个 pip install html5lib 才能工作。