🎯 Trình duyệt đám mây tùy chỉnh, chống phát hiện được hỗ trợ bởi Chromium tự phát triển, thiết kế dành cho trình thu thập dữ liệu webtác nhân AI. 👉Dùng thử ngay
Quay lại blog

Polars Web Scraping: Từ Trang Chạy Thực Tế đến DataFrame Nhanh Chóng

James Thompson
James Thompson

Scraping and Proxy Management Expert

22-Jul-2026

Tóm tắt:

  • Polars là một thư viện DataFrame nhanh, dựa trên Arrow, và nó kết hợp với Scrapeless để biến một trang web trực tiếp thành một khung có kiểu dữ liệu và có thể truy vấn.
  • Polars không có khách hàng HTTP và không có trình phân tích HTML, vì vậy các bước lấy dữ liệu và trích xuất đến từ Scrapeless và một trình phân tích; Polars đảm nhiệm khi các bản ghi đã tồn tại.
  • Xây dựng một khung từ danh sách các từ điển với pl.DataFrame(records), và schema được xác định từ hàng đầu tiên khi bạn chuyển đổi giá trị trong quá trình trích xuất.
  • API biểu thức thay thế việc đánh chỉ mục nối chuỗi của pandas: pl.col("price_gbp").mean() bên trong group_by(...).agg(...) đọc và chạy một cách rõ ràng.
  • Đường đi lười biếng, df.lazy()...collect(), cho phép Polars lên kế hoạch và tối ưu hóa toàn bộ truy vấn trước khi nó chạm vào dữ liệu, đây là nơi nó vượt trội trong các tập dữ liệu lớn hơn.
  • Bắt đầu với gói miễn phí Scrapeless và chỉ định bước lấy dữ liệu tại danh mục của riêng bạn.

Polars luôn xuất hiện trong công việc dữ liệu vì nó nhanh và API của nó dễ chịu, nhưng hầu như mọi hướng dẫn đều đưa cho nó một tệp Parquet đã tồn tại. Việc cạo dữ liệu là tình huống ngược lại. Dữ liệu là trực tiếp, nó là HTML, và nó chưa có kiểu cho đến khi bạn xác định kiểu. Hướng dẫn này chuyển một danh mục sách công khai từ một URL thành một DataFrame Polars mà bạn có thể nhóm và tổng hợp, và nó trung thực về một điều mà Polars không thể làm cho bạn: lấy trang.

Bước lấy dữ liệu đến từ API Cạo Dữ Liệu Toàn Cầu Scrapeless, cái trả về HTML đã được hiển thị cho một URL công khai. Một trình phân tích khiến HTML đó thành các bản ghi. Polars làm phần còn lại, và làm điều đó nhanh chóng.

Điều gì Polars thêm vào quy trình cạo dữ liệu

Polars là một thư viện DataFrame được xây dựng trên định dạng bộ nhớ cột Apache Arrow, điều này làm cho các cột có kiểu và tổng hợp nhóm nhanh chóng. Đối với dữ liệu được cạo, lợi ích rõ ràng: khi các bản ghi của bạn có trong một khung, việc làm sạch và tóm tắt chúng chỉ là một vài biểu thức chứ không phải là một vòng lặp viết tay, và các cột giữ nguyên kiểu của chúng. Polars dựa vào định dạng cột Apache Arrow cho cách tổ chức trong bộ nhớ của nó, vì vậy một cột giá là một cột số thực, không phải là một danh sách chuỗi mà bạn phải phân tích lại trong mỗi thao tác.

Cài đặt

Polars xử lý khung; một trình phân tích xử lý HTML. Cài đặt cả hai.

bash Copy
pip install polars lxml

Đặt khóa API Scrapeless của bạn một lần trong shell. Sử dụng khóa thật khi chạy và giữ khóa giữ chỗ ra khỏi mã nguồn của bạn.

bash Copy
export SCRAPELESS_API_KEY="sk_your_key_here"

Lấy dữ liệu, Trích xuất và Xây dựng một khung

Giai đoạn đầu tiên lấy trang web, lấy một bản ghi cho mỗi sản phẩm, và xây dựng DataFrame. Bởi vì Polars không thể lấy hoặc phân tích HTML, giai đoạn này là nơi Scrapeless và trình phân tích làm việc của họ; Polars nhận các bản ghi đã hoàn thành.

python Copy
import json
import os
import urllib.request

import polars as pl
from lxml import html as lxhtml

API_URL = "https://api.scrapeless.com/api/v2/unlocker/request"
RATINGS = {"One": 1, "Two": 2, "Three": 3, "Four": 4, "Five": 5}


