【问题标题】:Using BeautifulSoup in order to find all "ul" and "li" elements使用 BeautifulSoup 查找所有“ul”和“li”元素
【发布时间】:2018-05-14 19:59:20
【问题描述】:

我目前正在使用 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'},
                                    .
                                    .
                                    .                }
                                    }
                       }
               }
       }

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

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

干杯!

【问题讨论】:

  • 您的 HTML 结构不一致,您至少缺少一个开头的 &lt;li&gt;(或者结尾的 &lt;/li&gt; 太多)。请确保它是干净的 HTML。在字典中,您确实需要一个用于测试字典值的键。也许'children'
  • 它们是我想在未来查找的真实值,例如formsemailmodel.
  • 这不是一个有效的数据结构。在每组大括号中,您的 href 字典都有键,但您的孩子字典没有。这不是一个字典,也不是一个集合,它只是一个 SyntaxError。也许你想要'href': 'ref2', children: { … 之类的东西?
  • @MartijnPieters 是的,您对 HTML 结构的看法是正确的。我收到的数据更多,我试图缩短它。我猜是错误地添加了两个额外的&lt;li&gt;-elements,这已经在帖子中修复了。

标签: python beautifulsoup html-lists


【解决方案1】:

只需递归ul元素,提取所有有文本的li元素的文本,如果有&lt;ul&gt;元素则递归更深:

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 元素;如果存在&lt;a&gt; 元素,则该锚元素的文本将转换为键,并且我们将标记属性的副本存储为值(忽略任何class 属性)。如果a 标记旁边有一个&lt;ul&gt; 元素,它会被递归解析并作为children 键添加到&lt;a&gt; 标记的属性字典中。

对于您的示例输入,这会产生:

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

【讨论】:

    【解决方案2】:

    没有简单的方法可以做到这一点,但也不是那么麻烦。

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

    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 dict 一个键,因为你真正想要的结构不是有效的 dict。)

    当然你会想要,例如,添加一些错误处理,但这应该足以让你开始。

    【讨论】:

    • 感谢一路上对我的帮助,不幸的是最终多次添加了孩子
    【解决方案3】:

    我真的很喜欢 Martijn Pieters parse_ul(),但我有一些代码不遵循此解析器的规则,在单个 &lt;li&gt; .. &lt;/li&gt; 内有一个双 &lt;ul&gt;&lt;/ul&gt;,其中最后一个部分有一个 &lt;a ... &gt; text &lt;/a&gt; 前缀. 比如&lt;li&gt;&lt;a ...&gt; &lt;ul&gt; &lt;/ul&gt; &lt;a..&gt;&lt;/a&gt;&lt;ul&gt; &lt;/ul&gt; &lt;/li&gt;

    见下文

    <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)    
    

    将生成以下输出,其中缺少 &lt;a..&gt;&lt;/a&gt;&lt;ul&gt; &lt;/ul&gt; 中包含的第二部分:

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

    【讨论】:

      猜你喜欢
      • 2020-09-09
      • 1970-01-01
      • 1970-01-01
      • 2011-10-29
      • 1970-01-01
      • 2019-10-18
      • 1970-01-01
      • 2012-09-26
      相关资源
      最近更新 更多