Tengo una larga lista de encabezados seguidos de listas:

<h2>Header1</h2>
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
</ul>
<h2>Header2</h2>
<ul>
<li>D</li>
<li>E</li>
<li>F</li>
</ul>

Etcétera. ¿Cuál es la forma más compacta de tomar todas las listas después de cada encabezado usando BeautifulSoup y el encabezado correspondiente?

Entonces, idealmente, el resultado sería un diccionario, que se vería así:

{
'header1': ['A','B','C'],
'header2': ['D','E','F'],
}
2
David Pekker 2 oct. 2019 a las 22:12

3 respuestas

La mejor respuesta

Puede probar esto para comenzar y optimizar después de tener la idea.

import bs4

txt = '''\
<h2>Header1</h2>
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
</ul>
<h2>Header2</h2>
<ul>
<li>D</li>
<li>E</li>
<li>F</li>
</ul>
'''

soup = bs4.BeautifulSoup(txt)

output = dict()

key = []

for _ in soup.findAll('h2'):
  key.append(_.findAll(text=True)[0])

vec = [j.findAll('li') for j in soup.findAll('ul')]

for i in range(len(vec)):
  output[key[i]] = []
  for j in vec[i]:
    output[key[i]].append(j.findAll(text=True)[0])

print(output)

Salida

{'Header1': ['A', 'B', 'C'], 'Header2': ['D', 'E', 'F']}

Editado: código más corto y ordenado

from bs4 import BeautifulSoup

txt = '''\
<h2>Header1</h2>
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
</ul>
<h2>Header2</h2>
<ul>
<li>D</li>
<li>E</li>
<li>F</li>
</ul>
'''

soup = BeautifulSoup(txt, 'html.parser')
output = dict()
header = soup.find_all('h2')

for num in range(len(header)):
  temp = header[num]
  key = temp.find_all(text=True)[0]
  output[key] = []

  for item in (soup.find_all('ul')[num]).find_all('li'):
    output[key].append(item.find_all(text=True)[0])

print(output)

La salida será la misma

{'Header1': ['A', 'B', 'C'], 'Header2': ['D', 'E', 'F']}
3
QuantStats 2 oct. 2019 a las 20:28

Si los encabezados son únicos, puede usar :has con el combinador de hermanos adyacentes para asegurarse de que haya un ul inmediatamente después del h2, y luego :contains para obtener el {{X4 correcto }} combo después de h2 dentro de una comprensión dict. Más compacto que las otras soluciones hasta ahora. Requiere bs4 4.7.1+

from bs4 import BeautifulSoup as bs

html = '''<h2>Header1</h2>
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
</ul>
<h2>Header2</h2>
<ul>
<li>D</li>
<li>E</li>
<li>F</li>
</ul>'''

soup = bs(html, 'lxml')
d = {i.text:[j.text for j in soup.select(f'h2:contains("{i.text}") + ul li')] for i in soup.select('h2:has(+ul)')}
print(d)

enter image description here

2
QHarr 3 oct. 2019 a las 04:50

La solución @QuantStats funcionará, pero creo que esta puede ser un poco más compacta:

txt = '''\
<h2>Header1</h2>
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
</ul>
<h2>Header2</h2>
<ul>
<li>D</li>
<li>E</li>
<li>F</li>
</ul>
'''

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

output = {}
for i in soup.findAll('h2'):
    k = i.text
    ul = i.findNext('ul')
    v = [li.text for li in ul.findAll('li')]
    output[k] = v

print(output)
1
Philipp Chapkovski 2 oct. 2019 a las 23:29
58207964