Pagination patterns
Most websites split data across multiple pages. This guide shows how to handle the most common pagination patterns: clicking a “next” button, iterating numbered pages by URL, and collecting results across all pages.
All pagination examples use the cdp driver because page navigation and clicking require a browser. See Browser-driven pages for setup.
Click a “next” button
The most common pagination pattern is to click a Next link until it disappears.
Use do while because the loop body must run at least once to process the first page. After each iteration, Ferret checks whether the next-page link still exists.
Key points
for do whileprocesses the first page. Unlike a regularfor whileloop, it executes its body before evaluating the continuation condition, making it ideal for pagination because the initial page is already loaded.- The loop index distinguishes the first page. The first iteration has an index of 0.
NEXT_PAGE(i)skips navigation on that iteration and clicks Next only on subsequent pages. waitfor ... triggerprevents timing races. Thetriggerblock executes only afterwaitforhas started listening for the navigation event. This guarantees that even very fast navigations cannot occur before the event listener is ready.- Navigation completes before extraction.
CLICK_NEXT()returns only after the browser has finished navigating, so the extraction loop always runs against the newly loaded page. query existscontrols pagination. After each iteration, Ferret checks whether a matching Next link still exists. When it no longer does, the loop terminates automatically.query onelocates the navigation control. It returns the single Next link that serves as the target for the click operation.limitis a safety guard. It prevents accidental infinite or unexpectedly long pagination loops if a site behaves incorrectly.- Optional queries tolerate missing data. The
~?query operator returnsnonewhen an element is missing. Combined with optional chaining (?.), incomplete product cards can be processed without failing the query.
Infinite scrolling
Some websites load additional content when the user scrolls to the bottom of the page. Use web::html::scroll_bottom to trigger another content load, then process only the newly added items.
Key points
- Infinite scrolling requires a browser. The page uses JavaScript and user interaction to load additional content, so the query opens it with the CDP driver.
for do whileprocesses the initial batch. The loop body runs before its condition is evaluated, allowing the products already present on the first page to be extracted before the first scroll.web::html::scroll_bottomdrives the loop. After each iteration, Ferret scrolls to the bottom of the page. The loop continues while further scrolling is possible.- The loop index identifies each batch. On the first iteration, i is 0, so the query processes the products initially present on the page. Later values correspond to batches loaded by subsequent scrolls.
- The first iteration does not wait. The initial products are already available, so
waitis used only after a scroll has triggered another content load. - The temporary wait avoids reading the page too early. It gives the website time to fetch and render newly loaded products before extraction begins. This is a temporary workaround until
page.network.statuscan be used to wait for the page to become idle. :skip()prevents duplicate results. Each iteration skips the products returned by earlier iterations and selects only the newly loaded batch.pageSizemust match the website’s behavior. The skip offset assumes that every scroll loads eight products. If the site returns variable-sized batches, track the number of already processed elements instead.query oneextracts fields from each product. Each title and price lookup is scoped to the current product card rather than the entire page.- This pattern needs a stopping condition. The loop ends when
web::html::scroll_bottom(page)indicates that the page can no longer scroll further. Alimitcan also be added as a safety guard for sites that scroll indefinitely.
Iterate numbered pages by URL
When pages are addressable by URL (e.g., ?page=1, ?page=2), use a for loop with a range:
Annotated query
- A range drives the pagination.
1..totalPagesgenerates the sequence of page numbers to visit, making it easy to iterate over numbered URLs. - Each URL is built independently. The current page number is used to construct the appropriate URL for each request. The first page often uses a different URL pattern than subsequent pages.
- Each page is fetched directly.
web::html::open()loads every page independently over HTTP, so no browser session or navigation is required. - Pages are processed independently. Each iteration loads, extracts, and returns data from one page before moving on to the next.
- Browser automation is unnecessary. This approach is faster and more resource-efficient because it avoids rendering pages or simulating user interactions.
- Best suited for static websites. Use this pattern when the desired content is present in the initial HTML returned by the server. If the content is loaded dynamically with JavaScript, use browser automation instead.
- Optional queries tolerate missing data. The
~?query operator returnsnonewhen an element is missing. Combined with optional chaining (?.), incomplete product cards can be processed without failing the query.
Detect the last page
If you do not know the total number of pages, open the first page to find out:
Collect results into a flat array
When each page returns an array of items, the outer loop produces an array of arrays. Use arrays::flatten to merge them:
Alternatively, use the [**] array contraction operator to flatten inline:
Add error recovery
Pagination scripts are long-running and may encounter network errors or missing elements. Wrap page loads and interactions with error recovery:
See Error handling and resilience for more patterns.