# Fichier: python_cheats/cheatsheets/beautifulsoup.txt
# Cheatsheet BeautifulSoup - Guide Complet



[OK] INSTALLATION & IMPORT


# Installation
pip install beautifulsoup4
pip install lxml  # Parser rapide (recommandé)
pip install html5lib  # Parser permissif

# Import basique
from bs4 import BeautifulSoup
import requests

# Import avec alias
from bs4 import BeautifulSoup as bs

# Import des types
from bs4 import Tag, NavigableString, Comment


[OK] CRÉATION D'OBJET BeautifulSoup


# Depuis string HTML
html = "<html><body><p>Hello</p></body></html>"
soup = BeautifulSoup(html, 'html.parser')

# Depuis fichier
with open('page.html', 'r', encoding='utf-8') as f:
    soup = BeautifulSoup(f, 'html.parser')

# Depuis requête web
response = requests.get('https://example.com')
soup = BeautifulSoup(response.content, 'html.parser')
# ou
soup = BeautifulSoup(response.text, 'html.parser')

# Avec encoding spécifique
soup = BeautifulSoup(html, 'html.parser', from_encoding='utf-8')


[OK] PARSERS DISPONIBLES


# html.parser (built-in, pas de dépendance)
soup = BeautifulSoup(html, 'html.parser')

# lxml HTML (rapide, flexible)
soup = BeautifulSoup(html, 'lxml')

# lxml XML (pour XML)
soup = BeautifulSoup(xml, 'lxml-xml')
soup = BeautifulSoup(xml, 'xml')

# html5lib (lent mais permissif, comme navigateur)
soup = BeautifulSoup(html, 'html5lib')

# Comparaison:
# html.parser: Rapide, décent, built-in
# lxml: Plus rapide, très tolérant
# html5lib: Le plus lent, le plus permissif
# xml: Pour documents XML stricts


[OK] NAVIGATION BASIQUE


# Accès direct par tag
soup.title          # Premier tag <title>
soup.body           # Premier tag <body>
soup.p              # Premier tag <p>
soup.a              # Premier tag <a>

# Nom du tag
soup.title.name     # 'title'

# Texte du tag
soup.title.string   # Contenu texte
soup.title.text     # Contenu texte (alias)
soup.p.get_text()   # Texte sans balises

# Attributs
soup.a['href']      # Valeur de l'attribut href
soup.a.get('href')  # Idem (plus sûr)
soup.a.attrs        # Dict de tous les attributs

# Parent
soup.title.parent   # Tag parent
soup.title.parent.name  # Nom du parent

# Enfants directs
soup.body.children  # Itérateur sur enfants
list(soup.body.children)  # Liste des enfants

# Tous les descendants
soup.body.descendants  # Itérateur récursif
list(soup.body.descendants)


[OK] RECHERCHE - find() & find_all()


# Trouver premier élément
soup.find('p')                  # Premier <p>
soup.find('div', class_='main') # Premier <div class="main">
soup.find('a', id='link1')      # Premier <a id="link1">

# Trouver tous les éléments
soup.find_all('p')              # Tous les <p>
soup.find_all(['p', 'a'])       # Tous <p> et <a>
soup.find_all('div', limit=5)   # 5 premiers <div>

# Alias plus courts
soup('p')                       # Équivalent find_all('p')
soup.select('p')                # Sélecteur CSS

# find_all avec attributs
soup.find_all('a', href=True)   # <a> avec href
soup.find_all('img', src=True)  # <img> avec src
soup.find_all('div', class_='box')  # class="box"

# Recherche par attribut quelconque
soup.find_all(attrs={'data-id': '123'})
soup.find_all('div', attrs={'data-value': 'test'})

# Recherche par texte
soup.find_all(string='Hello')
soup.find_all(string=['Hello', 'World'])

# Recherche récursive
soup.find_all('p', recursive=True)   # Défaut
soup.find_all('p', recursive=False)  # Enfants directs seulement


[OK] SÉLECTEURS CSS - select()


# Sélecteur simple
soup.select('p')            # Tous les <p>
soup.select('div')          # Tous les <div>

# Par classe
soup.select('.classname')   # class="classname"
soup.select('div.main')     # <div class="main">
soup.select('.box.red')     # class="box red"

# Par ID
soup.select('#myid')        # id="myid"
soup.select('div#header')   # <div id="header">

# Attributs
soup.select('[href]')       # Avec attribut href
soup.select('a[href]')      # <a> avec href
soup.select('[href="url"]') # href="url" exact
soup.select('[href*="part"]')  # href contient "part"
soup.select('[href^="http"]')  # href commence par "http"
soup.select('[href$=".pdf"]')  # href finit par ".pdf"

