我正在尝试解析一个简化如下所示的HTML页面:
<div class="anotherclass part"
<a href="http://example.com" >
<div class="column abc"><strike>£3.99</strike><br>£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的初学者,我已阅读并重新阅读了http://www.crummy.com/software/BeautifulSoup/bs3/documentation.html
上的beautifulsoup文档我有这段代码:
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
我想从类def
和ghi
中提取我认为我的脚本正确执行的任何文本。
我还想提取abc
中的两个价格,我的脚本目前以相当笨重的方式进行。有时会有两种价格,有时只有一种,有时甚至都没有。
最后,我想从我的脚本无法完成的类"A, List, Of, Terms, To, Extract"
中提取jkl
部分。我认为获取p
标记的字符串部分会起作用,但我无法理解为什么它不会。此部分中的日期始终与班级ghi
中的日期匹配,因此应该很容易替换/删除它。
有什么建议吗?感谢您!
答案 0 :(得分:2)
首先,如果您将convertEntities=bs.BeautifulSoup.HTML_ENTITIES
添加到
soup = bs.BeautifulSoup(html, convertEntities=bs.BeautifulSoup.HTML_ENTITIES)
然后将£
等html实体转换为相应的unicode字符,例如£
。这将允许您使用更简单的正则表达式来识别价格。
现在,在给定part
的情况下,您可以使用<div>
属性在contents
中找到包含价格的文字内容:
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
正在循环其价值观,它永远不会结束;它将继续按顺序产生值,然后在必要时重新开始 - 这正是我们想要的。
A, List, Of, Terms, To, Extract - 1 Feb 2013
可以通过使用contents
属性再次获得:
# 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}