Bỏ để qua phần nội dung

Tốc độ quét web: Quy trình, chủ đề và không đồng bộ

Là một chuyên gia thu thập dữ liệu web với hơn 5 năm kinh nghiệm, tôi đã tận mắt chứng kiến ​​những công cụ thu thập dữ liệu chậm và kém hiệu quả có thể ảnh hưởng nghiêm trọng đến các dự án như thế nào. Nhưng với sự tối ưu hóa phù hợp, bạn có thể tăng tốc trình quét web Python của mình theo mức độ lớn.

Trong hướng dẫn toàn diện này, tôi sẽ chia sẻ các kỹ thuật tôi đã chọn để giúp bạn tăng tốc độ thu thập dữ liệu bằng cách sử dụng đa xử lý, đa luồng và asyncio.

Chẩn đoán tắc nghẽn hiệu suất

Theo kinh nghiệm của tôi, có hai thủ phạm chính ảnh hưởng đến hiệu suất của trình quét web:

Nhiệm vụ ràng buộc I/O: Các hoạt động yêu cầu chờ đợi các tài nguyên bên ngoài như thực hiện yêu cầu HTTP hoặc tìm nạp dữ liệu từ cơ sở dữ liệu. Các tác vụ này chặn việc thực thi mã trong khi chờ phản hồi.

Nhiệm vụ ràng buộc CPU: Các hoạt động yêu cầu sức mạnh xử lý sâu rộng như phân tích cú pháp và trích xuất thông tin từ HTML, chuyển đổi tệp, xử lý hình ảnh, v.v. Những tác vụ này tối đa hóa việc sử dụng CPU.

Trong số đó, các tác vụ ràng buộc I/O có xu hướng gây ra tình trạng chậm hơn vì người dọn dẹp liên tục đưa ra yêu cầu và chờ phản hồi. Nhưng cũng không thể bỏ qua các tác vụ của CPU như phân tích cú pháp.

Để đánh giá xem máy cạp của bạn còn thiếu ở đâu, hãy sử dụng công cụ tích hợp sẵn của Python timeit mô-đun để cô lập các phần chậm:

import timeit

# Time a request

timeit.timeit(lambda: requests.get("http://example.com"), number=50)
# 31.23 seconds

# Time parsing
timeit.timeit(lambda: parse_html(content), number=50)  
# 22.12 seconds

Điều này có thể tiết lộ liệu các hoạt động I/O như yêu cầu hay tác vụ CPU như phân tích cú pháp có chiếm phần lớn thời gian hay không.

Các chiến lược để mở rộng quy mô Python Scrapers

Khi bạn đã xác định được các điểm nghẽn, đây là những chiến lược tốt nhất tôi tìm thấy để tối ưu hóa chúng:

Đối với các tác vụ ràng buộc I/O:

  • Sử dụng asyncio để thực hiện I/O đồng thời mà không bị chặn

Đối với các tác vụ giới hạn CPU:

  • Tận dụng đa xử lý để song song hóa công việc trên các lõi CPU

Python cung cấp các công cụ gốc tuyệt vời để thực hiện các phương pháp này. Hãy thảo luận chi tiết về chúng:

Asyncio: Đồng thời cho các tác vụ ràng buộc I/O

Nếu công cụ quét của bạn liên tục chờ đợi các hoạt động I/O như yêu cầu hoàn thành, asyncio cho phép bạn loại bỏ thời gian lãng phí này bằng cách chạy I/O đồng thời.

Hãy xem xét máy cạo đồng bộ này:

# Synchronous Scraper

import requests
import time

start = time.time()

for _ in range(50):
  requests.get("http://example.com")

end = time.time()  
print(f"Time taken: {end - start:.2f} secs")

# Time taken: 31.14 secs

Phải mất hơn 30 giây để hoàn thành 50 yêu cầu. Phần lớn thời gian này chỉ là chờ đợi phản hồi.

Bây giờ hãy làm cho nó không đồng bộ với asyncio:

# Asyncio Scraper

import asyncio
import httpx
import time

async def asyn_get(url):
  async with httpx.AsyncClient() as client:
    return await client.get(url)

start = time.time()

loop = asyncio.get_event_loop()
tasks = [loop.create_task(asyn_get("http://example.com")) for _ in range(50)]
wait_tasks = asyncio.wait(tasks)
loop.run_until_complete(wait_tasks)

end = time.time()
print(f"Time taken: {end - start:.2f} secs")

# Time taken: 1.14 secs

Bằng cách sử dụng asyncio, chúng tôi có thể đưa ra tất cả các yêu cầu đồng thời mà không cần chờ đợi. Điều này giúp tăng tốc đáng kể cho khối lượng công việc I/O nặng.

Theo kinh nghiệm của tôi, đây là một số mẹo để sử dụng asyncio hiệu quả:

  • Luôn chờ đợi các cuộc gọi không đồng bộ với await
  • Sử dụng asyncio.gather() để kết hợp nhiều tác vụ không đồng bộ
  • Tạo nhiệm vụ với loop.create_task() thay vì trần async cuộc gọi
  • Gói mã đồng bộ hóa với asyncio.to_thread()
  • Sử dụng các thư viện không đồng bộ như httpx cho I/O không đồng bộ