def fetch_html(url: str) -> str:
    payload = json.dumps(
        {"actor": "unlocker.webunlocker", "input": {"url": url, "js_render": False, "headless": False}}
    ).encode()
    request = urllib.request.Request(
        API_URL,
        data=payload,
        headers={"x-api-token": os.environ["SCRAPELESS_API_KEY"], "Content-Type": "application/json"},
        method="POST",
    )
    with urllib.request.urlopen(request, timeout=90) as response:
        return json.loads(response.read())["data"]


dom = lxhtml.fromstring(fetch_html("https://books.toscrape.com/"))
records = []
for card in dom.cssselect("article.product_pod"):
    records.append(
        {
            "title": card.cssselect("h3 a")[0].get("title"),
            "price_gbp": float(card.cssselect("p.price_color")[0].text_content().strip().lstrip("£")),
            "rating": RATINGS[card.cssselect("p.star-rating")[0].get("class").split()[-1]],
            "in_stock": "In stock" in card.cssselect("p.instock.availability")[0].text_content(),
        }
    )

df = pl.DataFrame(records)
print(f"records extracted: {df.height}")
print("schema:", dict(df.schema))

Việc trích xuất sử dụng tiêu chuẩn CSS Selectors thông qua cssselect của lxml, và nó chuyển đổi mỗi giá trị khi nó được đọc: giá trở thành float, từ đánh giá sao trở thành một số nguyên, và tình trạng sẵn có trở thành một boolean. Việc chuyển đổi đó là thói quen quan trọng. Xây dựng các bản ghi với các kiểu dữ liệu Python thực và Polars suy diễn một schema thực từ chúng.

text Copy
records extracted: 20
schema: {'title': String, 'price_gbp': Float64, 'rating': Int64, 'in_stock': Boolean}

Hai mươi thẻ sản phẩm trên trang đầu tiên trở thành một khung với bảng kiểu được gõ: String, Float64, Int64Boolean. Không có cột nào được để lại dưới dạng văn bản mà sau đó phải được phân tích lại.

Chuyển đổi với Biểu thức

Polars thay thế việc lập chỉ mục chuỗi bằng API biểu thức, và đây là phần đáng học hỏi. Một biểu thức như pl.col("price_gbp").mean() mô tả một phép toán mà Polars thực hiện hiệu quả, và các biểu thức kết hợp bên trong filter, group_byagg.

python Copy
summary = (
    df.lazy()
    .filter(pl.col("in_stock"))
    .group_by("rating")
    .agg(pl.len().alias("books"), pl.col("price_gbp").mean().round(2).alias("avg_price"))
    .sort("rating")
    .collect()
)
print(summary)

df.lazy() biến khung háo hức thành một kế hoạch truy vấn, chuỗi mô tả công việc, và .collect() thực thi nó. Với tập nhỏ này, sự khác biệt là không thể nhìn thấy, nhưng con đường lười biếng cho phép Polars phân tích toàn bộ truy vấn và bỏ qua công việc trước khi nó đọc một giá trị nào, đó là lý do tại sao nó có khả năng mở rộng. Các nhóm tổng hợp nhóm các cuốn sách còn hàng theo xếp hạng sao và báo cáo số lượng và giá trung bình cho mỗi nhóm.

text Copy
shape: (5, 3)
┌────────┬───────┬───────────┐
│ rating ┆ books ┆ avg_price │
│ ---    ┆ ---   ┆ ---       │
│ i64    ┆ u32   ┆ f64       │
╞════════╪═══════╪═══════════╡
│ 1      ┆ 6     ┆ 40.02     │
│ 2      ┆ 3     ┆ 36.83     │
│ 3      ┆ 3     ┆ 42.32     │
│ 4      ┆ 4     ┆ 31.1      │
│ 5      ┆ 4     ┆ 39.75     │
└────────┴───────┴───────────┘

Kịch bản hoàn chỉnh

Kết hợp các giai đoạn, thêm một biểu thức nữa để tìm cuốn sách năm sao rẻ nhất, và lưu khung dưới dạng Parquet.

python Copy
import json
import os
import urllib.request

import polars as pl
from lxml import html as lxhtml

API_URL = "https://api.scrapeless.com/api/v2/unlocker/request"
RATINGS = {"One": 1, "Two": 2, "Three": 3, "Four": 4, "Five": 5}