# Descendant
soup.select('div p')        # <p> dans <div>
soup.select('div span a')   # <a> dans <span> dans <div>

# Enfant direct
soup.select('div > p')      # <p> enfant direct de <div>

# Frère adjacent
soup.select('h1 + p')       # <p> juste après <h1>

# Frères suivants
soup.select('h1 ~ p')       # Tous <p> après <h1>

# Pseudo-classes
soup.select('p:first-child')    # Premier enfant
soup.select('p:last-child')     # Dernier enfant
soup.select('p:nth-child(2)')   # 2ème enfant
soup.select('p:nth-of-type(1)') # Premier <p>

# Combinaisons complexes
soup.select('div.main > p.intro')
soup.select('article > h2 + p')
soup.select('nav ul li a[href^="/"]')

# Sélection unique
soup.select_one('div.main')     # Premier match seulement


[OK] RECHERCHE AVANCÉE - FONCTIONS & REGEX


# Fonction custom
def has_class_but_no_id(tag):
    return tag.has_attr('class') and not tag.has_attr('id')

soup.find_all(has_class_but_no_id)

# Fonction pour attribut spécifique
def has_large_image(tag):
    return tag.name == 'img' and tag.get('width', 0) > 500

soup.find_all(has_large_image)

# Regex sur nom de tag
import re
soup.find_all(re.compile('^h[1-6]$'))  # h1, h2, h3, h4, h5, h6
soup.find_all(re.compile('^div|span$'))  # div ou span

# Regex sur attributs
soup.find_all(href=re.compile('example.com'))
soup.find_all(class_=re.compile('btn-'))
soup.find_all(id=re.compile('^item\d+$'))

# Regex sur texte
soup.find_all(string=re.compile('python', re.I))  # Case insensitive

# Fonction complexe
def has_data_attributes(tag):
    return any(attr.startswith('data-') for attr in tag.attrs)

soup.find_all(has_data_attributes)

# Lambda functions
soup.find_all(lambda tag: len(tag.get_text(strip=True)) > 100)
soup.find_all(lambda tag: tag.name == 'a' and 'href' in tag.attrs)


[OK] EXTRACTION DE TEXTE


# Texte simple
tag.string          # String direct (ou None)
tag.text            # Tout le texte
tag.get_text()      # Tout le texte (méthode)

# Texte avec séparateur
tag.get_text(separator=' ')
tag.get_text(separator='\n')
tag.get_text(separator='|')

# Texte nettoyé
tag.get_text(strip=True)  # Sans whitespace avant/après
tag.get_text(separator=' ', strip=True)

# Texte de tous les descendants
for text in tag.strings:
    print(text)

# Texte nettoyé des descendants
for text in tag.stripped_strings:
    print(text)

# Liste de tous les textes
texts = [t for t in tag.stripped_strings]
all_text = ' '.join(tag.stripped_strings)

# Ignorer certains tags
for script in soup(['script', 'style']):
    script.decompose()
text = soup.get_text()


[OK] EXTRACTION D'ATTRIBUTS


# Single attribut
link = tag['href']          # Erreur si absent
link = tag.get('href')      # None si absent
link = tag.get('href', '')  # Défaut si absent

# Tous les attributs
attrs = tag.attrs           # Dict
print(attrs)

# Vérifier présence
has_href = tag.has_attr('href')
has_class = 'class' in tag.attrs

# Classes (attribut spécial - liste)
classes = tag['class']      # Liste ['class1', 'class2']
classes = tag.get('class', [])

# Data attributes
data_id = tag['data-id']
data_value = tag.get('data-value')

# Attributs multiples
for tag in soup.find_all('a'):
    print(tag.get('href'), tag.get('title'))


[OK] NAVIGATION DANS L'ARBRE


# Parents
tag.parent              # Parent immédiat
tag.parents             # Tous les parents (itérateur)

for parent in tag.parents:
    print(parent.name)

# Enfants
tag.contents            # Liste des enfants directs
tag.children            # Itérateur sur enfants directs
tag.descendants         # Itérateur récursif

for child in tag.children:
    print(child)

# Frères (siblings)
tag.next_sibling        # Frère suivant
tag.previous_sibling    # Frère précédent
tag.next_siblings       # Tous les suivants
tag.previous_siblings   # Tous les précédents

# Note: next_sibling peut être NavigableString (whitespace)
sibling = tag.next_sibling
while sibling and not isinstance(sibling, Tag):
    sibling = sibling.next_sibling

