使用BeautifulSoup来查找所有" ul"和" li"分子

时间:2018-05-14 19:59:20

标签: python beautifulsoup html-lists

我目前正在使用Python中的抓取脚本,我想将以下HTML响应映射到多列表或字典中(这没关系)。

我目前的代码是:

from bs4 import BeautifulSoup
from urllib.request import Request, urlopen

req     = Request("https://my.site.com/crawl", headers={'User-Agent': 'Mozilla/5.0'})
webpage = urlopen(req)
soup    = BeautifulSoup(webpage, 'html.parser')
ul      = soup.find('ul', {'class': ''})

运行此操作后,我得到以下结果存储在 ul 中:

<ul>
    <li><a class="reference" href="#ref1">Data1</a></li>
    <li><a class="reference" href="#ref2">Data2</a>
        <ul>
            <li><a class="reference" href="#ref3">Data3</a></li>
            <li><a class="reference" href="#ref4">Data4</a>
                <ul>
                    <li><a class="reference" href="#ref5"><span class="pre">Data5</span></a></li>
                    <li><a class="reference" href="#ref6"><span class="pre">Data6</span></a></li>
                    .
                    .
                    .
                </ul>
            </li>
        </ul>
    </li>
    <li><a class="reference" href="#ref7">Data7</a>
        <ul>
            <li><a class="reference" href="#ref8"><span class="pre">Data8</span></a></li>
            <li><a class="reference" href="#ref9"><span class="pre">Data9</span></a></li>
            .
            .
            .
        </ul>
    </li>
    <li><a class="reference" href="#ref10">Data10</a>
        <ul>
            <li><a class="reference" href="#ref11"><span class="pre">Data11</span></a></li>
            <li><a class="reference" href="#ref12">Data12</a></li>
        </ul>
    </li>
</ul>

由于这是一个外部网站,我无法控制列表中元素的ID或类。

似乎我无法理解这一点,是否有一种简单的方法将数据排列到列表或字典中?:

dict = {'Data1': {'href': 'ref1'}, 
        'Data2': {'href': 'ref2', {
                  'Data3': {'href': 'ref3'}, 
                  'Data4': {'href': 'ref4', {
                            'Data5': {'href': 'ref5'},
                            'Data6': {'href': 'ref6'},
                                    .
                                    .
                                    .                }
                                    }
                       }
               }
       }

我觉得这是一个繁琐的过程,但我没有看到任何其他方法。

非常感谢任何让我朝着正确方向前进的帮助!

干杯!

3 个答案:

答案 0 :(得分:1)

只需递归ul元素,提取所有包含文字的li元素的文本,如果有<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

这需要所有直接li元素;如果有<a>元素,则该锚元素的文本将变为键,并且我们将标记属性的副本存储为值(忽略任何class属性)。如果<ul>标记旁边还有 a元素,则会递归解析并将其作为children键添加到<a>的属性字典中1}}标签。

对于您的样本输入,这会产生:

>>> from pprint import pprint    
>>> pprint(parse_ul(soup.ul))
{'Data1': {'href': '#ref1'},
 'Data10': {'children': {'Data11': {'href': '#ref11'},
                         'Data12': {'href': '#ref12'}},
            'href': '#ref10'},
 'Data2': {'children': {'Data3': {'href': '#ref3'},
                        'Data4': {'children': {'Data5': {'href': '#ref5'},
                                               'Data6': {'href': '#ref6'}},
                                  'href': '#ref4'}},
           'href': '#ref2'},
 'Data7': {'children': {'Data8': {'href': '#ref8'}, 'Data9': {'href': '#ref9'}},
           'href': '#ref7'}}

答案 1 :(得分:1)

没有琐碎的方法可以做到这一点,但并不是那么麻烦。

例如,您可以递归地执行此操作,如下所示:

def make_data(ul):
    d = {}
    for a in ul.find_all('a'):
        d[a.text] = {'href': a.attrs['href']}
    lis = ul.find_all('li', recursive=False)
    children = {}
    for li in lis:
        child = li.ul
        if child:
            children[li.a.attrs['href']] = make_data(child)
    if children:
        d['children'] = children
    return d

(我必须给每个children个词组一个键,因为你真正想要的结构不是一个有效的词典。)

当然,您想要,例如,添加一些错误处理,但这应该足以让您入门。

答案 2 :(得分:0)

我真的很喜欢Martijn Pieters parse_ul(),但是我有一些代码不符合该解析器的规则,在单个<ul></ul>中有一个双<li> .. </li>,最后一部分是<a ... > text </a>前缀。 例如<li><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()使其接受此偏差并输出该偏差?

{'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'}
}