4 votes

Comment utiliser Python/Beautiful Soup pour extraire du texte entre deux balises différentes ?

J'essaie d'extraire les titres des liens situés entre deux balises en gras sur une page HTML en utilisant Python/Beautiful Soup.

L'extrait HTML de ce que j'essaie d'extraire est le suivant :

<B>Heading Title 1:</B>&nbsp;<a href="link1">Title1</a>&nbsp;
<a href="link2">Title2</a>&nbsp;

&nbsp;

<B>Heading Title 2:</B>&nbsp;<a href="link3">Title3</a>&nbsp;
<a href="link4">Title4</a>&nbsp;
<a href="link5">Title5</a>&nbsp;

...

Je cherche spécifiquement à concaténer Title1 et Title2 (séparés par un délimiteur) en une entrée dans un objet de type liste, de même pour Title 3, Title 4 et Title 5, et ainsi de suite. (Un problème que je prévois est que le nombre de titres n'est pas le même pour chaque titre de rubrique).

J'ai essayé plusieurs approches, notamment

import requests, bs4, csv

res = requests.get('WEBSITE.html')

soup = BeautifulSoup(res.text, 'html.parser')

soupy4 = soup.select('a')

with open('output.csv', 'w') as f:
    writer = csv.writer(f, delimiter=',', lineterminator='\n')
    for line in soupy4:
        if 'common_element_link' in line['href']:
            categories.append(line.next_element)
            writer.writerow([categories])

Cependant, bien que cette méthode écrive tous les titres dans un fichier, elle le fait en ajoutant directement chaque titre supplémentaire de la manière suivante :

['Title1']
['Title1', 'Title2']
['Title1', 'Title2', 'Title3']
['Title1', 'Title2', 'Title3', 'Title4']
...

Idéalement, je voudrais que ce code fasse ce qui suit :

['Title1', 'Title2']
['Title3', 'Title4', 'Title5']
...

Je suis très novice en ce qui concerne les listes python et la programmation en général et je ne sais pas comment procéder. J'apprécierais tout retour d'information que vous pourriez avoir à ce sujet.

Merci de votre attention !

3voto

QHarr Points 24420

Vous pouvez utiliser nth-of-type , :not pseudo classe avec fratrie générale ~ combinateur. Comme le a sont tous des frères et sœurs, je crois, dans le html montré, j'utilise la balise b avec nth-of-type pour diviser le a entre les blocs. J'utilise la fonction :not à retirer ultérieurement a frères et sœurs de l'actuel.

from bs4 import BeautifulSoup as bs

html = '''
<B>Heading Title 1:</B>&nbsp;<a href="link1">Title1</a>&nbsp;
<a href="link2">Title2</a>&nbsp;

&nbsp;

<B>Heading Title 2:</B>&nbsp;<a href="link3">Title3</a>&nbsp;
<a href="link4">Title4</a>&nbsp;
<a href="link5">Title5</a>&nbsp;
'''
soup = bs(html, 'lxml')
items = soup.select('b:has(~a)')
length = len(items)
if length == 1:
    row = [item.text for item in soup.select('b ~ a')]
    print(row)
elif length > 1:
    for i in range(1, length + 1):
        row = [item.text for item in soup.select('b:nth-of-type(' + str(i) + ') ~ a:not(b:nth-of-type(' + str(i + 1) + ') ~ a)')]
        print(row)

de la production :

enter image description here

3voto

Ajax1234 Points 42210

Vous pouvez utiliser itertools.groupby pour combiner tous les textes de liens entre les titres :

import itertools, re
from bs4 import BeautifulSoup as soup
d = [[i.name, i] for i in soup(content, 'html.parser').find_all(re.compile('b|a'))]
new_d = [[a, list(b)] for a, b in itertools.groupby(d, key=lambda x:x[0] == 'b')]
final_result = [[c.text for _, c in b] for a, b in new_d if not a]

Sortie :

[['Title1', 'Title2'], ['Title3', 'Title4', 'Title5']]

L'original find_all fonctionne comme un "aplatisseur" et crée une liste de listes avec les noms et le contenu des balises cibles. itertools.groupby possède une clé qui regroupe les balises selon que le nom de la balise est ou non celui d'un contenu en gras. Ainsi, un dernier passage peut être effectué sur new_d , ignorant b et extraire le texte des liens.

0 votes

Pouvez-vous nous expliquer un peu comment cela fonctionne ?

1 votes

@QHarr L'original find_all fonctionne comme un "aplatisseur" et crée une liste de listes avec les noms et le contenu des balises cibles. itertools.groupby a une key qui regroupe les balises en fonction du fait que le nom de la balise correspond à un contenu en gras. Ainsi, un dernier passage peut être effectué sur new_d , ignorant b et extraire le texte des liens.

1 votes

@QHarr Merci, j'ai mis à jour le post avec l'explication.

2voto

Edo Edo Points 124

Le problème est que vous passez en boucle toutes les balises 'a' sans algorithme, est-ce que vous voulez concaténer tous les 3 liens ? vous pouvez mettre une boucle for à l'intérieur :

for line in alllinks:
    maintitle=''
    for i in xrange(3):
       maintitle+=line.text
    mainlist.append(maintitle)

recherche les blocs parents, puis boucle sur les blocs enfants imbriqués

sp=sp.find('div',id='whatever')
a=sp.select('a')  (this is recursive, finds all a tags in that div)
for tag in a:
    title=a.text.strip()
    url=a['href']

Je recommande de rechercher les balises html parentes des "liens" que vous souhaitez regrouper, au lieu de le faire de manière arbitraire en fonction de l'ordre de tous les liens.

p.s. vous pouvez également faire en sorte que find() soit récursif bien que ce ne soit pas recommandé en utilisant l'option recursive=True

additionner des chaînes de caractères : str3=str1+str2

llist=[]
for z in zrange(10)
   llist.append('bob'+str(z))

chaque élément de la liste a un index

print llist[1]

lire des listes, des chaînes de caractères, des dictionnaires

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X