# Navigation séquentielle
tag.next_element        # Prochain élément parsé
tag.previous_element    # Élément précédent parsé
tag.next_elements       # Tous les suivants
tag.previous_elements   # Tous les précédents


[OK] FIND PARENTS & SIBLINGS


# find_parent() - Trouver parent
tag.find_parent('div')
tag.find_parent('div', class_='container')

# find_parents() - Trouver tous les parents
tag.find_parents('div')
tag.find_parents(['div', 'section'])

# find_next_sibling() - Frère suivant
tag.find_next_sibling('p')
tag.find_next_sibling('div', class_='next')

# find_previous_sibling() - Frère précédent
tag.find_previous_sibling('h2')

# find_next_siblings() - Tous frères suivants
tag.find_next_siblings('p')
tag.find_next_siblings('li', limit=3)

# find_previous_siblings() - Tous frères précédents
tag.find_previous_siblings('div')

# find_next() - Prochain élément
tag.find_next('a')
tag.find_next('div', class_='content')

# find_all_next() - Tous éléments suivants
tag.find_all_next('p')
tag.find_all_next('a', limit=5)

# find_previous() - Élément précédent
tag.find_previous('h1')

# find_all_previous() - Tous éléments précédents
tag.find_all_previous('div')


[OK] MODIFICATION DU CONTENU


# Changer le texte
tag.string = "Nouveau texte"

# Changer attribut
tag['href'] = 'https://newurl.com'
tag['class'] = ['new-class']

# Ajouter attribut
tag['data-id'] = '123'

# Supprimer attribut
del tag['class']
tag.attrs.pop('id', None)

# Ajouter contenu
tag.append("Texte à la fin")
tag.insert(0, "Texte au début")

# Nouveau tag
new_tag = soup.new_tag('a', href='url')
new_tag.string = "Lien"
tag.append(new_tag)

# Insérer avant/après
tag.insert_before("Avant")
tag.insert_after("Après")

new_tag = soup.new_tag('div')
tag.insert_before(new_tag)

# Remplacer
old_tag.replace_with(new_tag)
tag.replace_with("Nouveau contenu")

# Extraire (enlever et retourner)
extracted = tag.extract()

# Supprimer (détruire)
tag.decompose()

# Effacer le contenu
tag.clear()

# Unwrap (enlever tag, garder contenu)
tag.unwrap()


[OK] CRÉATION DE NOUVEAUX ÉLÉMENTS


# Nouveau tag
new_tag = soup.new_tag('div')
new_tag = soup.new_tag('a', href='url', id='link1')
new_tag['class'] = ['btn', 'primary']
new_tag.string = "Texte"

# NavigableString
new_text = NavigableString("Texte simple")

# Comment
new_comment = Comment("Ceci est un commentaire")

# Ajouter au soup
soup.body.append(new_tag)

# Insérer à position spécifique
soup.body.insert(0, new_tag)

# Exemple complet
div = soup.new_tag('div', **{'class': 'card', 'data-id': '1'})
h2 = soup.new_tag('h2')
h2.string = "Titre"
p = soup.new_tag('p')
p.string = "Description"
div.append(h2)
div.append(p)
soup.body.append(div)


[OK] ENCODAGE & OUTPUT


# Prettify (formaté)
print(soup.prettify())
print(tag.prettify())

# String brut
str(soup)
str(tag)

# Encoder
soup.encode('utf-8')
soup.encode('latin-1')

# Decode (string)
soup.decode()

# Formater output
soup.prettify(formatter='html')     # Entités HTML
soup.prettify(formatter='minimal')  # Minimal
soup.prettify(formatter=None)       # Pas de formatage

# Sans formatter
str(soup)
tag.decode_contents()  # Contenu uniquement


[OK] TYPES D'OBJETS


# Tag
from bs4 import Tag
isinstance(tag, Tag)
tag.name  # Nom du tag

# NavigableString (texte)
from bs4 import NavigableString
isinstance(text, NavigableString)

# Comment
from bs4 import Comment
isinstance(comment, Comment)

# CData
from bs4 import CData

# Vérifier type
if isinstance(element, Tag):
    print("C'est un tag")
elif isinstance(element, NavigableString):
    print("C'est du texte")


[OK] FILTRAGE & EXTRACTION DE DONNÉES


# Extraire tous les liens
links = []
for link in soup.find_all('a'):
    href = link.get('href')
    if href:
        links.append(href)

# List comprehension
links = [a['href'] for a in soup.find_all('a', href=True)]

