🎯 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

Cách thu thập dữ liệu Shadow DOM với Playwright và Scrapeless

Daniel Kim
Daniel Kim

Lead Scraping Automation Engineer

30-Jul-2026

Tóm tắt:

  • Shadow DOM có thể hiển thị văn bản không tồn tại trong các phần tử DOM nhẹ của phần tử chủ. JavaScript có thể tạo nội dung hiển thị bên trong một shadow root sau khi phần tử tùy chỉnh được tải.
  • Các bộ chọn CSS và văn bản trong Playwright có thể duyệt các shadow root mở. Bộ chọn tương tự không hoạt động với document.querySelector vì các truy vấn DOM ở mức tài liệu dừng lại ở giới hạn shadow.
  • Các shadow root đóng và XPath yêu cầu những kỳ vọng khác nhau. Các bộ chọn của Playwright không xuyên qua các root đóng, và các bộ chọn XPath không hỗ trợ việc duyệt Shadow DOM tự động.
  • Logic trích xuất Shadow DOM độc lập với việc vận chuyển trình duyệt. Xác minh quy trình làm việc cục bộ, sau đó tái sử dụng cùng các bộ chọn thông qua kết nối Scrapeless CDP khi yêu cầu trình duyệt được lưu trữ hoặc egress quản lý.
  • Miễn phí để bắt đầu. Tài khoản Scrapeless mới bao gồm môi trường chạy Scraping Browser miễn phí—đăng ký tại app.scrapeless.com.

Một thành phần Shadow DOM có thể hiển thị văn bản không có trong các đối tượng con của DOM thông thường của phần tử chủ. Hướng dẫn này sử dụng Playwright để duyệt một shadow root mở thực tế, cho thấy lý do tại sao một bộ chọn ở mức tài liệu không trả về gì, và tách biệt logic trích xuất đó khỏi kết nối Scrapeless Scraping Browser chạy cùng một mã trong một trình duyệt đám mây.

Shadow DOM Thay Đổi Gì Cho Một Trình Thu Thập Dữ Liệu

Shadow DOM cung cấp cho một phần tử tùy chỉnh một cây con được đóng gói. Tài liệu chính của trang có thể thấy thẻ chủ, nhưng các API DOM thông thường không tự động vượt qua từ thẻ chủ vào shadow root của nó.

Trên bản demo công khai MDN được sử dụng dưới đây, mã nguồn chứa một thẻ chủ <popup-info> với thuộc tính data-text. Một đoạn mã hoãn nâng cấp thẻ đó, gọi attachShadow({ mode: "open" }), và tạo một span nội bộ .info. Trình duyệt hiển thị văn bản trợ giúp từ span đó. HTML ban đầu không chứa span.

Hướng dẫn Shadow DOM của MDN định nghĩa giới hạn và sự khác biệt giữa root mở và root đóng. Sự phân biệt này quan trọng: một root mở tiết lộ element.shadowRoot; một root đóng trả về null thông qua API đó.

Tại Sao Sử Dụng Playwright Với Scrapeless

Các bộ chọn của Playwright xuyên qua các shadow root mở để tìm kiếm CSS và văn bản bình thường. Tài liệu bộ chọn Playwright nêu rõ hai ngoại lệ: XPath không xuyên qua shadow root, và các root chế độ đóng không được hỗ trợ.

Mã duyệt không phụ thuộc vào nơi chạy Chromium. Một trình duyệt cục bộ đủ cho một bản demo công khai; Scrapeless Scraping Browser trở nên hữu ích khi mục tiêu cần một trình duyệt được lưu trữ, nhận dạng quản lý, egress dân cư, hoặc một phiên đám mây liên tục. Playwright kết nối đến trình duyệt đó thông qua API connect_over_cdp.