def fetch_html(url: str) -> str:
    payload = json.dumps(
        {"actor": "unlocker.webunlocker", "input": {"url": url, "js_render": False, "headless": False}}
    ).encode()
    request = urllib.request.Request(
        API_URL,
        data=payload,
        headers={"x-api-token": os.environ["SCRAPELESS_API_KEY"], "Content-Type": "application/json"},
        method="POST",
    )
    with urllib.request.urlopen(request, timeout=90) as response:
        return json.loads(response.read())["data"]


dom = lxhtml.fromstring(fetch_html("https://books.toscrape.com/"))
records = []
for card in dom.cssselect("article.product_pod"):
    records.append(
        {
            "title": card.cssselect("h3 a")[0].get("title"),
            "price_gbp": float(card.cssselect("p.price_color")[0].text_content().strip().lstrip("£")),
            "rating": RATINGS[card.cssselect("p.star-rating")[0].get("class").split()[-1]],
            "in_stock": "In stock" in card.cssselect("p.instock.availability")[0].text_content(),
        }
    )

df = pl.DataFrame(records)
print(f"records extracted: {df.height} | columns: {df.columns}")

summary = (
    df.lazy()
    .filter(pl.col("in_stock"))
    .group_by("rating")
    .agg(pl.len().alias("books"), pl.col("price_gbp").mean().round(2).alias("avg_price"))
    .sort("rating")
    .collect()
)
print(summary)

cheapest = df.filter(pl.col("rating") == 5).sort("price_gbp").select("title", "price_gbp").head(1)
print("cuốn sách 5 sao rẻ nhất:", cheapest.to_dicts())

df.write_parquet("books.parquet")
print(f"bytes parquet: {os.path.getsize('books.parquet')}")

Chạy báo cáo từng bước, kết thúc với một tệp Parquet giữ lại kiểu cho người đọc tiếp theo.

text Copy
records extracted: 20 | columns: ['title', 'price_gbp', 'rating', 'in_stock']
shape: (5, 3)
┌────────┬───────┬───────────┐
│ rating ┆ books ┆ avg_price │
│ ---    ┆ ---   ┆ ---       │
│ i64    ┆ u32   ┆ f64       │
╞════════╪═══════╪═══════════╡
│ 1      ┆ 6     ┆ 40.02     │
│ 2      ┆ 3     ┆ 36.83     │
│ 3      ┆ 3     ┆ 42.32     │
│ 4      ┆ 4     ┆ 31.1      │
│ 5      ┆ 4     ┆ 39.75     │
└────────┴───────┴───────────┘
cuốn sách 5 sao rẻ nhất: [{'title': 'Set Me Free', 'price_gbp': 17.46}]
bytes parquet: 2233

write_parquet lưu trữ các cột kiểu trong định dạng tệp Apache Parquet, vì vậy quy trình tiếp theo đọc price_gbp trở lại dưới dạng float mà không cần chuyển đổi lại. Hướng dẫn người dùng Polars tài liệu API biểu thức và lười biếng đầy đủ khi bạn vượt qua ví dụ này.

Điều Polars sẽ không làm

Polars là một công cụ xử lý dataframe, và đó là toàn bộ giới hạn: nó không có client HTTP và không có bộ phân tích HTML. Nó sẽ không yêu cầu một URL, nó sẽ không hiển thị JavaScript và nó sẽ không chuyển đổi các thẻ <article> thành các dòng. Điều đó được thiết kế như vậy, và đó là lý do tại sao quy trình làm việc này sử dụng hai công cụ phía trước. Scrapeless lấy trang và một bộ phân tích đọc đánh dấu thành các bản ghi. Nếu bạn cần một cách xử lý sâu hơn cho bước phân tích, hướng dẫn về sử dụng BeautifulSoup cho web scraping đề cập đến việc trích xuất HTML trong Python, và các bản ghi tương tự được đưa thẳng vào pl.DataFrame.

Khi một mục tiêu hiển thị nội dung của nó bằng JavaScript, lần lấy đầu tiên sẽ trả về một khung rỗng và vòng lặp trích xuất sẽ không tìm thấy thẻ. Đặt js_render thành True trong yêu cầu để Scrapeless trả về trang sau khi các script của nó chạy, và để nó là False khi đánh dấu đã được hiển thị bởi máy chủ, như danh mục này.

Trước khi bạn mở rộng quá trình thu thập qua trang đầu tiên, hãy đọc robots.txt và các điều khoản của mục tiêu. Giao thức loại trừ Robots nêu rõ các đường dẫn mà một trang web yêu cầu các client tự động tránh, và giữ bên trong nó giúp quy trình làm việc bền vững. Giữ khối lượng giới hạn và dữ liệu công khai, như ví dụ này.