# Extraire images
images = [img['src'] for img in soup.find_all('img', src=True)]

# Extraire titres
titles = [h.get_text(strip=True) for h in soup.find_all(['h1', 'h2', 'h3'])]

# Extraire avec plusieurs attributs
data = []
for item in soup.select('.product'):
    name = item.select_one('.name').get_text(strip=True)
    price = item.select_one('.price').get_text(strip=True)
    data.append({'name': name, 'price': price})

# Nettoyer whitespace
text = ' '.join(soup.stripped_strings)

# Filtrer par condition
paragraphs = [p for p in soup.find_all('p') 
              if len(p.get_text(strip=True)) > 50]


[OK] EXEMPLES PRATIQUES


# Extraire article de blog
article = soup.find('article')
title = article.find('h1').get_text(strip=True)
author = article.find('span', class_='author').get_text(strip=True)
date = article.find('time')['datetime']
content = article.find('div', class_='content').get_text(strip=True)

# Extraire liste
items = []
for li in soup.select('ul.items li'):
    items.append(li.get_text(strip=True))

# Extraire tableau
table_data = []
for row in soup.select('table tr'):
    cols = [col.get_text(strip=True) for col in row.find_all(['td', 'th'])]
    table_data.append(cols)

# Extraire formulaire
form_data = {}
for input_tag in soup.find('form').find_all('input'):
    name = input_tag.get('name')
    value = input_tag.get('value', '')
    if name:
        form_data[name] = value

# Extraire métadonnées
meta_tags = soup.find_all('meta')
metadata = {}
for meta in meta_tags:
    name = meta.get('name') or meta.get('property')
    content = meta.get('content')
    if name and content:
        metadata[name] = content


[OK] SCRAPING WEB COMPLET


import requests
from bs4 import BeautifulSoup

# GET request
url = 'https://example.com'
response = requests.get(url)
soup = BeautifulSoup(response.content, 'html.parser')

# Avec headers
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.content, 'html.parser')

# Avec timeout
try:
    response = requests.get(url, timeout=10)
    response.raise_for_status()
    soup = BeautifulSoup(response.content, 'html.parser')
except requests.RequestException as e:
    print(f"Erreur: {e}")

# POST request
data = {'username': 'user', 'password': 'pass'}
response = requests.post(url, data=data)
soup = BeautifulSoup(response.content, 'html.parser')

# Session (avec cookies)
session = requests.Session()
session.get('https://example.com/login')
session.post('https://example.com/login', data=data)
response = session.get('https://example.com/protected')
soup = BeautifulSoup(response.content, 'html.parser')


[OK] GESTION D'ERREURS


# Vérifier si élément existe
tag = soup.find('div', class_='main')
if tag:
    text = tag.get_text()
else:
    text = "Non trouvé"

# Try-except pour attributs
try:
    href = tag['href']
except (KeyError, TypeError):
    href = None

# Get avec défaut
href = tag.get('href', 'default')

# Vérifier avant accès
if tag and tag.find('span'):
    value = tag.find('span').get_text()

# None-safe
title = soup.find('h1')
title_text = title.get_text(strip=True) if title else "Pas de titre"

# Chaîne d'appels safe
element = soup.find('div')
if element:
    child = element.find('span')
    if child:
        text = child.get_text()


[OK] PERFORMANCE & OPTIMISATION


# Limiter recherche
soup.find_all('a', limit=10)  # Seulement 10 premiers

# Recherche non-récursive
soup.find_all('p', recursive=False)  # Enfants directs seulement

# Parser rapide (lxml)
soup = BeautifulSoup(html, 'lxml')

# Extraire et détruire éléments inutiles
for script in soup(['script', 'style', 'iframe']):
    script.decompose()

# Copier arbre pour modifications
from copy import copy
tag_copy = copy(tag)

# Utiliser SoupStrainer (parse partiel)
from bs4 import SoupStrainer

only_links = SoupStrainer('a')
soup = BeautifulSoup(html, 'html.parser', parse_only=only_links)

only_divs = SoupStrainer('div', class_='main')
soup = BeautifulSoup(html, 'html.parser', parse_only=only_divs)


[OK] DÉBOGAGE


# Type d'élément
print(type(element))
print(element.name if isinstance(element, Tag) else "Not a tag")

# Afficher structure
print(soup.prettify())

# Parents
for parent in tag.parents:
    print(parent.name)

# Tous les attributs
print(tag.attrs)

# Contenu brut
print(repr(tag))

# Contents
print(tag.contents)
print(list(tag.children))