Điều Kiện Đầu Tiên

  • Python 3.10 hoặc mới hơn.
  • playwright 1.59.0 hoặc một phiên bản tương thích hiện tại.
  • Một môi trường chạy Chrome/Chromium cục bộ cho tùy chọn thực thi đầy đủ bên dưới.
  • Một tài khoản Scrapeless đã được tài trợ và SCRAPELESS_API_KEY cho kết nối đám mây. Tài khoản xác minh trả về mã 14500 cho các phiên trình duyệt mới, vì vậy phần kết nối đám mây được đánh dấu là một khoảng trống điều kiện đầu tiên thay vì được hiển thị là thành công.

Cài Đặt

bash Copy
python -m pip install "playwright==1.59.0"

Máy chủ xác minh đã có Google Chrome tại /usr/bin/google-chrome. Nếu máy của bạn không có, hãy cài đặt Chromium do Playwright quản lý với lệnh thiết lập trình duyệt bình thường của dự án.

Kết Nối Tới Scrapeless Scraping Browser

Chú ý: Kết nối này yêu cầu số dư trình duyệt Scrapeless được tài trợ. Tài khoản được sử dụng cho xác minh cuối cùng đã trả về số dư không đủ, vui lòng nạp tiền trước; hãy chạy nó với khóa tài trợ của riêng bạn. Các khối duyệt sau phần này đã chạy hoàn toàn trong Chrome cục bộ đối với cùng một trang công khai.

python Copy
import os
from urllib.parse import urlencode
from playwright.sync_api import sync_playwright

params = urlencode({
    "token": os.environ["SCRAPELESS_API_KEY"],
    "sessionTTL": 120,
    "proxyCountry": "US",
})
cdp_url = f"wss://browser.scrapeless.com/api/v2/browser?{params}"

with sync_playwright() as p:
    browser = p.chromium.connect_over_cdp(cdp_url)
    page = browser.contexts[0].pages[0]
    # Chạy các bước trích xuất bên dưới với trang này.
    browser.close()

Tài liệu lập trình viên Scrapeless là nguồn hiện tại cho các tham số kết nối. Một khi page tồn tại, việc tìm kiếm Shadow DOM là tiêu chuẩn Playwright.

Kiểm Tra Thẻ Chủ Và Shadow Root Mở Của Nó

Bản demo thành phần MDN nhỏ và công khai:

python Copy
TARGET_URL = (
    "https://mdn.github.io/web-components-examples/"
    "popup-info-box-web-component/"
)

page.goto(TARGET_URL, wait_until="domcontentloaded")
page.wait_for_selector("popup-info .info")

host = page.locator("popup-info")
print("host light-DOM text:", repr(host.inner_text()))
print("shadow mode:", host.evaluate("element => element.shadowRoot.mode"))
text Copy
host light-DOM text: ''
shadow mode: open

Văn bản host trống không phải là bằng chứng cho thấy thành phần không chứa dữ liệu. Nó nói rằng host không có văn bản con thông thường. Nút .info thực tế sống dưới phần shadow root mở ra được tạo ra sau khi phần tử tùy chỉnh được nâng cấp.

Chứng minh Bộ chọn Tài liệu Dừng lại ở Ranh giới

Chạy một truy vấn DOM trình duyệt thông thường từ tài liệu:

python Copy
plain_match = page.evaluate(
    "document.querySelector('popup-info .info') !== null"
)
print("document query found inner node:", plain_match)
text Copy
document query found inner node: False

Bộ chọn là CSS hợp lệ. Sự thất bại đến từ phạm vi: document.querySelector tìm kiếm trong cây tài liệu và không thâm nhập vào cây bóng của host.

Để một Bộ chọn Playwright xuyên thủng Root Mở

Chuỗi bộ chọn thông thường của Playwright xuyên qua root mở:

python Copy
info = page.locator("popup-info .info")
record = {
    "host": "popup-info",
    "shadow_mode": host.evaluate("element => element.shadowRoot.mode"),
    "text": info.inner_text(),
    "source_url": page.url,
}

print("inner matches:", info.count())
print("help text:", record["text"])
print("text chars:", len(record["text"]))
text Copy
inner matches: 1
help text: Mã xác thực thẻ của bạn (CVC) là một tính năng bảo mật bổ sung - nó là 3 hoặc 4 số cuối trên mặt sau của thẻ của bạn.
text chars: 118

