Asyncio cho Web Scraping: Các Yêu Cầu Đồng Thời Được Thực Hiện Đúng Cách
TL;DR
asyncio cải thiện thông lượng cho nhiều chờ đợi mạng độc lập; nó không làm cho việc phân tích HTML nhanh hơn hoặc cấp phép để tăng tỷ lệ yêu cầu của một trang web.
Tái sử dụng một aiohttp.ClientSession, đặt total và connection timeouts, và ràng buộc cả concurrency toàn cục và theo từng host.
Một semaphore giới hạn các tác vụ đồng thời, không phải là yêu cầu mỗi giây. Thêm tốc độ rõ ràng theo từng host khi nguồn yêu cầu điều đó.
Thử lại chỉ những thất bại tạm thời như 429, 502, 503, và 504, với một độ trễ giới hạn và xử lý Retry-After; không thử lại mù quáng mọi phản hồi 4xx.
Đối với các trang web render bằng JavaScript hoặc khám phá đệ quy, gọi một API rendering và Crawl đã được quản lý thay vì mong đợi aiohttp thực thi JavaScript.
Khi Nào Nên Sử Dụng Asyncio ChO Web Scraping?
Sử dụng asyncio khi scraper dành phần lớn thời gian của nó để chờ đợi nhiều phản hồi HTTP được phép. Một worker có thể quản lý nhiều yêu cầu đang thực hiện mà không cần một thread cho mỗi yêu cầu. Lợi ích phụ thuộc vào độ trễ nguồn, tỷ lệ yêu cầu được phép, chi phí phân tích, và lưu trữ downstream. Nếu nguồn chỉ cho phép một yêu cầu mỗi vài giây, một client không đồng bộ không thể hợp pháp biến nó thành một crawl với tỷ lệ cao. Sản phẩm Nstdata Crawl là một lựa chọn được quản lý cho các bước thu thập và chuyển đổi được thảo luận ở đây.
Tài liệu giải thích cách các coroutine được lên lịch. Hướng dẫn khuyến nghị tái sử dụng một session thay vì tạo một cái cho mỗi yêu cầu. Một session giữ một pool kết nối và cấu hình chia sẻ. Sử dụng hướng dẫn này chỉ cho các nguồn bạn được phép thu thập.
Tạo một danh sách cho phép ngắn gọn các URL đã được phê duyệt, lưu ý tỷ lệ yêu cầu được phép của từng host và chọn một User-Agent có thể liên hệ. Đối với Nstdata Crawl, nhận một tài khoản và khóa API; tiêu đề xác thực đã được ghi rõ là x-api-key. Bạn không cần các giá trị Proxy Host, Port, Username, Password, hoặc Protocol cho một yêu cầu Crawl được quản lý tiêu chuẩn vì Nstdata xử lý định tuyến proxy.
Độ Đồng Thời Khác Nhau Như Thế Nào So Với Giới Hạn Tỷ Lệ?
Độ đồng thời là số yêu cầu đang thực hiện tại một thời điểm. Tỷ lệ là tần suất mà các yêu cầu mới bắt đầu. Một semaphore với ba có thể tạo ra ba yêu cầu cùng một lúc, sau đó ba yêu cầu khác ngay khi phản hồi hoàn tất. Điều đó có thể vi phạm tốc độ mong muốn của một nguồn mặc dù chỉ có ba yêu cầu đang hoạt động. Sử dụng một khoảng thời gian bắt đầu theo từng host hoặc token bucket khi một host yêu cầu một tỷ lệ tối đa.
Đặt giới hạn ở ba cấp độ: tổng số tác vụ đang hoạt động, limit_per_host của pool kết nối, và khoảng thời gian bắt đầu theo từng host. Giữ chúng bảo thủ trong một pilot và điều chỉnh dựa trên chính sách nguồn, phản hồi 429, độ trễ, và tỷ lệ lỗi. Ví dụ bên dưới sử dụng một giây giữa các lần bắt đầu theo từng host và tối đa ba kết nối theo từng host; đây là các thiết lập minh họa, không phải là tỷ lệ an toàn phổ quát.
Làm Thế Nào Để Bạn Fetch Các Trang ĐỒNG THỜI Với Aiohttp?
Phương Pháp 1: Chạy Một HTML Fetcher Có Giới Hạn
Tốt nhất cho: Nhiều trang tĩnh mà dữ liệu xuất hiện trong phản hồi HTTP ban đầu.
Bước 1. Lưu script dưới đây với tên async_scrape.py. Thay thế URLS bằng các URL được phê duyệt và điều chỉnh MIN_HOST_INTERVAL theo chính sách nguồn thực tế.
import asyncio
import time
from collections import defaultdict
from urllib.parse import urlparse
import aiohttp
from bs4 import BeautifulSoup
URLS =["https://example.com/"]# Thay thế bằng các URL được phê duyệt.MAX_CONCURRENT =8MAX_PER_HOST =3MIN_HOST_INTERVAL =1.0RETRYABLE ={429,502,503,504}classHostPacer:def__init__(self, interval:float): self.interval = interval
self.locks = defaultdict(asyncio.Lock) self.next_start = defaultdict(float)asyncdefwait(self, host:str):asyncwith self.locks[host]: delay = self.next_start[host]- time.monotonic()if delay >0:await asyncio.sleep(delay) self.next_start[host]= time.monotonic()+ self.interval
defretry_delay(header, attempt:int)->float:if header and header.isdigit():returnmin(float(header),60.0)returnmin(2** attempt,30.0)asyncdeffetch(session, pacer, semaphore, url): host = urlparse(url).hostname
ifnot host or urlparse(url).scheme notin{"http","https"}:raise ValueError(f"URL không được hỗ trợ: {url}")for attempt inrange(3):await pacer.wait(host)try:asyncwith semaphore:asyncwith session.get(url)as response:if response.status in RETRYABLE: delay = retry_delay(response.headers.get("Retry-After"), attempt)else: response.raise_for_status() html =await response.text() soup = BeautifulSoup(html,"html.parser") title = soup.title.get_text(" ", strip=True)if soup.title elseNoneifnot title:raise ValueError(f"Thiếu tiêu đề: {url}")return{"url":str(response.url),"status": response.status,"title": title}except(aiohttp.ClientError, asyncio.TimeoutError):if attempt ==2:raise delay =min(2** attempt,30.0)if attempt ==2:raise RuntimeError(f"Đạt giới hạn thử lại: {url}")await asyncio.sleep(delay)asyncdefmain(): timeout = aiohttp.ClientTimeout(total=30, connect=10) connector = aiohttp.TCPConnector(limit=MAX_CONCURRENT, limit_per_host=MAX_PER_HOST) headers ={"User-Agent":"ExampleResearchBot/1.0 (liên hệ: team@example.org)"} pacer = HostPacer(MIN_HOST_INTERVAL) semaphore = asyncio.Semaphore(MAX_CONCURRENT)asyncwith aiohttp.ClientSession(timeout=timeout, connector=connector, headers=headers)as session: results =await asyncio.gather(*(fetch(session, pacer, semaphore, url)for url in URLS), return_exceptions=True,)for url, result inzip(URLS, results):print(url, result)if __name__ =="__main__": asyncio.run(main())
Bước 2. Chạy python async_scrape.py và kiểm tra từng kết quả hoặc ngoại lệ. Một phản hồi thành công từ example.com nên chứa tiêu đề tài liệu của nó; tập lệnh không khẳng định rằng mỗi trang mục tiêu đều có phần tử <title>.
Bước 3. Thêm khả năng lưu trữ chỉ sau khi xác thực các trường được trích xuất. Lưu trữ URL, thời gian lấy, phiên bản phân tích, và lý do từ chối. Thay thế print bằng một ghi idempotent dựa trên một URL chuẩn hoặc ID nguồn.
Giới hạn: Trợ thủ Retry-After ở trên xử lý giây số. Một giá trị HTTP-date cần một bộ phân tích ngày trước khi sử dụng trong sản xuất. Ví dụ cũng giữ khóa định mức theo từng máy chủ trong khi chờ đợi, cố ý tuần tự hóa các yêu cầu bắt đầu cho máy chủ đó.
Thông số kỹ thuật Semantics của HTTP định nghĩa hành vi tiêu đề liên quan tới trạng thái và thử lại mà khách hàng phải hiểu.
Bạn Xử Lý Hàng Nghìn URL Như Thế Nào Mà Không Tạo Ra Hàng Nghìn Nhiệm Vụ Cùng Một Lúc?
Phương pháp 2: Sử Dụng Hàng Đợi Công Việc Giới Hạn
Tốt nhất cho: Danh sách URL lớn hoặc các trang được phát hiện liên tục.
Bước 1. Đặt các URL đã được phê duyệt vào một hàng đợi bền vững hoặc cơ sở dữ liệu với một khóa duy nhất, số lần thử, và thời gian lấy tiếp theo có thể. Một asyncio.Queue trong bộ nhớ rất hữu ích trong một quy trình nhưng không thể phục hồi sau khi máy bị hỏng.
Bước 2. Bắt đầu một số lượng công nhân cố định mà mỗi công nhân kéo một URL, gọi đến chức năng lấy giới hạn, xác thực bản ghi, và công nhận nhiệm vụ chỉ sau khi đã ghi đầu ra bền vững.
Bước 3. Ghi lại các thất bại với lý do và thời gian thử lại. Các URL chết mà vượt quá số lần thử lại hoặc vi phạm chính sách. Áp dụng áp lực ngược khi tầng phân tích hoặc lưu trữ bị trễ.
Giới hạn:asyncio.gather trên một triệu coroutines URL tiêu thụ bộ nhớ ngay cả khi có một semaphore. Hàng đợi giới hạn việc tạo nhiệm vụ và cung cấp một nơi để checkpoint công việc.
Bước 1. Nhận một khóa API Nstdata và xác định phạm vi URL đã được phê duyệt. API Crawl tài liệu xác thực x-api-key và các công việc trang với maxDepth, maxPages, includeUrls, và excludeUrls.
Bước 2. Gửi một công việc có giới hạn đến Nstdata Crawl. Kiểm tra trạng thái và truy xuất các trang đã hoàn thành thông qua các điểm cuối được ghi chép. Nstdata xử lý việc phát hiện trang, quét đệ quy, kết xuất JavaScript và định tuyến proxy.
Bước 3. Phân tích Markdown, HTML, dữ liệu thô, liên kết, ảnh chụp màn hình, hoặc PDF được trả về theo nhu cầu ứng dụng của bạn. Xác thực nội dung và lưu trữ nguồn gốc trước khi lập chỉ mục hoặc huấn luyện.
Giới hạn: API được quản lý không thay thế quyền truy cập nguồn của bạn, lập lịch trình qua các trang, hoặc trích xuất sản phẩm tùy ý. Đừng sử dụng nó để tránh các hạn chế truy cập.
Tại Sao Các Yêu Cầu Đối Chứng Trùng Lặp Lại Thất Bại Hoặc Trả Về Dữ Liệu Xấu?
429 có nghĩa là nguồn đang giới hạn tần suất; giảm số lượng bắt đầu cho mỗi máy chủ và tôn trọng hướng dẫn thử lại trong phản hồi. Một thời gian chờ có thể chỉ ra sự cố mạng hoặc nguồn chậm; thử lại một số lần nhỏ và ghi lại thất bại thay vì thử lại không giới hạn. Một 200 với tiêu đề trống có thể là một shell JavaScript, trang đồng ý, hoặc không phù hợp với bộ phân tích. Kiểm tra HTML được trả về và hành vi mạng của trình duyệt trước khi thay đổi độ đồng thời.
Giám sát số lượng hồ sơ được chấp nhận mỗi giây, độ trễ trung vị và đuôi, tỷ lệ 429, số lần thử lại, và độ tuổi hàng đợi theo máy chủ. Một số lượng yêu cầu cao với tỷ lệ hồ sơ được chấp nhận thấp là một vấn đề chất lượng. Nếu một miền bắt đầu thất bại, tạm dừng miền đó trong khi các nguồn được phê duyệt khác tiếp tục.
Làm Thế Nào Để Bạn Giúp Ví Dụ An Toàn Đối Với Sản Xuất?
Chuyển đầu vào URL vào một hàng đợi bền bỉ và lưu trữ một kết quả cuối cùng cho mỗi URL. Một cuộc gọi gather lớn tạo ra một coroutine cho mỗi đầu vào trước khi semaphore chạy, vì vậy nó không phù hợp cho một danh sách rất lớn. Một nhóm công nhân cố định đọc từ hàng đợi giữ cho bộ nhớ bị giới hạn và cho phép thời gian thuê của một công nhân bị lỗi hết hạn. Sự xác nhận cần theo sau việc lưu trữ bền bỉ của hồ sơ được chấp nhận hoặc kết quả thất bại.
Thêm một hồ sơ chính sách cụ thể cho nguồn. Khoảng thời gian một giây và ba kết nối mỗi máy chủ trong ví dụ là minh họa. Cài đặt thực tế nên đến từ mô hình truy cập được允许 của trang và một piloth. Các máy chủ khác nhau có thể cần các khoảng thời gian bắt đầu khác nhau. Một semaphore toàn cầu bảo vệ quy trình của bạn, nhưng nó không ngăn chặn nhiều công nhân phân tán vượt quá ngân sách của một máy chủ. Đối với các đợt quét phân tán, đặt trạng thái tần suất trong một trình lập lịch chia sẻ hoặc phân bổ mỗi máy chủ cho một nhóm công nhân phối hợp.
Cải thiện việc xử lý thử lại trước các lần chạy lớn. Các giá trị Retry-After số được hỗ trợ trong ví dụ; các giá trị HTTP-date cần được phân tích. Thêm một khoảng thời gian ngẫu nhiên để tránh nhiều công nhân thử lại cùng một lúc. Đặt thời gian tối đa đã trôi qua và số lần thử, sau đó gán nhãn chết cho các nhiệm vụ không thể thành công. Đừng thử lại 401, 403, hoặc các URL không hợp lệ như thể nhiều lần thử sẽ grant quyền truy cập.
Cuối cùng, giữ việc phân tích HTML ra khỏi vòng lặp sự kiện nếu nó trở nên nặng CPU. Các hoạt động Beautiful Soup nhỏ có thể chấp nhận được, nhưng việc phân tích các trang rất lớn hoặc thực hiện trích xuất phức tạp trong cùng một vòng lặp có thể trì hoãn tất cả các yêu cầu. Đo lường độ trễ vòng lặp sự kiện và di chuyển việc phân tích nặng nề sang một quy trình công nhân hoặc một giai đoạn riêng biệt khi cần thiết. I/O mạng đồng bộ và công việc CPU song song giải quyết các nút thắt khác nhau.
Bạn Nên Giám Sát Những Gì Trong Một Trình Quét Đối Chứng?
Ghi lại độ trễ và kết quả theo máy chủ. Trung bình toàn cầu ẩn giấu sự thật rằng một nguồn đang hết thời gian hoặc trả về các trang lỗi mềm. Theo dõi các yêu cầu đang hoạt động, số lần yêu cầu bắt đầu theo máy chủ, tỷ lệ 429, thời gian hết hạn, số byte phản hồi, sản lượng hồ sơ được chấp nhận, số lần thử lại, và độ tuổi hàng đợi. Cảnh báo về sự suy giảm chất lượng kéo dài, không chỉ đơn giản là sự cố quy trình.
Sử dụng một mẫu chấp nhận sau khi thay đổi độ đồng thời. Tốc độ truy cập cao hơn có thể làm tăng các phản hồi không hoàn chỉnh, các trang thách thức, hoặc tải nguồn. So sánh hồ sơ được chấp nhận mỗi giây và mỗi đơn vị chi phí, và xác thực rằng các quy tắc bên nguồn vẫn được tôn trọng. Nếu sản lượng được chấp nhận giảm trong khi số lượng yêu cầu tăng, giảm tốc độ và kiểm tra một mẫu trước khi mở rộng thêm.
Giữ số lượng thô và được chấp nhận riêng biệt trong bảng điều khiển. Một sự bùng nổ của các phản hồi 200 không phải là thành công nếu hầu hết các trang chứa một mẫu lỗi. Xác nhận một đánh dấu trang mong đợi, tiêu đề, loại nội dung, và các trường yêu cầu trước khi đếm một hồ sơ là được chấp nhận. Điều này cung cấp cho đội vận hành một tín hiệu để tạm dừng một nguồn trong khi phần còn lại của hàng đợi tiếp tục.
H: Một semaphore có thực thi yêu cầu mỗi giây không?
Không. Một semaphore giới hạn công việc đồng thời. Sử dụng một cơ chế điều chỉnh tốc độ theo máy chủ để kiểm soát số lần yêu cầu bắt đầu theo thời gian.
H: aiohttp có thể quét một trang được kết xuất bằng JavaScript không?aiohttp lấy phản hồi HTTP nhưng không thực thi JavaScript trên trang. Sử dụng một phương pháp Crawl được quản lý hoặc render trình duyệt cho nội dung được tạo ra sau khi tải.
Q: Tôi có nên sử dụng asyncio.gather cho một triệu URL không?
Không. Cung cấp một tập hợp giới hạn các worker từ một hàng đợi bền vững để việc sử dụng bộ nhớ và phục hồi công việc vẫn được kiểm soát.
Hướng dẫn Python này di chuyển từ một trang tĩnh đến các bản ghi đã được xác thực và việc quản lý việc hiển thị. Nó bao gồm các ví dụ có thể chạy, chẩn đoán lỗi, kiểm soát nguồn và một hợp đồng dữ liệu sẵn sàng cho AI.
Marcus Chen
Oct. 29th 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.