# Compter éléments
print(len(soup.find_all('a')))


[OK] EXPRESSIONS RÉGULIÈRES UTILES


import re

# Email
soup.find_all(string=re.compile(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'))

# URL
soup.find_all('a', href=re.compile(r'^https?://'))

# Numéros de téléphone
soup.find_all(string=re.compile(r'\d{3}-\d{3}-\d{4}'))

# Prix
soup.find_all(string=re.compile(r'\$\d+(?:\.\d{2})?'))

# Tags commençant par...
soup.find_all(re.compile('^b'))  # b, body, button, etc.

# Classes contenant...
soup.find_all(class_=re.compile('btn-'))


[OK] TECHNIQUES AVANCÉES


# Extraire JSON-LD
scripts = soup.find_all('script', type='application/ld+json')
import json
for script in scripts:
    data = json.loads(script.string)
    print(data)

# Suivre pagination
page = 1
while True:
    url = f'https://example.com/page/{page}'
    response = requests.get(url)
    soup = BeautifulSoup(response.content, 'html.parser')
    
    items = soup.find_all('div', class_='item')
    if not items:
        break
    
    for item in items:
        # Process item
        pass
    
    page += 1

# Télécharger images
for img in soup.find_all('img'):
    src = img.get('src')
    if src:
        if not src.startswith('http'):
            src = f'https://example.com{src}'
        img_data = requests.get(src).content
        filename = src.split('/')[-1]
        with open(filename, 'wb') as f:
            f.write(img_data)


[OK] EXPORT DE DONNÉES


# Vers CSV
import csv

data = []
for row in soup.select('table tr'):
    cols = [col.get_text(strip=True) for col in row.find_all(['td', 'th'])]
    data.append(cols)

with open('output.csv', 'w', newline='', encoding='utf-8') as f:
    writer = csv.writer(f)
    writer.writerows(data)

# Vers JSON
import json

articles = []
for article in soup.find_all('article'):
    articles.append({
        'title': article.find('h2').get_text(strip=True),
        'date': article.find('time')['datetime'],
        'content': article.find('p').get_text(strip=True)
    })

with open('output.json', 'w', encoding='utf-8') as f:
    json.dump(articles, f, ensure_ascii=False, indent=2)

# Vers DataFrame pandas
import pandas as pd

data = []
for item in soup.select('.product'):
    data.append({
        'name': item.select_one('.name').get_text(strip=True),
        'price': item.select_one('.price').get_text(strip=True),
        'rating': item.select_one('.rating')['data-rating']
    })

df = pd.DataFrame(data)
df.to_csv('products.csv', index=False)


[OK] BONNES PRATIQUES


# [OK] Toujours vérifier si élément existe avant accès
# [OK] Utiliser get() pour attributs au lieu de []
# [OK] Nettoyer whitespace avec strip=True
# [OK] Utiliser select() pour sélecteurs CSS complexes
# [OK] Parser avec lxml pour performance
# [OK] Decompose scripts/styles si pas nécessaires
# [OK] Respecter robots.txt et termes d'utilisation
# [OK] Ajouter délais entre requêtes (time.sleep)
# [OK] Utiliser User-Agent approprié
# [OK] Gérer exceptions (requests, parsing)

# [X] Ne pas scraper sans permission
# [X] Ne pas surcharger serveurs
# [X] Ne pas supposer structure HTML constante
# [X] Ne pas ignorer erreurs HTTP
# [X] Ne pas parser avec html.parser si lxml disponible


[OK] ERREURS COURANTES & SOLUTIONS


# [X] AttributeError: 'NoneType' object has no attribute 'get_text'
[OK] Vérifier: if tag: avant accès

# [X] KeyError: 'href'
[OK] Utiliser: tag.get('href') au lieu de tag['href']

# [X] Liste vide avec find_all()
[OK] Vérifier sélecteur, tester avec select()

# [X] Caractères bizarres (encoding)
[OK] Spécifier encoding: response.encoding = 'utf-8'

# [X] Rien trouvé avec select()
[OK] Vérifier si JavaScript charge contenu (utiliser Selenium)

# [X] Too many requests (429)
[OK] Ajouter time.sleep() entre requêtes


[OK] RESSOURCES


# Documentation: https://www.crummy.com/software/BeautifulSoup/bs4/doc/
# Tutoriel: https://realpython.com/beautiful-soup-web-scraper-python/
# Requests: https://requests.readthedocs.io/
# Sélecteurs CSS: https://www.w3schools.com/cssref/css_selectors.asp
# Regex Python: https://docs.python.org/3/library/re.html