I am trying to obtain different character's descriptions and habilities for a dataset. The problem I've encountered is that there seems to be a span tag within the h2 tag and in some cases a figure before de p tags. This is the format I'm facing:
<h2><span id="Apariencia">Apariencia</span></h2>
<figure style="width: 100px">
<p>...</p>
<p>...</p>
<p>...</p>
<h2><span id="Personalidad">Personalidad</span></h2>
<p>...</p>
<p>...</p>
<p>...</p>
I need to obtain the text in those paragraphs.
I have tried something like this but it obviously does not work.
import urllib.request
from bs4 import BeautifulSoup
fp = urllib.request.urlopen("https://jojo.fandom.com/es/wiki/Star_Platinum")
mybytes = fp.read()
html_doc = mybytes.decode("utf8")
fp.close()
soup = BeautifulSoup(html_doc, 'html.parser')
spans = soup.find_all('span', {"class": "mw-headline"})
for s in spans:
print(s.nextSibling.getText)
CodePudding user response:
You can search backwards for previous <h2>
and store result in the dictionary:
from bs4 import BeautifulSoup
html_doc = '''\
<h2><span id="Apariencia">Apariencia</span></h2>
<figure style="width: 100px">
<p>T1 ...</p>
<p>T2 ...</p>
<p>T3 ...</p>
<h2><span id="Personalidad">Personalidad</span></h2>
<p>T4 ...</p>
<p>T5 ...</p>
<p>T6 ...</p>'''
soup = BeautifulSoup(html_doc, 'html.parser')
out = {}
for p in soup.select('p'):
previous_h2 = p.find_previous('h2')
out.setdefault(previous_h2.text, []).append(p.text)
print(out)
Prints:
{
'Apariencia': ['T1 ...', 'T2 ...', 'T3 ...'],
'Personalidad': ['T4 ...', 'T5 ...', 'T6 ...']
}
CodePudding user response:
I think in this case, .select
with CSS selectors would be very useful.
To get all the section headers, you can use the selector h2:has(span.mw-headline[id])
; and to get a specific header by the id
attribute value (hId
below) of the span
nested within, you can use the selector hSel
below.
hId = 'Personalidad' # for example
hSel = f'h2:has(span.mw-headline[id="{hId}"])'
Then, to get all the tags after that header, you can use f'{hSel}~*'
, but you also need to filter out the tags after the next header to only get tags in that section, so the full selector would be
sSel = f'{hSel}~*:not({hSel}~h2~*):not(h2)'
[:not({hSel}~h2~*)
filters out the tags after the next header, and :not(h2)
filters out the next header tag itself.]
Making use of this, the function below returns a dictionary containing section header, text and html.
def get_wikiSection(header, wSoup, sec1Header='? Abstract ?'):
sSel = 'h2:has(span.mw-headline[id])'
sSel = f'*:has(~{sSel}):not({sSel}~*)'
if not header: hId = hSel = None # [first section has no header]
elif isinstance(header, str):
hId, hSel = header, f'h2:has(span.mw-headline[id="{header}"])'
header = wSoup.select_one(hSel)
if not header: return {'errorMsg': f'Not found: {hSel}'}
else: hId = header.select_one('span.mw-headline[id]')['id']
## header SHOULD BE: None/hId/a tag containing span.mw-headline[id] ##
if hId:
hSel = f'h2:has(span.mw-headline[id="{hId}"])'
sSel = f'{hSel}~*:not({hSel}~h2~*):not(h2)'
header = header.get_text(' ').strip()
else: header = sec1Header
sect = wSoup.select(sSel)
sText = '\n'.join([s.get_text(' ').strip() for s in sect])
sHtml = '\n'.join([''.join(s.prettify().splitlines()) for s in sect])
if not sect: sText = sHtml = None
return {'headerId': hId, 'sectionHeader': header,
'sectionText': sText, 'sectionHtml': sHtml}
For example, get_wikiSection('Personalidad', soup)
will return
{ 'headerId': 'Personalidad', 'sectionHeader': 'Personalidad', 'sectionText': 'Jotaro describió a Star Platinum como un ser muy violento. Suele ser silencioso, excepto cuando lanza golpes, gritando "ORA ORA ORA" en voz alta, bastante rápido y repetidamente. Con una cara relativamente humana ha demostrado tener expresiones tales como fruncir el ceño y sonreír.\nStar Platinum demuestra un tipo de interés en la auto-conservación, como se ve cuando detiene una bala que Jotaro dispara experimentalmente en su propia cabeza, protege a un Jotaro incapacitado de los ataques de DIO durante los efectos de su The World , y lo revive de cerca de la muerte directamente hacia latir su corazón (Sin embargo, considerando el papel pionero de Star Platinum en la serie, esta capacidad puede hablar principalmente de cualidades metafísicas o subconscientes genéricas para los usuarios de Stand).\nEn el manga original, Star Platinum desde el principio se ve con una sonrisa amplia y desconcertante. Más tarde, Star Platinum gana el rostro estoico de Jotaro, cualquier sonrisa futura va a advertir a la persona que se dirige a un gran dolor inminente.\nStar Platinum lleva el nombre de la carta del Tarot La Estrella , que simboliza el optimismo, el discernimiento y la esperanza.', 'sectionHtml': '<p> Jotaro describió a Star Platinum como un ser muy violento. Suele ser silencioso, excepto cuando lanza golpes, <a href="/es/wiki/Grito_de_Stand" title="Grito de Stand"> gritando </a> "ORA ORA ORA" en voz alta, bastante rápido y repetidamente. Con una cara relativamente humana ha demostrado tener expresiones tales como fruncir el ceño y sonreír.</p>\n<p> Star Platinum demuestra un tipo de interés en la auto-conservación, como se ve cuando detiene una bala que Jotaro dispara experimentalmente en su propia cabeza, protege a un Jotaro incapacitado de los ataques de <a href="/es/wiki/Dio_Brando" title="Dio Brando"> DIO </a> durante los efectos de su <a href="/es/wiki/The_World" title="The World"> The World </a> , y lo revive de cerca de la muerte directamente hacia latir su corazón (Sin embargo, considerando el papel pionero de Star Platinum en la serie, esta capacidad puede hablar principalmente de cualidades metafísicas o subconscientes genéricas para los usuarios de Stand).</p>\n<p> En el manga original, Star Platinum desde el principio se ve con una sonrisa amplia y desconcertante. Más tarde, Star Platinum gana el rostro estoico de Jotaro, cualquier sonrisa futura va a advertir a la persona que se dirige a un gran dolor inminente.</p>\n<p> Star Platinum lleva el nombre de la carta del Tarot <a href="http://en.wikipedia.org/wiki/es:La_Estrella_(Tarot)" title="wikipedia:es:La Estrella (Tarot)"> La Estrella </a> , que simboliza el optimismo, el discernimiento y la esperanza.</p>' }
If you want all the sections:
h2Tags = soup.select('h2:has(span.mw-headline[id])')
wikiSections = [get_wikiSection(h, soup) for h in [None] h2Tags]
The resulting list of dictionaries can also be converted to a pandas DataFrame as simply as pd.DataFrame(wikiSections)
.