BeautifulSoup: 查找类名:AND + NOT

5

我正在HTML中使用两个不同的div标签:

<div class="ABC BCD CDE123">

<div class="ABC BCD CDE234">

<div class="ABC BCD CDE345">

and

<div class="ABC XYZ BCD">

我希望使用BeautifulSoup4选择所有包含ABC和BCD标签的内容,但不包括XYZ类。

我已经了解到以下方法:

soup.find_all('div', class_=['ABC','BCD'])

搜索时使用OR(因此必须存在ABC或BCD)。

我也知道这里的方法:

def myfunction(theclass):
    return theclass is not None and len(theclass)=5
soup.find_all('div', class_=myfunction)

这将返回所有类名长度为5的div元素。

然后我尝试使用以下方法解决我的问题:

soup.find_all('div', class_ = lambda x: x and 'ABC' and 'BCD' in x.split() and x and 'XYZ' not in x.split())

但是这并没有起作用。 因此,我尝试使用以下方法进行调试:
def myfunction(theclass):
    print theclass
    return True
soup.find_all('div', class_=myfunction)

问题似乎是从这样的标签开始的:

这个标签看起来像是:

<div class="ABC BCD CDE123">

只有“ABC”被传递给myfunction,因此theclass = 'ABC'而不是我预期的theclass ='ABC BCD CDE123'。这也是我猜测lambda函数失败的原因。
有什么线索可以根据我的要求过滤标签吗?
我想使用BeautifullSoup4选择所有包含ABC和BCD的标签,但不包含XYZ类。

我当然可以用一些丑陋的多行代码解决这个问题,但我在这里寻找一个漂亮而干净的解决方案。 - stoney
Lxml + cssselect 可以让你做到: .ABC.BCD:not(.XYZ) - 不过我不确定 BS4 是否支持。 - pguardiario
3个回答

3
你的方法是正确的,但你漏掉了一点。BeautifulSoup会将属性class的值转换为列表。
例如:
>>> soup.div['class']
['ABC', 'BCD', 'CDE123']

不要使用x.split(),直接检查该值是否在列表中。

代码:

html = '''
<div class="ABC BCD CDE123"></div>
<div class="ABC BCD CDE234"></div>
<div class="ABC BCD CDE345"></div>
<div class="ABC XYZ BCD"></div>'''

soup = BeautifulSoup(html, 'html.parser')
print(soup.find_all('div', class_=lambda c: <strong>'ABC' in c and 'BCD' in c and 'XYZ' not in c</strong>))

输出:

[<div class="ABC BCD CDE123"></div>,
 <div class="ABC BCD CDE234"></div>,
 <div class="ABC BCD CDE345"></div>]

2

可以使用SET来完成这个操作。获取所有带有ABC和BCD类的结果列表。将结果封装在Python SET中。对XYZ执行相同的操作。现在您将拥有两个SET,一个用于ABC和BCD,另一个用于XYZ。对两个SET进行减法运算。

在搜索列表中使用ABC和BCD时,请使用select函数而不是find_all函数。

from bs4 import BeautifulSoup

data = '''
<div class="ABC BCD CDE123"></div>
<div class="ABC BCD CDE234"></div>
<div class="ABC BCD CDE345"></div>
<div class="ABC XYZ BCD"></div>
<div class="ABC XYZ AAC"></div>
<div class="ABC AAC"></div>
'''

soup = BeautifulSoup(data)
ABC_BCD = set(soup.select('div.ABC.BCD'))
XYZ     = set(soup.select('div.XYZ'))
result = ABC_BCD - XYZ
for element in result:
    print element

输出

<div class="ABC BCD CDE234"></div>
<div class="ABC BCD CDE123"></div>
<div class="ABC BCD CDE345"></div>

使用相同的代码,使用find_all函数。
ABC_BCD = set(soup.find_all('div', class_=['ABC','BCD']))
XYZ     = set(soup.find_all('div', class_=['XYZ']))
result = ABC-BCD
for element in result:
    print element

输出结果为

<div class="ABC BCD CDE234"></div>
<div class="ABC AAC"></div> #This is what we dont need
<div class="ABC BCD CDE123"></div>
<div class="ABC BCD CDE345"></div>

谢谢,你的第一个代码段有一个小错误: 应该是 "for element in result: print element" 才能得到你在下一个代码段中所述的结果。关于使用find_all的方法,选择器应该是 "soup.find_all('div', class_=['ABC','BCD'])" 这样选择的是或关系,而不是我们想要的。 (顺便说一句,在那里也应该有"result = ABC_BCD - XYZ") 但我喜欢将结果放入SET中然后进行减法运算的方法。 - stoney

1

我不知道有没有一步解决方案,但是你可以使用CSS选择器,然后过滤掉你不想要的元素。

from bs4 import BeautifulSoup

html = '''
<div class="ABC BCD CDE123"></div>
<div class="ABC BCD CDE234"></div>
<div class="ABC BCD CDE345"></div>
<div class="ABC XYZ BCD"></div>
<div class="ABC XYZ AAC"></div>
<div class="ABC AAC"></div>
'''

soup = BeautifulSoup(html, "html.parser")
divs = soup.select('div.ABC.BCD')
result = [div for div in divs if "XYZ" not in div['class']]

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