BeautifulSoup - 在标签内按文字搜索

时间:2015-08-12 07:26:09

标签: python regex beautifulsoup

请注意以下问题:

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'

右。根据{{​​3}},汤使用正则表达式的匹配函数,而不是搜索函数。所以我需要提供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')

4 个答案:

答案 0 :(得分:27)

问题是,您的<a>代码中包含<i>标记,并不具备您期望的string属性。首先让我们来看看text=""的{​​{1}}参数是什么。

注意:find()参数是一个旧名称,因为BeautifulSoup 4.4.0它被称为text

来自docs

  

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

string

现在让我们来看看soup.find_all("a", string="Elsie") # [<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>] Tag属性是什么(再次来自docs):

  

如果一个标签只有一个子节点,那个子节点是一个NavigableString,那么   child以.string:

的形式提供
string

(...)

  

如果标签包含多个内容,则不清楚是什么   .string应该引用,所以.string被定义为None:

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

这正是你的情况。您的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) 所以它应该足够快。

答案 1 :(得分:11)

如果True 文字包含&#34;编辑&#34>,您可以传递返回a的{​​{3}}到.find

In [51]: def Edit_in_text(tag):
   ....:     return tag.name == 'a' and 'Edit' in tag.text
   ....: 

In [52]: soup.find(Edit_in_text, href="/customer-menu/1/accounts/1/update")
Out[52]: 
<a href="/customer-menu/1/accounts/1/update">
<i class="fa fa-edit"></i> Edit
</a>

编辑:

您可以在函数中使用function方法代替text,这样可以得到相同的结果:

def Edit_in_text(tag):
    return tag.name == 'a' and 'Edit' in tag.get_text()

答案 2 :(得分:8)

使用lambda

在一行中
soup.find(lambda tag:tag.name=="a" and "Edit" in tag.text)

答案 3 :(得分:1)

从 bs4 4.7.1 开始,您可以使用 :contains css 伪类选择器来定位节点的文本

from bs4 import BeautifulSoup as BS

soup = BS("""
<a href="/customer-menu/1/accounts/1/update">
    Edit
</a>
""")
single = soup.select_one('a:contains("Edit")').text.strip()
multiple = [i.text.strip() for i in soup.select('a:contains("Edit")')]
print(single, '\n', multiple)
相关问题