Bạn đã sẵn sàng thử nghiệm với dữ liệu của riêng mình chưa? Tạo một tài khoản Scrapeless miễn phí và thay đổi URL và bộ chọn trong giai đoạn lấy dữ liệu.

Kết luận

Polars chuyển đổi các bản ghi đã được thu thập thành một khung có kiểu, có thể truy vấn với rất ít mã, miễn là cái gì đó khác đưa trang đến trước. Scrapeless lấy HTML, một bộ phân tích xây dựng các bản ghi với các kiểu thực, và Polars nhóm, tổng hợp và lưu trữ chúng qua API biểu thức và lười biếng của nó. Bắt đầu từ kịch bản hoàn chỉnh, thay thế URL và bộ chọn bằng mục tiêu của riêng bạn, và hướng tới cách lười biếng khi dữ liệu của bạn lớn lên.

Bắt đầu với gói miễn phí của Scrapeless để lấy các trang của riêng bạn, và kiểm tra giá cả của Scrapeless khi bạn lên kế hoạch cho một công việc định kỳ.

Câu hỏi thường gặp

Q: Polars có tự động thu thập các trang web không?

Không. Polars là một thư viện DataFrame không có client HTTP và không có bộ phân tích HTML, vì vậy nó không thể yêu cầu một URL hoặc đọc đánh dấu. Kết hợp nó với một lớp lấy dữ liệu như Scrapeless và một bộ phân tích như lxml hoặc BeautifulSoup; Polars đảm nhận khi các bản ghi tồn tại.

Q: Tại sao lại chọn Polars thay vì pandas cho dữ liệu đã thu thập?

Polars được xây dựng trên định dạng cột Apache Arrow và cung cấp một công cụ truy vấn lười biếng, vì vậy việc tổng hợp và lọc nhóm trên các khung lớn là nhanh chóng, và API biểu thức là nhất quán. Một sự trao đổi liên quan đến việc thu thập: pandas có read_html cho các bảng, trong khi Polars yêu cầu bạn tự xây dựng khung từ các bản ghi, điều này phù hợp hơn với các trang kiểu thẻ hơn là chỉ bảng.

Q: Sự khác biệt giữa lười và háo hức trong Polars là gì?

Một DataFrame háo hức thực hiện ngay lập tức mỗi thao tác, trong khi df.lazy() xây dựng một kế hoạch truy vấn chỉ thực thi khi bạn gọi .collect(). Đường lười cho phép Polars tối ưu hóa toàn bộ truy vấn và bỏ qua công việc không cần thiết, đó là lý do tại sao nó mở rộng tốt hơn trên các tập dữ liệu lớn mặc dù kết quả là giống hệt nhau trên các tập nhỏ.

Q: Tại sao các số đã thu thập của tôi lại xuất hiện dưới dạng chuỗi trong Polars?

Các giá trị đã thu thập đến dưới dạng văn bản, và nếu bạn truyền chúng cho pl.DataFrame mà không thay đổi, loại cột sẽ là String. Ép buộc mỗi giá trị trong quá trình trích xuất, như ví dụ ép giá thành float và từ đánh giá thành số nguyên, để lược đồ được kiểu từ hàng đầu tiên và không cột nào cần phải phân tích lại sau này.

Q: Làm thế nào tôi có thể xây dựng một Polars DataFrame từ danh sách các bản ghi đã thu thập?

Thu thập mỗi bản ghi như một từ điển với các khóa nhất quán và truyền danh sách cho pl.DataFrame(records). Polars suy diễn lược đồ từ các giá trị, vì vậy các giá trị có kiểu trong các từ điển tạo ra một khung có kiểu, sẵn sàng cho API biểu thức.

Tại Scrapless, chúng tôi chỉ truy cập dữ liệu có sẵn công khai trong khi tuân thủ nghiêm ngặt các luật, quy định và chính sách bảo mật trang web hiện hành. Nội dung trong blog này chỉ nhằm mục đích trình diễn và không liên quan đến bất kỳ hoạt động bất hợp pháp hoặc vi phạm nào. Chúng tôi không đảm bảo và từ chối mọi trách nhiệm đối với việc sử dụng thông tin từ blog này hoặc các liên kết của bên thứ ba. Trước khi tham gia vào bất kỳ hoạt động cạo nào, hãy tham khảo ý kiến ​​cố vấn pháp lý của bạn và xem xét các điều khoản dịch vụ của trang web mục tiêu hoặc có được các quyền cần thiết.

Bài viết phổ biến nhất

Danh mục