请注意以下问题:
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')
答案 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)