BeautifulSoup 网络爬虫 find_all(): 查找精确匹配

42 投票
5 回答
121989 浏览
提问于 2025-04-18 00:24

我正在使用Python和BeautifulSoup进行网页数据抓取。

假设我有以下的HTML代码需要抓取:

<body>
    <div class="product">Product 1</div>
    <div class="product">Product 2</div>
    <div class="product special">Product 3</div>
    <div class="product special">Product 4</div>
</body>

我想用BeautifulSoup找到只有那些属性为class="product"的产品(也就是只有产品1和产品2),而不是那些标记为“special”的产品。

如果我这样做:

result = soup.find_all('div', {'class': 'product'})

结果会包括所有的产品(1、2、3和4)。

我该怎么做才能找到class完全匹配“product”的产品呢?


我运行的代码:

from bs4 import BeautifulSoup
import re

text = """
<body>
    <div class="product">Product 1</div>
    <div class="product">Product 2</div>
    <div class="product special">Product 3</div>
    <div class="product special">Product 4</div>
</body>"""

soup = BeautifulSoup(text)
result = soup.findAll(attrs={'class': re.compile(r"^product$")})
print result

输出结果:

[<div class="product">Product 1</div>, <div class="product">Product 2</div>, <div class="product special">Product 3</div>, <div class="product special">Product 4</div>]

5 个回答

-1

把你的代码从下面这个:

result = soup.findAll(attrs={'class': re.compile(r"^product$")})

改成这个:

result = soup.find_all(attrs={'class': 'product'})

这样做的结果是会得到一个列表,你可以通过索引来访问列表里的内容。

0

你可以通过使用gazpacho来解决这个问题,只抓取产品1产品2,方法是要求完全匹配:

from gazpacho import Soup

html = """\
<body>
    <div class="product">Product 1</div>
    <div class="product">Product 2</div>
    <div class="product special">Product 3</div>
    <div class="product special">Product 4</div>
</body>
"""

soup = Soup(html)
divs = soup.find("div", {"class": "product"}, partial=False)
[div.text for div in divs]

输出结果正好是:

['Product 1', 'Product 2']
3
soup.findAll(attrs={'class': re.compile(r"^product$")})

这段代码的意思是,它会匹配所有类名的结尾不是product的内容。

5

你可以这样使用CSS选择器:

result = soup.select('div.product.special')

css选择器

72

在BeautifulSoup 4中,class属性(还有一些其他属性,比如accesskey和表格单元格的headers属性)被当作一个集合来处理;也就是说,你是根据属性中列出的每个元素来进行匹配的。这是符合HTML标准的。

因此,你不能只限制搜索到一个类。

你需要在这里使用一个自定义函数来匹配类:

result = soup.find_all(lambda tag: tag.name == 'div' and 
                                   tag.get('class') == ['product'])

我使用了一个lambda来创建一个匿名函数;每个标签是根据名称进行匹配的(必须是'div'),而且类属性必须完全等于列表['product'];也就是说,只有一个值。

示例:

>>> from bs4 import BeautifulSoup
>>> text = """
... <body>
...     <div class="product">Product 1</div>
...     <div class="product">Product 2</div>
...     <div class="product special">Product 3</div>
...     <div class="product special">Product 4</div>
... </body>"""
>>> soup = BeautifulSoup(text)
>>> soup.find_all(lambda tag: tag.name == 'div' and tag.get('class') == ['product'])
[<div class="product">Product 1</div>, <div class="product">Product 2</div>]

为了完整起见,这里列出了所有这样的集合属性,来自BeautifulSoup的源代码:

# The HTML standard defines these attributes as containing a
# space-separated list of values, not a single value. That is,
# class="foo bar" means that the 'class' attribute has two values,
# 'foo' and 'bar', not the single value 'foo bar'.  When we
# encounter one of these attributes, we will parse its value into
# a list of values if possible. Upon output, the list will be
# converted back into a string.
cdata_list_attributes = {
    "*" : ['class', 'accesskey', 'dropzone'],
    "a" : ['rel', 'rev'],
    "link" :  ['rel', 'rev'],
    "td" : ["headers"],
    "th" : ["headers"],
    "td" : ["headers"],
    "form" : ["accept-charset"],
    "object" : ["archive"],

    # These are HTML5 specific, as are *.accesskey and *.dropzone above.
    "area" : ["rel"],
    "icon" : ["sizes"],
    "iframe" : ["sandbox"],
    "output" : ["for"],
    }

撰写回答