> ## Documentation Index
> Fetch the complete documentation index at: https://www.octoparse.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Comment gérer la pagination

> Découvrez comment dépasser la première page : pages numérotées, liens Suivant, défilement infini, boutons Charger plus, offsets d’API et curseurs.

La Pagination est la couche de navigation d’un outil de scraping. Après avoir récupéré, affiché et extrait une page, il reste une question pratique : **où se trouve le prochain lot d’enregistrements et comment savoir qu’il n’y en a plus ?**

La plupart des échecs viennent de l’idée que tous les sites utilisent des pages numérotées. Un catalogue peut employer des paramètres d’URL, un bouton Suivant, un défilement infini, un bouton Charger plus, un offset d’API ou un jeton de curseur opaque, parfois plusieurs à la fois.

## Commencer par la requête, pas par l’interface

Avant d’écrire la logique, ouvrez les DevTools et observez les changements lors du passage au lot suivant.

1. Ouvrez l’onglet Network et filtrez sur Fetch/XHR.
2. Cliquez sur la page suivante, faites défiler ou appuyez sur Charger plus.
3. Examinez l’URL, les paramètres, le corps de la requête et la réponse.
4. Décidez s’il faut suivre des liens, interagir avec la page ou appeler directement une API.

L’interface fournit un indice, mais la requête réseau fait foi. Un bouton « Charger plus » peut appeler une API avec `offset=40`, tandis qu’un lien peut actualiser les résultats avec JavaScript après le changement d’URL.

| Changement observé                                           | Premier essai recommandé                                                  |
| ------------------------------------------------------------ | ------------------------------------------------------------------------- |
| URL avec `page=2`, `p=2` ou `/page/2`                        | Parcourir les URL numérotées                                              |
| Lien `<a>` vers la page suivante                             | Suivre `href` jusqu’à sa disparition ou sa désactivation                  |
| Contenu après défilement                                     | Trouver la requête XHR ; ne faire défiler le navigateur que si nécessaire |
| Contenu après un clic                                        | Réutiliser l’API ou cliquer dans une session de navigateur                |
| JSON avec `next_cursor`, `endCursor`, `has_more` ou `offset` | Paginer via la réponse de l’API                                           |

## Pages numérotées

C’est le cas le plus simple, car l’emplacement suivant apparaît dans l’URL :

```text theme={null}
https://example.com/products?page=1
https://example.com/products?page=2
https://example.com/catalog/page/3
```

Incrémentez le numéro et arrêtez lorsque la réponse ne contient aucun élément, moins d’éléments que prévu ou une page 404/vide connue.

```python theme={null}
import requests
from bs4 import BeautifulSoup

all_products = []

for page in range(1, 100):
    html = requests.get(f"https://example.com/products?page={page}").text
    soup = BeautifulSoup(html, "html.parser")
    cards = soup.select(".product-card")

    if not cards:
        break

    for card in cards:
        all_products.append(card.select_one(".title").get_text(strip=True))
```

Attention aux index commençant à `0`, aux paramètres `p` ou `start` et aux sites qui renvoient la première page lorsque le numéro dépasse la limite. Cette répétition est pire qu’une page vide, car elle crée silencieusement des doublons.

## Liens Suivant

Certains sites n’affichent aucun numéro, seulement un lien ou une flèche « Suivant ». Si l’élément est une ancre normale, suivez le lien :

```python theme={null}
from urllib.parse import urljoin

import requests
from bs4 import BeautifulSoup

url = "https://example.com/products"
seen_urls = set()

while url and url not in seen_urls:
    seen_urls.add(url)
    soup = BeautifulSoup(requests.get(url).text, "html.parser")

    for card in soup.select(".product-card"):
        print(card.select_one(".title").get_text(strip=True))

    next_link = soup.select_one("a[rel='next'], a.next")
    url = urljoin(url, next_link["href"]) if next_link and next_link.get("href") else None
```

La protection `seen_urls` est importante : certains sites mal configurés renvoient depuis le dernier lien vers la page courante ou la première. Vérifiez aussi `aria-disabled="true"`, `disabled` ou une classe `disabled`.

## Défilement infini

Il semble exiger un navigateur, mais repose généralement sur une API. Faites défiler avec les DevTools ouverts et cherchez la requête du lot suivant ; les paramètres s’appellent souvent `offset`, `page`, `after`, `cursor` ou `limit`.

Si l’endpoint est exploitable, appelez-le directement :

```python theme={null}
import requests

offset = 0
limit = 24
products = []

while True:
    data = requests.get(
        "https://example.com/api/products",
        params={"offset": offset, "limit": limit},
    ).json()

    batch = data.get("items", [])
    if not batch:
        break

    products.extend(batch)
    offset += len(batch)
```