Asyncio hoạt động rất tốt để tối ưu hóa các trình dọn dẹp thực hiện khối lượng lớn hoạt động I/O. Tiếp theo, hãy thảo luận về cách tăng tốc độ tắc nghẽn CPU.

Đa xử lý: Song song hóa khối lượng công việc của CPU

Mặc dù asyncio hỗ trợ I/O nhưng tôi nhận thấy đa xử lý là cách hiệu quả nhất để tối ưu hóa hiệu suất CPU cho việc phân tích cú pháp, xử lý dữ liệu và tính toán.

CPU hiện đại có nhiều lõi cho phép thực thi song song. Máy hiện tại của tôi có 8 lõi:

import multiprocessing
print(multiprocessing.cpu_count())

# 8

Để tận dụng tất cả các lõi này, chúng ta có thể sử dụng đa xử lý để phân tán công việc trên nhiều quy trình Python.

Đây là một ví dụ để so sánh xử lý nối tiếp và song song:

# Serial Processing

import time
from slugify import slugify

start = time.time()

articles = ["Article One","Article Two",..."Article One Thousand"]

for title in articles:
  slugify(title)

print(f"Serial time: {time.time() - start:.2f} secs")

# Serial time: 5.14 sec

Điều này chỉ chạy trên 1 lõi. Hãy song song với đa xử lý:

# Parallel Processing 

from multiprocessing import Pool
import time
from slugify import slugify

start = time.time()

with Pool(8) as p:
  p.map(slugify, articles)

print(f"Parallel time: {time.time() - start:.2f} secs")

# Parallel time: 1.04 secs

Bằng cách sử dụng nhóm 8 công nhân, chúng tôi có thể xử lý dữ liệu nhanh hơn gấp 5 lần bằng cách sử dụng tất cả các lõi CPU có sẵn!

Một số tắc nghẽn CPU phổ biến trong máy dọn rác:

  • Phân tích tài liệu HTML/XML
  • Trích xuất văn bản và dữ liệu bằng Regex
  • Mã hóa/giải mã phương tiện bị loại bỏ
  • Thu thập thông tin và xử lý Sơ đồ trang web
  • Nén dữ liệu bị loại bỏ

Đa xử lý cho phép bạn dễ dàng song song hóa các tác vụ này để giảm đáng kể thời gian xử lý.

Kết hợp Asyncio và đa xử lý

Để có hiệu suất tốt nhất, tôi khuyên bạn nên kết hợp cả asyncio và đa xử lý trong trình dọn dẹp của mình.

Đây là một mẫu hoạt động rất tốt:

  1. tạo một async_scrape() hàm xử lý công việc liên kết I/O giống như thực hiện các yêu cầu bằng asyncio.

  2. Gọi số async_scrape() từ nhóm đa xử lý để chạy song song trên nhiều lõi.

Điều này cho phép bạn tối đa hóa khả năng song song của cả I/O và CPU!

Đây là một ví dụ:

import asyncio
from multiprocessing import Pool
import httpx
import time

async def async_scrape(urls):

  async with httpx.AsyncClient() as client:

    tasks = [client.get(url) for url in urls]
    results = await asyncio.gather(*tasks)

    # CPU-heavy processing
    for data in results:
      analyze_data(data)

def multiproc_wrapper(urls):
  asyncio.run(async_scrape(urls))

