Использование BeautifulSoup для поиска всех элементов ul и li

В настоящее время я работаю над скриптом сканирования на 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>
    <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>

Поскольку это внешний сайт, я не могу контролировать идентификатор или класс элементов в списке.

Кажется, я не могу об этом разобраться, есть ли простой способ упорядочить данные в виде списка или диктовки ?:

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

Мне кажется, что это обременительный процесс, но я не вижу другого способа сделать это.

Любая помощь, которая поможет мне двигаться в правильном направлении, очень ценится!

Ваше здоровье!

Ваша структура HTML непоследовательна, вам не хватает хотя бы одного открывающего <li> (или слишком много закрывающих </li>). Убедитесь, что это чистый HTML. В словаре вам нужен ключ для проверенного значения словаря. Может, 'children'?

Martijn Pieters 14.05.2018 22:06

Это настоящие ценности, которые я хочу найти в будущем, например formsemailmodel.

magnusnn 14.05.2018 22:06

Это недопустимая структура данных. В каждом наборе фигурных скобок ваш href dicts имеет ключи, а ваш дочерний dict - нет. Это не диктат и не набор, это просто SyntaxError. Может хотите что-то вроде 'href': 'ref2', children: { …?

abarnert 14.05.2018 22:08

@MartijnPieters: да, вы правы насчет структуры HTML. Данных, которые я получаю, намного больше, и я попытался сократить их. Я предполагаю, что по ошибке были добавлены два дополнительных элемента <li>, теперь это исправлено в посте.

magnusnn 14.05.2018 22:15
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
4
4 455
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Просто выполните рекурсию элемента 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>.

Для вашего образца ввода это дает:

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

Нет способа сделать это с помощью банальный, но это не так уж и громоздко.

Например, вы можете сделать это рекурсивно, примерно так:

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 dicts ключ, потому что структура, которую вы действительно хотели, не является действительным dict.)

Конечно, вы захотите, например, добавить некоторую обработку ошибок, но этого должно быть достаточно, чтобы вы начали.

Спасибо, что помогли мне на этом пути, к сожалению, это привело к тому, что дети добавили несколько раз

magnusnn 14.05.2018 22:42

Мне очень нравится parse_ul () Martijn Pieters, но у меня есть код, который не соответствует правилам этого парсера, с двойным <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'}
}    

Другие вопросы по теме