Beautifulsoup получает текст на основе имени тега nextSibling

#python #html #beautifulsoup

#python #HTML #beautifulsoup

Вопрос:

Я очищаю несколько страниц, которые имеют одинаковый формат, но он немного меняется здесь и там, и нет классов, которые можно использовать для поиска того, что мне нужно.

Формат выглядит следующим образом:

 <div id="mainContent">

    <p>Some Text I don't want</p>
    <p>Some Text I don't want</p>
    <p>Some Text I don't want</p>
    <span> More text I don't want</span>
    <ul>...unordered-list items..</ul>

    <p>Text I WANT</p>
    <ol>...ordered-list items..</ol>

    <p>Text I WANT</p>
    <ol>...ordered-list items..</ol>

</div>
  

Количество упорядоченных / неупорядоченных списков и других тегов меняется в зависимости от страницы, но что остается неизменным, так это то, что я всегда хочу текст из <p> тега, который является предыдущим <ol> аналогом тега.

Что я пытаюсь (и не работает), так это:

 main = soup.find("div", {"id":"mainContent"})

for d in main.children:
    if d.name == 'p' and d.nextSibling.name == 'ol':
        print(d.text)
    else:
        print("fail")
  

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

 for d in main.children:
    if d.name == 'p':
        print(d.nextSibling.name)
    else:
        print("fail")
  

Результат этого выглядит примерно так:

 fail
None
fail
None
fail
None
fail
fail
fail
fail
fail
None
fail
  

и т.д…

Почему это работает не так, как я думаю? Как я могу получить текст из <p> элемента, только если следующий тег есть <ol> ?

Ответ №1:

Вам нужны только p теги, которые находятся перед ol тегом. ol Сначала найдите теги, а затем найдите предыдущие объекты тегов, которые в данном случае являются p тегом. Теперь ваш код не работает, потому что между Tag элементами, которые являются объектами типа NavigableString, есть новая строка. И d.nextSibling также выдает вам эти новые строки. Поэтому вам нужно проверить тип объекта здесь.

 from bs4 import Tag
# create soup
# find the ols
ols = soup.find_all('ol')
for ol in ols:
     prev = ol.previous_sibling
     while(not isinstance(prev, Tag)):
         prev = prev.previous_sibling
     print(prev.text)
  

Это даст вам нужный текст.

 Text I WANT
Text I WANT
  

Комментарии:

1. Ах, ха! Спасибо. Я только что узнал, что могу точно найти его, используя if d.nextSibling.nextSibling.name == 'ol': , но я не мог понять, почему мне нужно было использовать двух братьев и сестер, чтобы получить следующего брата.

2. Да, это тоже сработает в этом случае. Но что, если обхода только двух братьев и сестер недостаточно? Потому html что это может быть непредсказуемо. Я думаю, что проверка типов объектов здесь является хорошей идеей.

3. Я определенно согласен. Спасибо

Ответ №2:

Вы можете использовать селектор css, т.е. ul ~ p Найти все теги p, которым предшествует ul:

 html = """<div id="mainContent">

    <p>Some Text I don't want</p>
    <p>Some Text I don't want</p>
    <p>Some Text I don't want</p>
    <span> More text I don't want</span>
    <ul>...unordered-list items..</ul>

    <p>Text I WANT</p>
    <ol>...ordered-list items..</ol>

    <p>Text I WANT</p>
    <ol>...ordered-list items..</ol>

</div>"""

from bs4 import BeautifulSoup

soup = BeautifulSoup(html)


print([p.text for p in soup.select("#mainContent  ul ~ p")])
  

Что даст вам:

 ['Text I WANT', 'Text I WANT']
  

Или найдите ol, а затем найдите предыдущий родственный p:

 print([ol.find_previous_sibling("p").text for ol in soup.select("#mainContent ol")])
  

Что также даст вам:

 ['Text I WANT', 'Text I WANT']
  

Комментарии:

1. print([p.text for p in soup.select("#mainContent ul ~ p")]) работает, 1 для одной строки. Второй вариант, который вы дали print([ol.find_previous_sibling("p").text for ol in soup.select("#mainContent ol")]) , дает TypeError: 'NavigableString' object is not callable

2. @DjH, вы уверены, что использовали find_previous_sibling("p") , эта ошибка больше похожа на вашу previous_sibling("p") .

3. О, черт, ваше право. Не копировал и не вставлял его, просто сделал, и это работает >.>

4. Один из них — это атрибут, который получает непосредственный предыдущий аналог, другой — это метод, который ищет то, что вы передаете, ol.find_previous_sibling().text также будет работать в этом случае, но использование p гарантирует, что если что-то изменится, вы все равно получите предыдущий аналог p.