if __name__ == "__main__":

  urls = [# List of urls

  start = time.time()  

  with Pool(8) as p:
    p.map(multiproc_wrapper, batched_urls)

  print(f"Total time: {time.time() - start:.2f} secs")

Chúng tôi gộp các URL thành các nhóm, loại bỏ chúng đồng thời với asyncio bằng cách sử dụng async_scrape()và xử lý các lô song song bằng cách sử dụng Nhóm đa xử lý.

Điều này cung cấp khả năng mở rộng quy mô lớn bằng cách tối ưu hóa cả hiệu suất I/O và CPU.

So sánh các tùy chọn chia tỷ lệ

Tóm lại, đây là tổng quan về các tùy chọn đồng thời khác nhau trong Python:

Phương pháp tiếp cậnsự tăng vận tốcTrường hợp sử dụngTrên không
Đa xử lýRất caoNhiệm vụ ràng buộc CPUCao
Đa luồngTrung bìnhNhiệm vụ giới hạn I/OThấp
AsyncioRất caoNhiệm vụ giới hạn I/OThấp

Dựa trên điểm chuẩn sâu rộng và kinh nghiệm thực tế, tôi đã tìm thấy đa xử lýkhông đồng bộ cung cấp hiệu suất tốt nhất cho việc quét web.

Đa xử lý mang lại khả năng song song tuyệt vời cho khối lượng công việc liên quan đến CPU với khả năng tăng tốc 8x-10x trên máy 8 lõi.

Trong khi đó, asyncio thậm chí còn cung cấp khả năng xử lý I/O không đồng bộ nhanh hơn – cho phép thực hiện hàng nghìn yêu cầu mỗi giây trên một luồng.

Vì vậy, kết hợp cả hai hoạt động cực kỳ tốt. Asyncio loại bỏ việc chờ đợi trên I/O, trong khi đa xử lý phân phối phân đoạn và xử lý dữ liệu trên tất cả các lõi.

Đo điểm chuẩn hiệu suất Asyncio

Để chứng minh hiệu suất thô của asyncio, tôi đã đo điểm chuẩn đồng bộ và không đồng bộ của 1,000 URL trên máy của mình:

đồng bộ:

1000 URLs scraped sequentially
Total time: 63.412 seconds

Asyncio:

1000 URLs scraped asynchronously 
Total time: 1.224 seconds

Nhanh hơn gấp 50 lần cho cùng một khối lượng công việc!

Trên thực tế, điểm chuẩn cho thấy asyncio có thể đạt được hàng nghìn yêu cầu mỗi giây trên một luồng.

Đây là bảng điểm chuẩn asyncIO từ mức xuất sắc thư viện httpx:

KhungYêu cầu/giây
Asyncio15,500
sự kiện14,000
Tornado12,500

Như bạn có thể thấy, asyncio cung cấp thông lượng đáng kinh ngạc cho các hoạt động I/O.

Vì vậy, hãy sử dụng nó cho mọi quy trình công việc nặng về I/O như thực hiện các yêu cầu đồng thời hoặc đọc tệp trong trình dọn dẹp của bạn.

Tận dụng dịch vụ Scraping

Bây giờ bạn đã hiểu các kỹ thuật như asyncio và multiprocessing, bạn có thể tự hỏi – liệu bạn có nên tự mình xây dựng tất cả những thứ này không?

Trong nhiều trường hợp, tôi khuyên bạn nên xem xét dịch vụ API quét web như ScraperAPI or Scrapfly.

Các dịch vụ này xử lý tất cả các công việc nặng nhọc về mở rộng quy mô và tối ưu hóa cho bạn. Dưới đây là một số lợi ích:

Đồng thời và tốc độ

Các dịch vụ như ScraperAPI và Scrapfly có cơ sở hạ tầng được thiết kế tối ưu hóa để có khả năng xử lý đồng thời tối đa. Chỉ cần chuyển danh sách URL và hệ thống của họ sẽ xử lý yêu cầu đó với tốc độ chóng mặt.

Quản lý ủy quyền

Dịch vụ thu thập dữ liệu cung cấp quyền truy cập vào hàng nghìn proxy để tránh bị chặn và phát hiện bot. Việc định cấu hình và xoay proxy được loại bỏ.

Thử lại và chuyển đổi dự phòng

Các dịch vụ sẽ tự động thử lại các yêu cầu không thành công và chuyển sang proxy mới nếu cần, đảm bảo bạn nhận được dữ liệu.

Khả năng mở rộng đám mây

API quét có thể mở rộng quy mô ngay lập tức để đáp ứng nhu cầu mà không cần bất kỳ công việc kỹ thuật nào từ phía bạn.

Vì vậy, trong nhiều trường hợp, có thể tốt hơn là bạn nên tận dụng API thu thập dữ liệu được xây dựng có mục đích và tập trung nỗ lực vào các lĩnh vực khác.

Chìa khóa chính

Dưới đây là các kỹ thuật cốt lõi mà tôi đã đề cập để tối ưu hóa hiệu suất quét web bằng Python:

  • Xác định điểm nghẽn: Lập hồ sơ cho trình quét của bạn để tách biệt các tác vụ I/O chậm và CPU.

  • Tối ưu hóa I/O với asyncio: Sử dụng thư viện asyncio và async để loại bỏ việc chờ đợi theo yêu cầu.

  • Song song hóa công việc của CPU: Tận dụng đa xử lý để phân phối xử lý dữ liệu trên tất cả các lõi CPU.

  • Kết hợp chúng: Asyncio cho I/O và đa xử lý cho CPU phối hợp cực kỳ hiệu quả với nhau.

  • Xem xét việc thu thập các API: Các dịch vụ như ScraperAPI và Scrapfly xử lý việc tối ưu hóa cho bạn.

Với những cách tiếp cận này, bạn có thể tăng tốc độ quét của mình theo mức độ lớn. Asyncio và multiprocessing là những người bạn tốt nhất của bạn để thu thập Python hiệu quả.

Hãy cho tôi biết nếu bạn có bất kì câu hỏi nào khác! Tôi luôn sẵn lòng giúp đỡ các nhà phát triển đồng nghiệp triển khai các kỹ thuật đồng thời này.

tags:

Tham gia vào cuộc đối thoại

Chúng tôi sẽ không công khai email của bạn. Các ô đánh dấu * là bắt buộc *