如何按子元素使用 BeautifulSoup 4 对无序列表进行排序

How do I sort an unordered list using BeautifulSoup 4 by a child element

我是 Python 编码和 BeautifulSoup4 的新手。我在 HTML 中有一个列表需要排序,它遵循以下模式:

<div id="mgioLangSelector">
<ul id="mgioLangList">
<li><a href="" class="mgio-autonym"><span class="mgioAutonymNative" lang="am">አማርኛ</span><span class="mgioAutonymSeperator"> / </span><span class="mgioAutonymEnglish">Amharic</span</a></li>
<li><a href="" class="mgio-autonym"><span class="mgioAutonymNative" lang="hr">hrvatski</span><span class="mgioAutonymSeperator"> / </span>        <span class="mgioAutonymEnglish">Croatian</span</a></li>
<li><a href="" class="mgio-autonym"><span class="mgioAutonymNative" lang="cs">čeština</span><span class="mgioAutonymSeperator"> / </span><span class="mgioAutonymEnglish">Czech</span</a></li>
<li><a href="" class="mgio-autonym"><span class="mgioAutonymNative" lang="vi">tiếng Việt</span><span class="mgioAutonymSeperator"> / </span><span class="mgioAutonymEnglish">Vietnamese</span</a></li>
<li><a href="" class="mgio-autonym"><span class="mgioAutonymNative" lang="sq">shqip</span><span class="mgioAutonymSeperator"> / </span><span class="mgioAutonymEnglish">Albanian</span</a></li>
</ul>
</div>

我需要就地对列表进行排序并保存结果HTML。列表需要按照第三个span的内容排序,with class = mgioAutonymEnglish

我怀疑我需要使用具有适当按键功能的 sorted(),但结果一片空白。

我试过以下代码:

from bs4 import BeautifulSoup
from lxml import etree
soup = BeautifulSoup(open("interimResults.html"), 'lxml', from_encoding="utf-8")
matches = soup.find_all("span", attrs={"class": "mgioAutonymEnglish"})
sorted(matches, key=lambda elem: elem.text)

这将对跨度的内容进行排序,但不会对原始列表中的列表进行排序。我假设我需要更改 lambda 函数,但我目前不知所措。

我需要做什么或更改什么才能成功对列表进行排序,然后将这些更改保存在 HTML 文档中?

这实际上比您想象的要复杂一些,所以逐步完成它会有所帮助。

让我们从您的 soup:

开始
>>> soup
<html><body>
...
<div id="mgioLangSelector">
<ul id="mgioLangList">
<li><a class="mgio-autonym" href=""><span class="mgioAutonymNative" lang="am">አማርኛ</span><span class="mgioAutonymSeperator"> / </span><span class="mgioAutonymEnglish">Amharic</span></a></li>
<li><a class="mgio-autonym" href=""><span class="mgioAutonymNative" lang="hr">hrvatski</span><span class="mgioAutonymSeperator"> / </span> <span class="mgioAutonymEnglish">Croatian</span></a></li>
<li><a class="mgio-autonym" href=""><span class="mgioAutonymNative" lang="cs">čeština</span><span class="mgioAutonymSeperator"> / </span><span class="mgioAutonymEnglish">Czech</span></a></li>
<li><a class="mgio-autonym" href=""><span class="mgioAutonymNative" lang="vi">tiếng Việt</span><span class="mgioAutonymSeperator"> / </span><span class="mgioAutonymEnglish">Vietnamese</span></a></li>
<li><a class="mgio-autonym" href=""><span class="mgioAutonymNative" lang="sq">shqip</span><span class="mgioAutonymSeperator"> / </span><span class="mgioAutonymEnglish">Albanian</span></a></li>
</ul>
</div>
...
</body></html>

首先要做的是抓住 ul:

ul = soup.find(attrs={"id": "mgioLangList"})

现在我们可以 extract() 它的所有 li 元素并将它们存储在列表中:

items = [li.extract() for li in ul.find_all("li")]

要对列表进行排序,我们需要一个键。你走对了,但实际上它需要看起来像这样:

items.sort(key=lambda e: e.find(attrs={"class": "mgioAutonymEnglish"}).string)

现在我们有了 li 元素的排序列表,我们可以将它们重新插入到文档中。可能不是很明显的是 ul 不仅仅包含 li 元素——它们由换行符分隔,这些换行符仍然存在:

>>> ul
<ul id="mgioLangList">





</ul>

... 事实上,它们仍然是六个独立的 '\n' 字符串:

>>> ul.contents
['\n', '\n', '\n', '\n', '\n', '\n']

为了在这些换行符之间插入我们排序的 li 元素列表,我们可以使用内置的 zip() function and the insert_after() 方法:

for linebreak, li in reversed(list(zip(ul.contents, items))):
    linebreak.insert_after(li)

请注意,因为我们在迭代时通过插入元素来修改 ul.contents,所以有必要这样做 in reverse 这样 for 循环就不会结束被自己绊倒了。

等等...

>>> soup
<html><body>
...
<div id="mgioLangSelector">
<ul id="mgioLangList">
<li><a class="mgio-autonym" href=""><span class="mgioAutonymNative" lang="sq">shqip</span><span class="mgioAutonymSeperator"> / </span><span class="mgioAutonymEnglish">Albanian</span></a></li>
<li><a class="mgio-autonym" href=""><span class="mgioAutonymNative" lang="am">አማርኛ</span><span class="mgioAutonymSeperator"> / </span><span class="mgioAutonymEnglish">Amharic</span></a></li>
<li><a class="mgio-autonym" href=""><span class="mgioAutonymNative" lang="hr">hrvatski</span><span class="mgioAutonymSeperator"> / </span> <span class="mgioAutonymEnglish">Croatian</span></a></li>
<li><a class="mgio-autonym" href=""><span class="mgioAutonymNative" lang="cs">čeština</span><span class="mgioAutonymSeperator"> / </span><span class="mgioAutonymEnglish">Czech</span></a></li>
<li><a class="mgio-autonym" href=""><span class="mgioAutonymNative" lang="vi">tiếng Việt</span><span class="mgioAutonymSeperator"> / </span><span class="mgioAutonymEnglish">Vietnamese</span></a></li>
</ul>
</div>
...
</body></html>

这是全部内容:

ul = soup.find(attrs={"id": "mgioLangList"})
items = [li.extract() for li in ul.find_all("li")]
items.sort(key=lambda e: e.find(attrs={"class": "mgioAutonymEnglish"}).string)
for linebreak, li in reversed(list(zip(ul.contents, items))):
    linebreak.insert_after(li)