N’utilisez un navigateur que si l’API est difficile à appeler hors de la page en raison de l’authentification, de paramètres signés ou d’un état client complexe.

```python theme={null}
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto("https://example.com/products")

    previous_count = 0
    for _ in range(40):
        page.mouse.wheel(0, 4000)
        page.wait_for_timeout(1500)

        current_count = page.locator(".product-card").count()
        if current_count == previous_count:
            break
        previous_count = current_count

    print(page.locator(".product-card").count())
    browser.close()
```

Ne vous fiez pas seulement à la hauteur de page : publicités, images et listes virtualisées peuvent la modifier continuellement. Combinez nombre d’éléments, inactivité réseau et nombre maximal de défilements.

## Boutons Charger plus

Un tel bouton est un défilement infini contrôlé. Le clic permet d’attendre, valider le nouveau nombre d’éléments et réessayer en cas d’échec. Utilisez l’API si elle est propre ; sinon, cliquez en boucle :

```python theme={null}
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto("https://example.com/products")

    while page.locator("button.load-more").is_visible():
        before = page.locator(".product-card").count()
        page.locator("button.load-more").click()
        page.wait_for_function(
            "(count) => document.querySelectorAll('.product-card').length > count",
            before,
        )

    browser.close()
```

La vérification importante n’est pas seulement « bouton cliqué », mais « nouveaux enregistrements apparus ». Un bouton peut échouer silencieusement, se désactiver ou rester visible après le dernier lot.

## API avec offset et curseur

Les sites modernes paginent souvent au niveau de l’API. L’offset demande une position numérique :

```text theme={null}
/api/products?offset=40&limit=20
```

Le curseur demande le prochain jeton opaque renvoyé par la réponse précédente :

```json theme={null}
{
  "items": [],
  "pageInfo": {
    "hasNextPage": true,
    "endCursor": "eyJpZCI6MTAwfQ=="
  }
}
```

Le curseur est plus stable lorsque des enregistrements changent pendant l’extraction : il signifie « continuer après cette position connue », et non « ignorer 40 lignes ».

```python theme={null}
import requests

cursor = None

while True:
    params = {"limit": 50}
    if cursor:
        params["after"] = cursor

    data = requests.get("https://example.com/api/products", params=params).json()
    for item in data.get("items", []):
        print(item["name"])

    page_info = data.get("pageInfo", {})
    if not page_info.get("hasNextPage"):
        break

    cursor = page_info.get("endCursor")
```

Gérez délibérément les limites de débit : respectez `Retry-After`, réessayez les échecs temporaires avec un délai progressif et enregistrez la progression si recommencer depuis la première page serait coûteux.

## Pagination hybride

Les sites combinent souvent les modèles :

* Une catégorie possède des pages numérotées, mais chacune charge davantage de produits après défilement.
* Une page de recherche commence avec un bouton Charger plus, puis passe à des liens numérotés.
* Une interface à onglets possède une Pagination distincte pour « Nouveau », « Populaire » et « Promotions ».
* Une liste pagine les URL des résultats, puis chaque page détaillée possède ses propres avis ou commentaires paginés.

Traitez-les comme des boucles imbriquées : la boucle externe gère la grande unité de navigation et chaque boucle interne une action répétée. Suivez les ID uniques sur toute l’exécution pour éviter les doublons.

## Mesures de protection pratiques

* **Définissez un signal d’arrêt.** Résultat vide, lien absent, bouton désactivé, `hasNextPage: false`, curseur répété et limite d’itérations sont valides.
* **Détectez les doublons.** Stockez des ID stables ou URL canoniques.
* **Limitez le rythme.** Ajoutez de petites attentes aléatoires entre les lots ; attendez les changements de contenu plutôt qu’un délai fixe.
* **Journalisez les échecs.** Après plusieurs tentatives, notez l’URL ou le curseur et continuez si possible.
* **Préférez les API lorsqu’elles sont légitimes et stables.** Elles sont généralement plus rapides et faciles à valider.
* **Utilisez un outil visuel si la vitesse de configuration prime.** Dans Octoparse, les flux Suivant, Charger plus et défilement infini se configurent visuellement, puis s’exécutent localement ou dans le cloud.

La Pagination ne consiste pas simplement à « passer à la page suivante » : c’est la boucle de contrôle de l’outil. Avec une prochaine étape claire, une condition d’arrêt fiable et une protection contre les doublons, il parcourt le site sans s’arrêter silencieusement à la première page ni tourner indéfiniment.
