如何使用Python中的Beautifulsoup从HTML中提取标签

3
我正在尝试解析一个HTML页面,简化后看起来像这样:
<div class="anotherclass part"
  <a href="http://example.com" >
    <div class="column abc"><strike>&#163;3.99</strike><br>&#163;3.59</div>
    <div class="column def"></div>
    <div class="column ghi">1 Feb 2013</div>
    <div class="column jkl">
      <h4>A title</h4>
      <p>
        <img class="image" src="http://example.com/image.jpg">A, List, Of, Terms, To, Extract - 1 Feb 2013</p>
    </div>
  </a>
</div>

我是一个Python编程的初学者,我已经阅读并反复阅读了BeautifulSoup文档http://www.crummy.com/software/BeautifulSoup/bs3/documentation.html
我得到了以下代码:
from BeautifulSoup import BeautifulSoup

with open("file.html") as fp:
  html = fp.read()

soup = BeautifulSoup(html)

parts = soup.findAll('a', attrs={"class":re.compile('part'), re.IGNORECASE} )
for part in parts:
  mypart={}

  # ghi
  mypart['ghi'] = part.find(attrs={"class": re.compile('ghi')} ).string
  # def
  mypart['def'] = part.find(attrs={"class": re.compile('def')} ).string
  # h4
  mypart['title'] = part.find('h4').string

  # jkl
  mypart['other'] = part.find('p').string

  # abc
  pattern = re.compile( r'\&\#163\;(\d{1,}\.?\d{2}?)' )
  theprices = re.findall( pattern, str(part) )
  if len(theprices) == 2:
    mypart['price'] = theprices[1]
    mypart['rrp'] = theprices[0]
  elif len(theprices) == 1:
    mypart['price'] = theprices[0]
    mypart['rrp'] = theprices[0]
  else:
    mypart['price'] = None
    mypart['rrp'] = None

我希望从类defghi中提取任何文本,我认为我的脚本已经正确实现了。
同时,我也想从abc中提取两个价格,但目前我的脚本做得相当笨拙。这部分有时会有两个价格,有时只有一个,有时没有。
最后,我想从类jkl中提取"A, List, Of, Terms, To, Extract"部分,但我的脚本失败了。我认为获取p标签的字符串部分应该可以工作,但我无法理解为什么它不起作用。这一部分中的日期始终与类ghi中的日期相匹配,因此应该很容易替换/删除它。
有什么建议吗?谢谢!
1个回答

2

首先,如果您将 convertEntities=bs.BeautifulSoup.HTML_ENTITIES 添加到

soup = bs.BeautifulSoup(html, convertEntities=bs.BeautifulSoup.HTML_ENTITIES)

接下来,HTML实体,如&#163;将会被转换为相应的Unicode字符,如£。这将使您能够使用更简单的正则表达式来识别价格。


现在,通过part,您可以使用其contents属性找到包含价格的<div>元素中的文本内容:

In [37]: part.find(attrs={"class": re.compile('abc')}).contents
Out[37]: [<strike>£3.99</strike>, <br />, u'\xa33.59']

我们需要做的就是从每个项目中提取数字,如果没有数字则跳过:
def parse_price(text):
    try:
        return float(re.search(r'\d*\.\d+', text).group())
    except (TypeError, ValueError, AttributeError):
        return None

price = []
for item in part.find(attrs={"class": re.compile('abc')}).contents:
    item = parse_price(item.string)
    if item:
        price.append(item)

此时,price 将是一个包含 0、1 或 2 个浮点数的列表。我们想表达的是:
mypart['rrp'], mypart['price'] = price

但是如果price[]或只包含一个元素,则这种方法不起作用。

使用if..else处理三种情况的方法可以,它是最直接和可能是最可读的方法。但也有点乏味。如果你想要更简洁的东西,你可以这样做:

由于我们希望在price只包含一个元素时重复相同的价格,所以你可能会考虑itertools.cycle

price为空列表[]的情况下,我们想要itertools.cycle([None]),但否则我们可以使用itertools.cycle(price)

因此,为了将两种情况合并为一个表达式,我们可以使用:

price = itertools.cycle(price or [None])
mypart['rrp'], mypart['price'] = next(price), next(price)
next函数逐一获取迭代器price中的值。由于price正在循环遍历其值,因此它永远不会结束;它只会按顺序产生值,然后在必要时重新开始-这正是我们想要的。
可以通过使用contents属性再次获取A,List,Of,Terms,To,Extract - 1 Feb 2013
# jkl
mypart['other'] = [item for item in part.find('p').contents
                   if not isinstance(item, bs.Tag) and item.string.strip()]

那么,完整的可运行代码将如下所示:

import BeautifulSoup as bs
import os
import re
import itertools as IT

def parse_price(text):
    try:
        return float(re.search(r'\d*\.\d+', text).group())
    except (TypeError, ValueError, AttributeError):
        return None

filename = os.path.expanduser("~/tmp/file.html")
with open(filename) as fp:
    html = fp.read()

soup = bs.BeautifulSoup(html, convertEntities=bs.BeautifulSoup.HTML_ENTITIES)

for part in soup.findAll('div', attrs={"class": re.compile('(?i)part')}):
    mypart = {}
    # abc
    price = []
    for item in part.find(attrs={"class": re.compile('abc')}).contents:
        item = parse_price(item.string)
        if item:
            price.append(item)

    price = IT.cycle(price or [None])
    mypart['rrp'], mypart['price'] = next(price), next(price)

    # jkl
    mypart['other'] = [item for item in part.find('p').contents
                       if not isinstance(item, bs.Tag) and item.string.strip()]

    print(mypart)

这将产生

{'price': 3.59, 'other': [u'A, List, Of, Terms, To, Extract - 1 Feb 2013'], 'rrp': 3.99}

这是一个非常漂亮的解决方案,unutbu :-) 非常感谢您的时间和努力。对于我来说,有很多东西需要消化,我从您的答案中学到了很多东西。非常感激。 - user1464409
只需要添加:真是令人印象深刻的努力——让我指出你代码中潜在的一个小错误:在parse_price(text)内部,你使用了 item.string 而不是 text - tzelleke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接