观察以下问题:
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将不匹配文本。找到标签并显示其文字会产生
<i>
>>> 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')
find_by_text(soup, 'Edit', 'a', href='/customer- menu/1/accounts/1/update')
问题是您的<a>标签内含<i>标签,但没有string您期望的属性。首先,让我们看一下text=""参数的find()作用。
<a>
string
text=""
find()
注意:text参数是一个旧名称,因为BeautifulSoup 4.4.0被称为string。
text
从文档:
尽管string用于查找字符串,但是您可以将其与查找标签的参数组合:Beautiful Soup将查找所有.string与您的string值匹配的标签。此代码查找其.string为“ Elsie”的标签: soup.find_all("a", string="Elsie") # [<a href="http://example.com/elsie" class="sister" id=”link1”>Elsie]
尽管string用于查找字符串,但是您可以将其与查找标签的参数组合:Beautiful Soup将查找所有.string与您的string值匹配的标签。此代码查找其.string为“ Elsie”的标签:
soup.find_all("a", string="Elsie") # [<a href="http://example.com/elsie" class="sister"
id=”link1”>Elsie]
现在,让我们看一下什么Tag是string属性(再次从文档中查看):
Tag
如果标记只有一个子代,并且该子代是NavigableString,则该子代可以作为.string使用: title_tag.string # u'The Dormouse's story'
如果标记只有一个子代,并且该子代是NavigableString,则该子代可以作为.string使用:
title_tag.string # u'The Dormouse's story'
(…)
如果标记包含多个内容,则不清楚.string应该指向什么,因此.string被定义为None: print(soup.html.string) # None
如果标记包含多个内容,则不清楚.string应该指向什么,因此.string被定义为None:
print(soup.html.string) # None
这正是您的情况。您的<a>标签包含一个文本 和 <i>标签。因此,None当尝试搜索字符串时,查找将获得,因此无法匹配。
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因此它应该足够快。
/customer-menu/1/accounts/1/update