Python Phân Trang: Cách Thu Thập Dữ Liệu Qua Các Trang Phân Trang
TL;DR
Phân trang là một hợp đồng giữa khách hàng của bạn và nguồn dữ liệu. Kiểm tra phản hồi để tìm số trang, offset, con trỏ hoặc liên kết tiếp theo trước khi viết vòng lặp.
Đối với API giới hạn/offset, hãy tiến bộ theo số bản ghi thực sự được trả về và dừng lại ở tổng số đã báo cáo hoặc một trang trống. Thêm một bảo vệ trang tối đa và loại bỏ bản sao theo ID ổn định.
API con trỏ là mờ đục theo thiết kế. Gửi con trỏ tiếp theo chính xác được trả về bởi máy chủ; không chế tạo số trang từ nó.
Hướng dẫn này sử dụng DummyJSON làm API demo. Nó hữu ích để học các cơ chế phân trang, không phải như một tập dữ liệu kinh doanh sản xuất hoặc hợp đồng đảm bảo tính khả dụng.
Ý Nghĩa Phân Trang Python Đối Với Thu Thập Dữ Liệu
Phân trang Python là logic yêu cầu một tập kết quả lớn trong các trang nhỏ hơn. Các API và trang web sử dụng phân trang vì việc trả về mỗi bản ghi trong một phản hồi có thể chậm, tốn bộ nhớ hoặc vượt quá giới hạn của máy chủ.
Một scraper chỉ lấy phản hồi đầu tiên có thể trông khỏe mạnh trong khi im lặng bỏ lỡ hầu hết tập dữ liệu. Vòng lặp chính xác phụ thuộc vào kiểu phân trang của nguồn dữ liệu: số trang, offset và giới hạn, token con trỏ, URL trang tiếp theo, hoặc nội dung được tải khi người dùng cuộn. Luôn đọc tài liệu API của nhà cung cấp và kiểm tra một phản hồi mẫu trước khi chọn một vòng lặp. Đối với phát hiện URL ở cấp độ trang web và trích xuất trang, Nstdata Crawl tách biệt với phân trang con trỏ hoặc offset cụ thể cho ứng dụng.
Các Mẫu Phân Trang Phổ Biến
Mẫu
Ví dụ yêu cầu
Làm thế nào để biết khi nào dừng
Thất bại thường gặp
Số trang
?page=3
Trang trống, không có trường trang tiếp theo, hoặc tổng số đã biết
Trải nghiệm Nstproxy Crawl - Bắt Đầu Dùng Thử Miễn Phí Hôm Nay
Offset + giới hạn
?skip=60&limit=30
Tổng số đã trả về hoặc không có bản ghi
Tăng dần theo kích thước trang sai
Con trỏ / token tiếp tục
?cursor=opaque-value
Không có con trỏ tiếp theo
Xử lý con trỏ như một số hoặc thay đổi nó
Liên kết tiếp theo HTML
Một liên kết nhãn Next
Không có liên kết tiếp theo sử dụng được
Xây dựng URL tiếp theo bằng cách nối chuỗi
Cuộn vô hạn
Trình duyệt lấy thêm khi cuộn
Dấu hiệu kết thúc cụ thể của trang hoặc API đã được tài liệu exhausted
Cuộn mãi mãi hoặc gọi các điểm cuối không tài liệu mà không có quyền
Tên tham số là ví dụ, không phải tiêu chuẩn API phổ quát. Một số API trả về một URL tiếp theo đầy đủ; một số khác trả về một con trỏ bên trong một đối tượng lồng ghép. Sử dụng tên và điều kiện dừng mà tài liệu nguồn cung cấp.
Xây Dựng Kịch Bản Phân Trang Giới Hạn/Offset Trong Python
Tài liệu của điểm cuối sản phẩm DummyJSON định nghĩa các tham số limit và skip và trả về tổng số kết quả. Ví dụ dưới đây yêu cầu các trang có giới hạn, xác thực hình dạng phản hồi, loại bỏ bản sao theo ID sản phẩm và ghi vào CSV.
import csv
import time
from typing import Any
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
API_URL ="https://dummyjson.com/products"PAGE_SIZE =30MAX_PAGES =100defmake_session()-> requests.Session: retry = Retry( total=3, connect=3, read=2, status=3, backoff_factor=0.5, status_forcelist=(429,500,502,503,504), allowed_methods=frozenset({"GET"}), respect_retry_after_header=True,) session = requests.Session() session.mount("https://", HTTPAdapter(max_retries=retry)) session.headers.update({"User-Agent":"pagination-demo/1.0"})return session
deffetch_all_products()->list[dict[str, Any]]: session = make_session() products:list[dict[str, Any]]=[] seen_ids:set[int]=set() skip =0 total:int|None=Nonefor page_number inrange(1, MAX_PAGES +1): response = session.get( API_URL, params={"limit": PAGE_SIZE,"skip": skip}, timeout=(5,20),) response.raise_for_status() payload = response.json() batch = payload.get("products") page_total = payload.get("total")ifnotisinstance(batch,list)ornotisinstance(page_total,int):raise ValueError("Phản hồi API không như mong đợi: mong đợi products[] và tổng số nguyên") total = page_total
ifnot batch:breakfor item in batch: item_id = item.get("id")ifnotisinstance(item_id,int):continueif item_id notin seen_ids: seen_ids.add(item_id) products.append(item)# Tiến bộ theo số bản ghi đã trả về, không phải theo giới hạn yêu cầu. skip +=len(batch)if skip >= total:break time.sleep(0.1)else:raise RuntimeError(f"Ngừng lại ở giới hạn an toàn của {MAX_PAGES} trang")if total isnotNoneandlen(products)< total:print(f"Cảnh báo: nhận được {len(products)} sản phẩm độc nhất; API đã báo cáo {total}")return products
defmain()->None: products = fetch_all_products() fields =["id","title","category","price"]withopen("products.csv","w", newline="", encoding="utf-8")as output: writer = csv.DictWriter(output, fieldnames=fields, extrasaction="ignore") writer.writeheader() writer.writerows(products)print(f"Đã lưu {len(products)} hàng độc nhất vào products.csv")if __name__ =="__main__": main()
Ví dụ này sử dụng chính sách thử lại cho các lỗi HTTP tạm thời và tôn trọng Retry-After khi máy chủ cung cấp nó. Nó không thử lại mọi lỗi: một lỗi 400 hoặc 401 thường yêu cầu sửa request hoặc thông tin xác thực, không phải lặp lại nó.
Tại sao Vòng lặp Tiến lên theo Số lượng Được Trả về
API có thể trả về ít bản ghi hơn so với yêu cầu ở cuối bộ sưu tập hoặc khi nó thực thi một kích thước trang tối đa. Cập nhật skip bằng len(batch) giữ cho request tiếp theo phù hợp với phản hồi. Vòng lặp kết thúc khi bù trừ đạt total, hoặc sớm hơn nếu API trả về một lô rỗng.
Bộ bảo vệ trang tối đa bảo vệ chống lại một trường tổng bị hỏng hoặc một điểm cuối liên tục trả về cùng một trang. Nếu cảnh báo xuất hiện, so sánh ID và bù trừ trong các nhật ký đã lưu trước khi tăng giới hạn. Các ID trùng lặp có thể có nghĩa là nguồn đã thay đổi trong quá trình thu thập hoặc rằng các tham số phân trang không được áp dụng như mong đợi.
Cách Xử Lý API Số Trang
Đối với một API số trang, bắt đầu từ trang được định nghĩa bởi tài liệu của nó, sau đó dừng lại ở điều kiện kết thúc được tài liệu của nguồn. Nếu phản hồi bao gồm số trang tổng, hãy sử dụng nó. Nếu nó bao gồm một URL trang tiếp theo, hãy theo dõi URL đó. Nếu API trả về một trang cuối ngắn nhưng không có tổng số, hãy dừng khi lô là rỗng hoặc ngắn hơn kích thước yêu cầu chỉ khi quy tắc đó được tài liệu.
Tránh những giả định như “trang 1 luôn tồn tại” hoặc “trang cuối là trang đầu tiên rỗng.” Một số API trả về một cờ has_more rõ ràng. Ưu tiên điều đó hơn là suy diễn sự hoàn thành từ số hàng.
Cách Xử Lý Phân Trang Dựa Trên Con Trỏ
Các API dựa trên con trỏ thường trả về dữ liệu như thế này:
Các tên trường ở trên chỉ mang tính minh họa. Trong một tích hợp thực tế, sao chép chính xác trường phản hồi và tham số yêu cầu từ tài liệu API. Vòng lặp về mặt khái niệm là:
Thực hiện yêu cầu ban đầu mà không có con trỏ.
Xử lý và lưu trữ các mục đã trả về.
Đọc con trỏ tiếp theo từ phản hồi.
Dừng lại nếu nhà cung cấp không trả về con trỏ tiếp theo.
Ngược lại, gửi con trỏ đó không thay đổi với yêu cầu tiếp theo.
Lưu trữ con trỏ cùng với trạng thái công việc nếu bạn cần tiếp tục sau một sự cố. Đảm bảo việc ghi bản ghi là idempotent để một trang được thử lại sau một khoảng thời gian không trùng lặp các hàng ở hạ nguồn.
Cách Theo Dõi Liên Kết Phân Trang HTML
Khi nguồn là một trang HTML được phép thay vì một API, hãy sử dụng Requests để lấy trang, phân tích nó bằng Beautiful Soup, và giải quyết các URL tương đối với URL phản hồi hiện tại. Không thêm một số trang trừ khi sơ đồ URL của trang web ổn định và được ghi chép.
from urllib.parse import urljoin
from bs4 import BeautifulSoup
import requests
defget_next_url(page_url:str)->str|None: response = requests.get(page_url, timeout=(5,20)) response.raise_for_status() soup = BeautifulSoup(response.text,"html.parser") link = soup.select_one("a.next")if link isNone:returnNone href = link.get("href")return urljoin(response.url, href)if href elseNone
Tài liệu chính thức của Beautiful Soup giải thích cách selectors và thuộc tính phân tích hoạt động. Thêm một tập hợp URL đã thấy và một giới hạn trang tối đa xung quanh trợ giúp này. Nếu liên kết tiếp theo chỉ ra bên ngoài phạm vi miền được phép của bạn, hãy dừng lại và xem xét nó thay vì theo dõi tự động.
Cuộn Vô Hạn và Các Trang Được Phân Trang Bằng JavaScript
Cuộn vô hạn là một hành vi trình bày, không phải một giao thức phân trang chung. Trước tiên, kiểm tra xem trang web có tài liệu một API hoặc phơi bày một con trỏ tiếp theo được phép trong giao diện của riêng nó hay không. Nếu bạn sở hữu ứng dụng, hãy sử dụng cùng một hợp đồng API như frontend. Nếu bạn không, hãy xin phép trước khi tự động hóa trình duyệt hoặc gọi một điểm cuối không dành cho việc sử dụng của bạn.
Đối với một trang được ủy quyền yêu cầu JavaScript, Playwright cho Python có thể chờ một phần tử đã biết và thực hiện một số lượng hành động cuộn có giới hạn. Định nghĩa một số lượng mục tối đa hoặc một dấu hiệu kết thúc ổn định; một vòng lặp cuộn cho đến khi "không có gì thay đổi" có thể bị kẹt trên một trang có nội dung định kỳ hoặc lỗi mạng tạm thời.
Ngăn Chặn Thiếu Hồ Sơ và Vòng Lặp Phân Trang
Một công việc thu thập đáng tin cậy cần hơn một điều kiện vòng lặp:
Định danh ổn định: loại bỏ các bản ghi bằng ID hoặc URL chính thống.
Theo dõi tiến trình: ghi lại số trang, offset/cursor, số lượng trả về và tổng số lượng.
Công việc có giới hạn: đặt số trang tối đa, thời gian đã trôi qua và tỷ lệ yêu cầu.
Kiểm tra lược đồ: xác minh rằng trường thu thập và siêu dữ liệu phân trang tồn tại.
Kỷ luật thử lại: thử lại các lỗi tạm thời và thời gian chờ lỗi máy chủ với độ trễ; ngừng lại với các lỗi ủy quyền hoặc xác thực.
Kiểm tra tính nhất quán: so sánh các bản ghi duy nhất nhận được với tổng số đã báo cáo khi nguồn cung cấp một cái.
Trạng thái tiếp tục: lưu trữ trang hoặc con trỏ thành công cuối cùng nếu một công việc có thể kéo dài qua các khởi động lại.
Phân trang offset vẫn có thể bỏ lỡ hoặc lập lại các bản ghi khi tập dữ liệu cơ sở thay đổi trong quá trình chạy dài. Nếu nguồn hỗ trợ một mã thông báo ảnh chụp nhanh hoặc con trỏ ổn định, hãy ưu tiên nó cho dữ liệu có thể thay đổi. Nếu không, hãy ghi lại thời gian thu thập và sử dụng một lần đối chiếu sau đó.
Khi Nào Nên Sử Dụng Một Khung Thu Thập Thay Vì Phân Trang Thủ Công
Một vòng lặp offset/limit ngắn thường đủ cho một API. Sử dụng một khung như Crawlee cho Python khi bạn cần hàng đợi bền vững, phát hiện trang, nhiều bộ xử lý trang, thử lại qua một tập hợp lớn các URL, hoặc lưu trữ có thể tái sử dụng. Giữ các khái niệm riêng biệt: phân trang API lấy các trang trong một điểm cuối dữ liệu; thu thập trang phát hiện và truy cập các URL liên kết.
Phân trang Python là đáng tin cậy khi bạn tuân theo hợp đồng thực tế của nguồn, làm cho mỗi trang có giới hạn, và xác minh rằng các ID và tổng số đã thu thập có ý nghĩa. Bắt đầu với một lần chạy nhỏ và lưu phản hồi thô và trạng thái trang. Sử dụng con trỏ đúng như được trả về, giải quyết các liên kết HTML tiếp theo với urljoin(), và chuyển sang khung thu thập chỉ khi quy trình công việc yêu cầu nhiều hơn các yêu cầu API theo chuỗi.
Giữ Cho Thu Thập Trang Tách Biệt Với Phân Trang API
Nstdata Crawl có thể giúp phát hiện URL và trích xuất trang từ các trang web được phép. Tiếp tục tuân theo hợp đồng riêng của từng API về trang, offset, hoặc con trỏ trong mã ứng dụng.
Q: Làm thế nào để tôi lấy nhiều trang trong Python?
Đọc các trường phân trang của nguồn, lặp lại sử dụng số trang, offset, con trỏ, hoặc URL tiếp theo, và dừng lại khi có điều kiện kết thúc được tài liệu. Thêm một giới hạn số trang tối đa và loại bỏ các bản ghi bằng một khóa ổn định.
Q: Làm thế nào tôi xử lý phân trang với yêu cầu?
Sử dụng một requests.Session, đặt thời gian chờ, xác thực mỗi phản hồi, và cập nhật tham số phân trang từ hợp đồng phản hồi. Giữ lại tiến trình nếu cần tiếp tục thu thập.
Q: Làm thế nào tôi thu thập cuộn vô hạn?
Đối với một trang web bạn sở hữu hoặc được ủy quyền truy cập, sử dụng API đã được tài liệu hoặc một quy trình tự động hóa trình duyệt với điều kiện kết thúc rõ ràng và giới hạn mục. Không tự động hóa xung quanh các hạn chế truy cập.
Q: Làm thế nào tôi dừng một vòng lặp phân trang Python?
Dừng lại khi API cho biết không còn trang nào nữa, con trỏ không có, liên kết tiếp theo bị thiếu, độ lệch đạt tổng số, hoặc phản hồi trống—theo hành vi đã được ghi chép của API đó. Giữ một giới hạn trang cứng như một biện pháp an toàn.
Q: Làm thế nào để tôi quét phân trang con trỏ?
Gửi con trỏ không rõ được trả về bởi một phản hồi trong yêu cầu tiếp theo và dừng lại khi API không trả về con trỏ tiếp theo. Không tăng hoặc giải mã con trỏ trừ khi nhà cung cấp ghi chép hành vi đó.
Ivy Lin
Oct. 19th 2026
Thu thập toàn bộ trang web chỉ với một yêu cầu API
Tỷ lệ thành công 99,8% với kết xuất JavaScript
Nhận dữ liệu sạch, sẵn sàng cho LLM ở nhiều định dạng
Chuyển mọi trang web thành Markdown, HTML, JSON, liên kết, PDF và hơn thế nữa mà không cần quản lý hạ tầng thu thập dữ liệu.