Văn bản đó là giá trị data-text thực sự của thành phần được sao chép vào span trong shadow-root. Số lượng ký tự bao gồm dấu câu và khoảng trắng, vì vậy đó là một cách bảo vệ hữu ích để tránh tình trạng vô tình trích xuất một phần tử trống hoặc văn bản thay thế của biểu tượng.

Sẵn sàng di chuyển cùng một bộ chọn đến trình duyệt được lưu trữ? Tạo một tài khoản Scrapeless miễn phí và giữ nguyên mọi dòng sau khi tạo trình duyệt.

Kịch bản Xác minh Địa phương Hoàn chỉnh

Kịch bản này chạy toàn bộ chẩn đoán trên Chrome cục bộ. Thay thế launch(...) bằng kết nối CDP ở trên sẽ chuyển nó đến Scrapeless mà không thay đổi logic trích xuất:

python Copy
from playwright.sync_api import sync_playwright

TARGET_URL = (
    "https://mdn.github.io/web-components-examples/"
    "popup-info-box-web-component/"
)

with sync_playwright() as p:
    browser = p.chromium.launch(
        executable_path="/usr/bin/google-chrome",
        headless=True,
    )
    page = browser.new_page()
    page.goto(TARGET_URL, wait_until="domcontentloaded")
    page.wait_for_selector("popup-info .info")

    host = page.locator("popup-info")
    info = page.locator("popup-info .info")
    host_text = host.inner_text()
    shadow_mode = host.evaluate("element => element.shadowRoot.mode")
    plain_match = page.evaluate(
        "document.querySelector('popup-info .info') !== null"
    )
    help_text = info.inner_text()

    assert shadow_mode == "open"
    assert not plain_match
    assert "mã xác thực thẻ" in help_text

    print("page title:", page.title())
    print("host light-DOM text:", repr(host_text))
    print("shadow mode:", shadow_mode)
    print("document query found inner node:", plain_match)
    print("inner matches:", info.count())
    print("help text:", help_text)
    print("text chars:", len(help_text))
    browser.close()

Chạy thử nghiệm ranh giới thay vì chỉ hiển thị một bộ chọn thành công: văn bản light-DOM trống, bộ chọn tài liệu là sai, root báo cáo mở, và Playwright trả về một chuỗi trợ giúp dài 118 ký tự.

Ý nghĩa của Root Mở và Đóng

Root mở

attachShadow({mode: "open"}) để host.shadowRoot có sẵn. Các bộ chọn của Playwright có thể xuyên qua nó bằng các bộ chọn CSS/văn bản. Các root mở lồng nhau được xử lý theo cách tương tự, mặc dù mỗi thành phần có thể tải vào một lịch trình khác nhau.

Root đóng

attachShadow({mode: "closed"}) làm cho host.shadowRoot trả về null. Các bộ chọn thông thường của Playwright không xuyên qua nó. Đừng trình bày việc tiêm mã hoặc đo lường trình duyệt như một giải pháp phổ quát: một root đóng là một ranh giới bao bọc rõ ràng, và việc thay đổi mã trang có thể làm thay đổi hành vi mà bạn muốn đo lường.

Nội dung được Slot

Một <slot> hiển thị các con cái light-DOM bên trong một bố cục cây bóng. Các nút được chỉ định vẫn thuộc về light DOM của host, vì vậy quyền sở hữu và hành vi bộ chọn của chúng khác với các phần tử được tạo ra bên trong shadow root. Kiểm tra DOM trực tiếp trước khi quyết định bên nào sở hữu dữ liệu.

Các Chế độ Thất bại của Shadow DOM Thường gặp

Host tồn tại nhưng bộ chọn bên trong thời gian chờ

