小编典典

BeautifulSoup-通过标签内的文本搜索

python

观察以下问题:

import re
from bs4 import BeautifulSoup as BS

soup = BS("""
<a href="/customer-menu/1/accounts/1/update">
    Edit
</a>
""")

# This returns the <a> element
soup.find(
    'a',
    href="/customer-menu/1/accounts/1/update",
    text=re.compile(".*Edit.*")
)

soup = BS("""
<a href="/customer-menu/1/accounts/1/update">
    <i class="fa fa-edit"></i> Edit
</a>
""")

# This returns None
soup.find(
    'a',
    href="/customer-menu/1/accounts/1/update",
    text=re.compile(".*Edit.*")
)

由于某种原因,当<i>标签也存在时,BeautifulSoup将不匹配文本。找到标签并显示其文字会产生

>>> a2 = soup.find(
        'a',
        href="/customer-menu/1/accounts/1/update"
    )
>>> print(repr(a2.text))
'\n Edit\n'

对。根据文档,汤使用正则表达式的匹配功能,而不是搜索功能。所以我需要提供DOTALL标志:

pattern = re.compile('.*Edit.*')
pattern.match('\n Edit\n')  # Returns None

pattern = re.compile('.*Edit.*', flags=re.DOTALL)
pattern.match('\n Edit\n')  # Returns MatchObject

好的。看起来不错。让我们一起喝汤

soup = BS("""
<a href="/customer-menu/1/accounts/1/update">
    <i class="fa fa-edit"></i> Edit
</a>
""")

soup.find(
    'a',
    href="/customer-menu/1/accounts/1/update",
    text=re.compile(".*Edit.*", flags=re.DOTALL)
)  # Still return None... Why?!

编辑

我基于geckons的解决方案答案:我实现了以下帮助器:

import re

MATCH_ALL = r'.*'


def like(string):
    """
    Return a compiled regular expression that matches the given
    string with any prefix and postfix, e.g. if string = "hello",
    the returned regex matches r".*hello.*"
    """
    string_ = string
    if not isinstance(string_, str):
        string_ = str(string_)
    regex = MATCH_ALL + re.escape(string_) + MATCH_ALL
    return re.compile(regex, flags=re.DOTALL)


def find_by_text(soup, text, tag, **kwargs):
    """
    Find the tag in soup that matches all provided kwargs, and contains the
    text.

    If no match is found, return None.
    If more than one match is found, raise ValueError.
    """
    elements = soup.find_all(tag, **kwargs)
    matches = []
    for element in elements:
        if element.find(text=like(text)):
            matches.append(element)
    if len(matches) > 1:
        raise ValueError("Too many matches:\n" + "\n".join(matches))
    elif len(matches) == 0:
        return None
    else:
        return matches[0]

现在,当我想找到上面的元素时,我就运行 find_by_text(soup, 'Edit', 'a', href='/customer- menu/1/accounts/1/update')


阅读 176

收藏
2020-12-20

共1个答案

小编典典

问题是您的<a>标签内含<i>标签,但没有string您期望的属性。首先,让我们看一下text=""参数的find()作用。

注意:text参数是一个旧名称,因为BeautifulSoup 4.4.0被称为string

文档

尽管string用于查找字符串,但是您可以将其与查找标签的参数组合:Beautiful
Soup将查找所有.string与您的string值匹配的标签。此代码查找其.string为“ Elsie”的标签:

soup.find_all("a", string="Elsie")
# [<a href="http://example.com/elsie" class="sister"

id=”link1”>Elsie]

现在,让我们看一下什么Tagstring属性(再次从文档中查看):

如果标记只有一个子代,并且该子代是NavigableString,则该子代可以作为.string使用:

title_tag.string
# u'The Dormouse's story'

(…)

如果标记包含多个内容,则不清楚.string应该指向什么,因此.string被定义为None:

print(soup.html.string)
# None

这正是您的情况。您的<a>标签包含一个文本 <i>标签。因此,None当尝试搜索字符串时,查找将获得,因此无法匹配。

如何解决呢?

也许有更好的解决方案,但我可能会选择这样的方法:

import re
from bs4 import BeautifulSoup as BS

soup = BS("""
<a href="/customer-menu/1/accounts/1/update">
    <i class="fa fa-edit"></i> Edit
</a>
""")

links = soup.find_all('a', href="/customer-menu/1/accounts/1/update")

for link in links:
    if link.find(text=re.compile("Edit")):
        thelink = link
        break

print(thelink)

我认为没有太多链接指向,/customer-menu/1/accounts/1/update因此它应该足够快。

2020-12-20