> ## 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.

# Come gestire l'impaginazione

> Scopri come superare la prima pagina: pagine numerate, link successivi, scorrimento infinito, pulsanti Carica altro, offset API e cursori.

L'impaginazione è il livello di navigazione di uno scraper. Dopo aver recuperato, visualizzato ed estratto una pagina, deve rispondere a una domanda pratica: **dove si trova il gruppo di record successivo e come capire quando non ne restano altri?**

La maggior parte degli errori deriva dal considerare ogni sito un elenco di pagine numerate. Un catalogo può invece usare parametri URL, un pulsante successivo, scorrimento infinito, “Carica altro”, un offset API o un token cursore opaco. Alcuni siti combinano più schemi.

## Parti dalla richiesta, non dall'interfaccia

Prima di scrivere la logica, apri DevTools e osserva che cosa cambia passando al gruppo successivo.

1. Apri la scheda Network e filtra Fetch/XHR.
2. Fai clic sulla pagina successiva, scorri o premi “Carica altro”.
3. Esamina URL, parametri della query, corpo della richiesta e risposta.
4. Decidi se seguire link, interagire con la pagina o chiamare direttamente un endpoint API.

Usa l'interfaccia come indizio, ma considera decisiva la richiesta di rete. Un pulsante “Carica altro” potrebbe chiamare una semplice API con `offset=40`; un link potrebbe caricare i risultati con JavaScript dopo la modifica dell'URL.

| Che cosa cambia                                                    | Primo tentativo consigliato                                    |
| ------------------------------------------------------------------ | -------------------------------------------------------------- |
| L'URL contiene `page=2`, `p=2` o `/page/2`                         | Esegui un ciclo sugli URL numerati                             |
| Un link `<a>` porta alla pagina successiva                         | Segui `href` finché scompare o viene disabilitato              |
| I contenuti compaiono scorrendo                                    | Trova la richiesta XHR; scorri nel browser solo se necessario  |
| I contenuti compaiono facendo clic                                 | Riutilizza la richiesta API o fai clic in una sessione browser |
| Il JSON contiene `next_cursor`, `endCursor`, `has_more` o `offset` | Gestisci l'impaginazione tramite la risposta API               |

## Pagine numerate

È il caso più semplice, perché la posizione successiva è visibile nell'URL:

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

Lo scraper incrementa il numero e si arresta quando la risposta non contiene elementi, ne contiene meno del previsto o restituisce una pagina 404/vuota nota.

```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))
```

Controlla gli indici che iniziano da `0`, parametri come `p` o `start` e siti che restituiscono nuovamente la prima pagina quando il numero è fuori intervallo. La ripetizione della prima pagina è peggiore di una pagina vuota, perché crea duplicati senza errori evidenti.

## Link alla pagina successiva

Alcuni siti mostrano soltanto un link “Successiva” o una freccia. Se è una normale ancora, seguilo:

```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 protezione `seen_urls` è importante: alcuni siti configurati male fanno puntare l'ultimo link alla pagina corrente o alla prima. Verifica anche stati disabilitati come `aria-disabled="true"`, `disabled` o una classe `disabled`.

## Scorrimento infinito

Sembra un problema risolvibile solo nel browser, ma di solito utilizza un'API. Scorri una volta con DevTools aperto e cerca la richiesta che recupera il gruppo successivo; i parametri sono spesso `offset`, `page`, `after`, `cursor` o `limit`.

Quando l'endpoint è utilizzabile, chiamalo direttamente:

```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)
```

Usa un browser soltanto quando autenticazione, parametri firmati o stato lato client rendono difficile chiamare l'API all'esterno della pagina.

```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()
```

Non affidarti soltanto all'altezza della pagina, che può cambiare per annunci, immagini o elenchi virtualizzati. Numero di elementi, inattività della rete e limite massimo di scorrimenti sono una combinazione più sicura.

## Pulsanti Carica altro

Un pulsante Carica altro è uno scorrimento infinito controllato: la pagina attende un clic prima di richiedere il gruppo successivo. Lo scraper può quindi attendere, convalidare il nuovo numero di elementi e riprovare in caso di errore.

Se il pulsante chiama un'API chiara, usa l'API; altrimenti fai clic in un ciclo browser:

```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()
```

Il controllo importante non è “il pulsante è stato premuto”, ma “sono comparsi nuovi record”. I pulsanti possono non rispondere, disabilitarsi o restare visibili dopo l'ultimo gruppo.

## API con offset e cursore

I siti moderni gestiscono spesso l'impaginazione a livello API. L'offset richiede una posizione numerica:

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

Il cursore richiede il token opaco restituito dalla risposta precedente:

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

Il cursore è più stabile quando i record vengono aggiunti o rimossi durante lo scraping: anziché “salta le prime 40 righe”, indica “continua dopo questa posizione nota”.

```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")
```

Gestisci consapevolmente i limiti di frequenza: rispetta `Retry-After`, riprova gli errori temporanei con backoff e salva l'avanzamento se ricominciare dalla prima pagina sarebbe costoso.

## Impaginazione ibrida

I siti reali combinano spesso più schemi:

* Una categoria ha pagine numerate, ma ogni pagina carica altri prodotti durante lo scorrimento.
* Una ricerca inizia con Carica altro e poi passa ai link numerati.
* Un'interfaccia a schede ha impaginazioni separate per “Nuovi”, “Popolari” e “In offerta”.
* Una pagina di elenco impagina gli URL dei risultati e ogni dettaglio ha recensioni o commenti a loro volta impaginati.

Gestiscili come cicli annidati. Il ciclo esterno controlla l'unità di navigazione più ampia e ogni ciclo interno una singola azione ripetuta. Traccia ID univoci per tutta l'esecuzione per evitare duplicati nell'output.

## Misure di sicurezza pratiche

* **Definisci un segnale di arresto.** Risultati vuoti, link successivi assenti, pulsanti disabilitati, `hasNextPage: false`, cursori ripetuti e limiti massimi di iterazione sono tutti validi.
* **Rileva i duplicati.** Scorrimento infinito e API con cursore possono ripetere record se i dati cambiano durante l'esecuzione. Conserva ID stabili o URL canonici.
* **Regola la navigazione.** Aggiungi brevi attese casuali tra i gruppi. L'automazione deve attendere modifiche dei contenuti, non soltanto timeout fissi.
* **Registra gli errori.** Se una pagina non riesce dopo più tentativi, registra URL o cursore e continua quando possibile.
* **Preferisci le API quando sono legittime e stabili.** L'impaginazione API diretta è solitamente più veloce e facile da convalidare del controllo di un browser.
* **Usa uno strumento visuale quando la velocità conta più del codice personalizzato.** In Octoparse, i comuni flussi con pagina successiva, Carica altro e scorrimento infinito si configurano visivamente e si eseguono in locale o nel cloud.

L'impaginazione non significa soltanto “vai alla pagina successiva”: è il ciclo di controllo dello scraper. Con una logica chiara per il passaggio successivo, una condizione di arresto affidabile e protezione dai duplicati, lo scraper può attraversare il sito senza fermarsi silenziosamente alla prima pagina né continuare all'infinito.