Phần tử tùy chỉnh có thể chưa được nâng cấp, JavaScript của nó có thể đã thất bại hoặc bộ chọn có thể nhắm vào một gốc đóng. Chờ đợi phần tử bên trong như là dấu hiệu sẵn sàng thực sự và kiểm tra lỗi trong console trước khi thay đổi bộ chọn.

XPath không tìm thấy gì

Sử dụng bộ chọn CSS hoặc văn bản. Việc duyệt bóng được tài liệu hóa của Playwright không áp dụng cho các bộ chọn XPath.

Một thành phần lồng nhau trả về văn bản một phần

Xác định phần tử ổn định sâu nhất thay vì gọi inner_text() trên một host lớn. Điều này giúp tránh trộn văn bản slot, nhãn ẩn hoặc đầu ra của các thành phần lồng nhau.

HTML nguồn không có nút bên trong

Điều này là bình thường khi JavaScript xây dựng cây bóng. Sử dụng một trình duyệt. Một phân tích cú pháp HTTP chỉ thấy phản hồi nguồn và không thể quan sát một cây con được tạo ra sau khi tải.

Kết luận

Scraping Shadow DOM là một vấn đề về phạm vi trước khi trở thành vấn đề về bộ chọn. Chứng minh cây nào sở hữu nút, chờ đợi cho phần tử tùy chỉnh nâng cấp và sử dụng các bộ chọn CSS/văn bản của Playwright cho các gốc mở. Giữ ranh giới gốc đóng trung thực. Giao thức trình duyệt là tách biệt: chạy cục bộ cho một bề mặt thử nghiệm công khai, sau đó chuyển đổi việc tạo trình duyệt sang điểm cuối Scrapeless CDP khi mục tiêu cần một trình duyệt chống phát hiện được lưu trữ hoặc quản lý xuất khẩu.

Bắt đầu với Scrapeless, xem lại giá cả của Scrapeless, và đọc những gì Giao thức DevTools của Chrome kiểm soát cho kiến trúc CDP xung quanh.

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

H: Liệu Playwright có thể lấy nội dung từ một gốc Shadow DOM mở không?

Có. Các bộ chọn CSS và văn bản của Playwright xuyên qua các gốc bóng mở, vì vậy một bộ chọn như page.locator("popup-info .info") có thể tiếp cận nút bên trong.

H: Tại sao document.querySelector lại thất bại với cùng một bộ chọn?

document.querySelector tìm kiếm trong cây tài liệu và dừng lại ở host bóng. Bạn phải truy vấn qua gốc mở một mình hoặc sử dụng bộ chọn Playwright thực hiện việc duyệt đó.

H: Liệu Playwright có thể xuyên qua một gốc bóng đóng không?

Không, không phải qua hành vi bộ chọn thông thường của nó. Một gốc đóng trả về null từ host.shadowRoot và là một ranh giới không được hỗ trợ được tài liệu hóa.

H: XPath có hoạt động bên trong Shadow DOM không?

Không. Hướng dẫn bộ chọn chính thức của Playwright cho biết các bộ chọn XPath không xuyên qua các gốc bóng; hãy sử dụng bộ chọn CSS hoặc văn bản.

H: Làm thế nào để tôi xử lý các gốc bóng mở lồng nhau?

Xâu các bộ chọn về phía phần tử bên trong ổn định và chờ phần tử đó. Playwright có thể vượt qua nhiều gốc mở, nhưng mỗi phần tử tùy chỉnh có thể nâng cấp không đồng bộ.

H: Tôi có cần một trình duyệt cho việc scraping Shadow DOM không?

Có khi JavaScript tạo ra cây bóng. Một phản hồi HTTP thông thường chứa đánh dấu host và các tập lệnh, không phải cây con sống được sinh ra sau khi thực thi.

H: Scraping Shadow DOM có hợp pháp không?

Cơ chế DOM không quyết định quyền hạn. Xem lại các điều khoản của mục tiêu và chỉ thị robots, sử dụng dữ liệu công khai, giới hạn khối lượng yêu cầu và tìm kiếm tư vấn pháp lý cho việc thu thập nhạy cảm hoặc thương mại.

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