首页 > 解决方案 > 使用 BeautifulSoup 查找所有具有微小偏差的“ul”和“li”元素

问题描述

在 python 的爬虫脚本中,我使用 BeautifulSoup 解析一个 html 多列表,几乎类似于问题 { Using BeautifulSoup in order to find all "ul" and "li" elements } 并由 Martijn Pieters 使用他的 python 函数 parse_ul 回答()。

def parse_ul(elem):
result = {}
for sub in elem.find_all('li', recursive=False):
    if sub.a is None:
        continue
    data = {k: v for k, v in sub.a.attrs.items() if k != 'class'}
    if sub.ul is not None:
        # recurse down
        data['children'] = parse_ul(sub.ul)
    result[sub.a.get_text(strip=True)] = data
return result      

我需要解析一个不遵循 Martijn Pieters 解析器规则的多列表。这个 html 多列表<ul></ul>在一个单中包含一个双精度<li> .. </li>,其中最后一个部分有一个<a ... > text </a>前缀(不带<li>

例如<li><a ...></a><ul> </ul> <a..></a><ul> </ul> </li>

见下文

<ul>
  <li><a class="ref" href="#ref1">Data1</a></li>
  <li><a class="ref" href="#ref2">Data2</a>
    <ul>
      <li><a class="ref" href="#ref4">Data4</a>
        <ul>
          <li><a class="ref" href="#ref5"><span class="pre">Data5</span></a>/li>
          <li><a class="ref" href="#ref6"><span class="pre">Data6</span></a></li>
           .
           .
        </ul>
   <!-- a-tag without preceding <li> tag  -->
        <a class="ref" href="#ref4a">Data4a</a>
        <ul>
          <li><a class="ref" href="#ref5a"><span class="pre">Data5a</span></a></li>
          <li><a class="ref" href="#ref6a"><span class="pre">Data6a</span></a></li>
           .
           .
        </ul>               
      </li>
    </ul>
  </li>
   .
   .
</ul>

我不知道如何更改 parse_ul() 以便它接受这种偏差并输出这个?Martijn Pieters 解析器非常出色,我的问题也必须有一个出色的解决方案:-)

{'Data1': {'href': '#ref1'},
 'Data2': {'children': {'Data4': {'children': {'Data5': {'href': '#ref5'},
                                               'Data6': {'href': '#ref6'}}},
                                 'href': '#ref4'},
                       {'Data4a': {'children':{'Data5a': {'href': '#ref5a'},
                                               'Data6a': {'href': '#ref6a'}}},
                                 'href': '#ref4a'},
           'href': '#ref2'}
}    

以下脚本:

from bs4 import BeautifulSoup
import pprint

pp = pprint.PrettyPrinter(indent=4)     # Init pritty print (pprint)
soup = BeautifulSoup(html_contents, 'lxml')
menu_dict = parse_ul(soup.ul)
pp.pprint(menu_dict)    

将生成以下输出,其中缺少包含在中的第二部分<a..></a><ul> </ul>

{'Data1': {'href': '#ref1'},
 'Data2': {'children': {'Data4': {'children': {'Data5': {'href': '#ref5'},
                                               'Data6': {'href': '#ref6'}}},
                                 'href': '#ref4'},
           'href': '#ref2'}
}    

标签: pythonhtmllistbeautifulsouphtml-parsing

解决方案


一种可能的解决方案是在解析之前修改 HTML 代码。

这将找到所有<a>具有先前兄弟姐妹的标签<ul>并将它们插入到适当的位置:

txt = '''<ul>
  <li><a class="ref" href="#ref1">Data1</a></li>
  <li><a class="ref" href="#ref2">Data2</a>
    <ul>
      <li><a class="ref" href="#ref4">Data4</a>
        <ul>
          <li><a class="ref" href="#ref5"><span class="pre">Data5</span></a>/li>
          <li><a class="ref" href="#ref6"><span class="pre">Data6</span></a></li>
        </ul>

        <!-- a-tag without preceding <li> tag  -->
        <a class="ref" href="#ref4a">Data4a</a>
        <ul>
          <li><a class="ref" href="#ref5a"><span class="pre">Data5a</span></a></li>
          <li><a class="ref" href="#ref6a"><span class="pre">Data6a</span></a></li>
        </ul>
      </li>
    </ul>
  </li>
</ul>'''

from bs4 import BeautifulSoup

soup = BeautifulSoup(txt, 'html.parser')

# find "lone" <A>+<UL> tags, wrap them in new <LI> tag and append the li tag after parent <LI>:
for a in soup.select('a'):
    prev = a.find_previous_sibling()
    if prev and prev.name == 'ul':
        parent_li = a.parent
        next_ul = a.find_next('ul')

        new_li = soup.new_tag("li")
        new_li.append(a)
        new_li.append(next_ul)

        parent_li.insert_after(new_li)


# this is unchanged code from your question:
def parse_ul(elem):
    result = {}
    for sub in elem.find_all('li', recursive=False):
        if sub.a is None:
            continue
        data = {k: v for k, v in sub.a.attrs.items() if k != 'class'}
        if sub.ul is not None:
            # recurse down
            data['children'] = parse_ul(sub.ul)
        result[sub.a.get_text(strip=True)] = data
    return result

from pprint import pprint
pprint(parse_ul(soup.ul))

印刷:

{'Data1': {'href': '#ref1'},
 'Data2': {'children': {'Data4': {'children': {'Data5': {'href': '#ref5'}},
                                  'href': '#ref4'},
                        'Data4a': {'children': {'Data5a': {'href': '#ref5a'},
                                                'Data6a': {'href': '#ref6a'}},
                                   'href': '#ref4a'}},
           'href': '#ref2'}}

推荐阅读