【问题标题】:Python nested html tags with Beautifulsoup带有 Beautifulsoup 的 Python 嵌套 html 标签
【发布时间】:2015-05-08 10:06:20
【问题描述】:

我正在尝试从嵌套的 html 代码中获取所有的 href URL:

...
<li class="dropdown">
<a href="#" class="dropdown-toggle wide-nav-link" data-toggle="dropdown">TEXT_1 <b class="caret"></b></a>
<ul class="dropdown-menu">
<li class="class_A"><a title="Title_1" href="http://www.customurl_1.com">Title_1</a></li>
<li class="class_B"><a title="Title_2" href="http://www.customurl_2.com">Title_2</a></li>
...
<li class="class_A"><a title="Title_X" href="http://www.customurl_X.com">Title_X</a></li>
</ul>
</li>
...
<li class="dropdown">
<a href="#" class="dropdown-toggle wide-nav-link" data-toggle="dropdown">TEXT_2 <b class="caret"></b></a>
<ul class="dropdown-menu">
<li class="class_A"><a title="Title_1" href="http://www.customurl_1.com">Title_1</a></li>
<li class="class_B"><a title="Title_2" href="http://www.customurl_2.com">Title_2</a></li>
...
<li class="class_A"><a title="Title_X" href="http://www.customurl_X.com">Title_X</a></li>
</ul>
</li>
...

在原始 html 代码中,大约有 15 个“li”块,类“dropdown”, 但我只想从带有 text = TEXT_1 的块中获取 URL。 可以用 BeautifulSoup 抓取所有这些嵌套的 url 吗?

感谢您的帮助

【问题讨论】:

  • 你不应该使用正则表达式;使用HTML parser
  • 我已经尝试过使用正则表达式,它可以工作,但结果并不好。
  • 不管你的船是什么 - 你可以使用正则表达式,但你不应该
  • 是的,你可以用 BeautifulSoup 做你想做的事,你应该尝试一下,如果遇到困难就回来。
  • 您应该使用允许使用 XPath 查询您的 html 的 lxml

标签: python html regex beautifulsoup


【解决方案1】:

lxml 和 Xpath 的示例:

from lxml import etree
from io import StringIO

parser = etree.HTMLParser()
tree   = etree.parse(StringIO(html), parser)
hrefs = tree.xpath('//li[@class="dropdown" and a[starts-with(.,"TEXT_1")]]/ul[@class="dropdown-menu"]/li/a/@href')

print hrefs

html 是一个包含 html 内容的 unicode 字符串。结果:

['http://www.customurl_1.com', 'http://www.customurl_2.com', 'http://www.customurl_X.com']

注意:我在 XPath 查询中使用starts-with 函数更精确,但如果TEXT_1 并不总是在文本节点的开头,您可以以相同的方式使用contains

查询详情:

//              # anywhere in the domtree
li              # a li tag with the following conditions:
[                               # (opening condition bracket for li)
    @class="dropdown"           # li has a class attribute equal to "dropdown" 
  and                           # and
    a                           # a child tag "a"
    [                           # (open a condition for "a")
        starts-with(.,"TEXT_1") # that the text starts with "TEXT_1"
    ]                           # (close a condition for "a")
]                               # (close the condition for li)
/                            # li's child (/ stands for immediate descendant)
ul[@class="dropdown-menu"]   # "ul" with class equal to "dropdown-menu"
/li                          # "li" children of "ul"
/a                           # "a" children of "li"
/@href                       # href attributes children of "a"

【讨论】:

  • 完美!!谢谢!
【解决方案2】:

虽然不如 Xpath 优雅,但您始终可以使用日常 Python 迭代来编写逻辑。 BeautifulSoup 允许将一个函数作为过滤器传递给find_all,以便在您遇到诸如此类的复杂情况时。

from bs4 import BeautifulSoup

html_doc = """<html>..."""
soup = BeautifulSoup(html_doc)

def matches_block(tag):
    return matches_dropdown(tag) and tag.find(matches_text) != None

def matches_dropdown(tag):
    return tag.name == 'li' and tag.has_attr('class') and 'dropdown' in tag['class']

def matches_text(tag):
    return tag.name == 'a' and tag.get_text().startswith('TEXT_1')

for li in soup.find_all(matches_block):
    for ul in li.find_all('ul', class_='dropdown-menu'):
        for a in ul.find_all('a'):
            if a.has_attr('href'):
                print (a['href'])

【讨论】: