Ghi lại các thay đổi DOM động bằng MutationObserver và Playwright
Web Data Collection Specialist
TL;DR:
- Các trang động có thể thay đổi DOM sau khi HTML ban đầu được tải. Một công cụ lấy dữ liệu phải chờ trạng thái có thể sử dụng thay vì coi phản hồi đầu tiên là tài liệu cuối cùng.
- MutationObserver ghi lại cách thay đổi của DOM. Cài đặt trình quan sát trước khi thực hiện hành động kích hoạt khi bạn cần các nút được thêm vào, các nút bị xóa, cập nhật văn bản hoặc chuyển đổi thuộc tính.
- Trình tìm kiếm của Playwright chờ xác nhận khi trạng thái yêu cầu có thể sử dụng. Sử dụng trình quan sát để theo dõi nguồn gốc thay đổi và các trình tìm kiếm để sẵn sàng cho trạng thái cuối cùng.
- Một trình quan sát giới hạn chỉ nên theo dõi dưới cây và loại biến đổi liên quan. Tuần tự hóa các bản ghi nhỏ, lọc các thuộc tính ồn ào, và ngắt kết nối sau khi trạng thái mong đợi xuất hiện.
- Chặn mạng thường tốt hơn khi dữ liệu phản hồi có cấu trúc là mục tiêu thật sự. MutationObserver hữu ích nhất khi chính việc chuyển tiếp DOM có ý nghĩa.
- Quy trình quan sát tương tự chạy cục bộ và qua Scrapeless. Chuyển sang Scrapeless Scraping Browser thay đổi việc tạo trình duyệt, không phải trình quan sát bên trang.
- Miễn phí để bắt đầu. Tài khoản Scrapeless mới bao gồm thời gian chạy Scraping Browser miễn phí—đăng ký tại app.scrapeless.com.
Giới thiệu: HTML ban đầu chỉ là trạng thái khởi đầu
Một trang động có thể chèn, hiển thị, thay thế hoặc xóa dữ liệu sau khi HTML ban đầu của nó đã được tải. Hướng dẫn này cài đặt một MutationObserver giới hạn trước khi kích hoạt một công cụ Selenium công khai, ghi lại các thay đổi DOM chính xác và so sánh quan sát dựa trên sự kiện với việc chờ đợi trình tìm kiếm của Playwright trước khi di chuyển cùng quy trình làm việc đến Scrapeless Scraping Browser.
Những gì MutationObserver Thêm vào Việc Lấy Dữ liệu
MutationObserver báo cáo các thay đổi trong một cây con của DOM. Nó có thể theo dõi việc chèn/xóa trẻ, thuộc tính và thay đổi văn bản mà không cần quét lại trang nhiều lần. Tham chiếu MutationObserver của MDN định nghĩa các tùy chọn callback và quan sát.
Một trình quan sát không tự quyết định khi dữ liệu sẵn sàng. Nó ghi lại những gì đã thay đổi. Các trình tìm kiếm của Playwright vẫn là cách rõ ràng hơn để chờ đợi một yếu tố cụ thể có thể sử dụng, trong khi các bản ghi của trình quan sát giải thích cách và khi nào trạng thái đó xuất hiện.
Tại sao Sử dụng Playwright Với Scrapeless
Playwright có thể cài đặt một trình quan sát trong trang trước khi nhấp vào hành động, thực hiện hành động, chờ đợi trạng thái UI cuối cùng và trả về một nhật ký sự kiện ngắn gọn. Scrapeless Scraping Browser cung cấp một phiên Chromium được lưu trữ cho cùng một tập lệnh bên trang.
Trình duyệt đám mây sử dụng connect_over_cdp. Chỉ việc tạo trình duyệt là thay đổi.
Các yêu cầu trước
- Python 3.10 hoặc mới hơn.
playwright1.59.0 hoặc phiên bản tương thích hiện tại.- Chrome/Chromium cục bộ cho ví dụ trực tiếp hoàn chỉnh.
- Một tài khoản Scrapeless có tài chính và
SCRAPELESS_API_KEYcho kết nối đám mây. Tài khoản xác minh trả về mã 14500, vì vậy kết nối đó là một lỗ hổng bắt buộc được đánh dấu.
Cài đặt
bash
python -m pip install "playwright==1.59.0"
Kết nối với Scrapeless Scraping Browser
Lưu ý: Khối này cần số dư Scraping Browser đã được tài trợ. Tài khoản xác minh cuối cùng trả về
số dư không đủ, vui lòng nạp thêm; quy trình quan sát hoàn chỉnh dưới đây đã chạy trong Chrome cục bộ.
python
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]
# Cài đặt và sử dụng trình quan sát bên dưới.
browser.close()
Sử dụng tài liệu phát triển của Scrapeless cho các tham số kết nối hiện tại.
Bước 1 — Kiểm tra Trạng thái Ban đầu của Trang
Công cụ công khai của Selenium tiết lộ hai nút bấm. Một cái chèn #box0 sau một giây; cái kia hiển thị một đầu vào #revealed bị ẩn sau một giây:
python
TARGET_URL = "https://www.selenium.dev/selenium/web/dynamic.html"
page.goto(TARGET_URL, wait_until="domcontentloaded")
print("hộp trước:", page.locator("#box0").count())
print("đầu vào khả dụng trước:", page.locator("#revealed").is_visible())
text
hộp trước: 0
đầu vào khả dụng trước: Sai
HTML ban đầu của công cụ chứa đầu vào ẩn nhưng không có hộp. Sự khác biệt đó là lý do tại sao trình quan sát phải theo dõi cả biến đổi danh sách trẻ và thuộc tính.
Bước 2 — Cài đặt Trình Quan sát Trước Hành Động
Lưu trữ từ điển đơn giản thay vì các nút DOM. MutationRecord hiển thị loại biến đổi, mục tiêu, thuộc tính đã thay đổi và các nút đã thêm/bị xóa.
python
page.evaluate("""
window.capturedMutations = [];
window.scrapeObserver = new MutationObserver(records => {
for (const record of records) {
for (const node of record.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE) {
capturedMutations.push({
type: 'added',
id: node.id,
display: getComputedStyle(node).display,
});
}
}
if (record.type === 'attributes') {
capturedMutations.push({
type: 'attribute',
id: record.target.id,
name: record.attributeName,
display: getComputedStyle(record.target).display,
});
}
}
});
scrapeObserver.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['style'],
});
""")
Bộ lọc attributeFilter giữ các thuộc tính không liên quan như class, ARIA, hoặc thuộc tính framework ra khỏi việc capturing bị giới hạn này.
Bước 3 — Kích hoạt các Thay đổi và Chờ Trạng Thái Có Thể Sử Dụng
python
page.click("#adder")
page.click("#reveal")
page.wait_for_selector("#box0")
page.locator("#revealed").wait_for(state="visible")
Hướng dẫn tự động chờ đợi của Playwright giải thích lý do tại sao các locator là kiểm tra sẵn sàng đúng đắn. Nhật ký của trình quan sát là bằng chứng bổ sung, không phải là thay thế cho một tuyên bố trạng thái cuối cùng cụ thể.
Bước 4 — Ngắt Kết Nối Trình Quan Sát và Đọc Các Ghi Chú
python
changes = page.evaluate("""() => {
scrapeObserver.disconnect();
return capturedMutations;
}""")
print("changes:", changes)
print("change count:", len(changes))
print("box matches:", page.locator("#box0").count())
print("input visible:", page.locator("#revealed").is_visible())
text
changes: [{'type': 'added', 'id': 'box0', 'display': 'block'}, {'type': 'attribute', 'id': 'revealed', 'name': 'style', 'display': 'inline-block'}]
change count: 2
box matches: 1
input visible: True
Việc ngắt kết nối ngăn chặn các thay đổi không liên quan trên trang sau này mở rộng danh sách sự kiện. Một trình quan sát lâu dài nên luôn có một điều kiện dừng rõ ràng.
Sẵn sàng quan sát phiên trình duyệt được lưu trữ? Tạo một tài khoản Scrapeless miễn phí và chỉ cần thay thế việc tạo trình duyệt.
Ghi Lại Hoàn Chỉnh Có Thể Thực Thi
python
from playwright.sync_api import sync_playwright
TARGET_URL = "https://www.selenium.dev/selenium/web/dynamic.html"
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")
box_before = page.locator("#box0").count()
input_before = page.locator("#revealed").is_visible()
page.evaluate("""
window.capturedMutations = [];
window.scrapeObserver = new MutationObserver(records => {
for (const record of records) {
for (const node of record.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE) {
capturedMutations.push({
type: 'added',
id: node.id,
display: getComputedStyle(node).display,
});
}
}
if (record.type === 'attributes') {
capturedMutations.push({
type: 'attribute',
id: record.target.id,
name: record.attributeName,
display: getComputedStyle(record.target).display,
});
}
}
});
scrapeObserver.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['style'],
});
""")
page.click("#adder")
page.click("#reveal")
page.wait_for_selector("#box0")
page.locator("#revealed").wait_for(state="visible")
changes = page.evaluate("""() => {
scrapeObserver.disconnect();
return capturedMutations;
}""")
box_after = page.locator("#box0").count()
input_after = page.locator("#revealed").is_visible()
assert box_before == 0 and input_before is False
assert changes == [
{"type": "added", "id": "box0", "display": "block"},
{
"type": "thuộc tính",
"id": "đã tiết lộ",
"name": "kiểu",
"display": "khối-inline",
},
]
assert box_after == 1 and input_after is True
print("hộp trước:", box_before)
print("đầu vào hiển thị trước:", input_before)
print("thay đổi:", changes)
print("số lượng thay đổi:", len(changes))
print("hộp sau:", box_after)
print("đầu vào hiển thị sau:", input_after)
browser.close()
Lần chạy cuối cùng ghi nhận chính xác một nút được thêm vào và một thay đổi thuộc tính kiểu, sau đó xác nhận độc lập một hộp và một đầu vào hiển thị.
Những gì bạn nhận được
Luồng công việc hoàn thành trả về một nhật ký đột biến gọn gàng cùng với các kiểm tra trạng thái cuối độc lập:
json
{
"trước": {
"hộp_đúng": 0,
"đầu_vào_hiển_thị": false
},
"đột biến": [
{
"type": "thêm",
"id": "hộp0",
"hiển_thị": "khối"
},
{
"type": "thuộc tính",
"id": "đã tiết lộ",
"name": "kiểu",
"hiển_thị": "khối-inline"
}
],
"sau": {
"hộp_đúng": 1,
"đầu_vào_hiển_thị": true
}
}
Các bản ghi đột biến giải thích cách trang đã thay đổi, trong khi các kiểm tra định vị xác nhận rằng các phần tử cuối cùng có thể sử dụng được. Việc giữ cả hai ngăn chặn một sự kiện được quan sát bị nhầm lẫn với một chuyển tiếp UI hoàn tất.
Lựa chọn Tín hiệu Trang Động Đúng
| Tín hiệu | Sử dụng khi nào | Những gì nó trả về | Hạn chế chính |
|---|---|---|---|
| MutationObserver | Trình tự hoặc loại thay đổi DOM quan trọng | Đột biến thêm, xóa, văn bản hoặc thuộc tính | Cần có phạm vi giới hạn và điều kiện dừng rõ ràng |
| Định vị viên Playwright chờ | Chỉ trạng thái có thể sử dụng cuối cùng quan trọng | Một phần tử đã sẵn sàng hoặc trạng thái được xác nhận | Không giải thích cách trang đạt được trạng thái đó |
| Bắt mạng | Dữ liệu có cấu trúc đến qua các yêu cầu | Tải trọng yêu cầu và phản hồi | Không mô tả các chuyển đổi DOM sau này |
| Kiểm tra định kỳ có giới hạn | Không có sự kiện, yêu cầu hoặc định vị viên ổn định nào tồn tại | Ảnh chụp trạng thái định kỳ | Các kiểm tra lặp lại làm tăng tiếng ồn và công việc của trang |
Chờ định vị viên nên là nguyên tắc sẵn sàng mặc định. Thêm MutationObserver khi việc trích xuất cũng cần nguồn gốc thay đổi, và ưu tiên bắt mạng khi phản hồi có cấu trúc ổn định hơn DOM đã được hiển thị.
Các Chế Độ Thất Bại Thường Gặp của MutationObserver
Sự Kiện Đầu Tiên Thiếu
Người quan sát đã được cài đặt sau hành động. Đăng ký nó trước khi nhấp chuột hoặc điều hướng đến quá trình chuyển tiếp trạng thái.
Bắt Lớn Quá
Quan sát một gốc nhỏ hơn, lọc thuộc tính và chỉ lưu trữ các trường bạn cần. Không bao giờ giữ lại các nút DOM trong một lần bắt dài hạn.
Các Thay Đổi Thuộc Tính Lặp Lại
Các framework có thể ghi lại cùng một thuộc tính nhiều lần. Quyết định xem bạn cần mọi chuyển tiếp hay chỉ giá trị cuối cùng, sau đó loại bỏ trùng lặp bên ngoài callback.
Người Quan Sát Không Bao Giờ Dừng
Ngắt kết nối nó khi các bản ghi mong đợi đến hoặc khi thời hạn của nhiệm vụ hết hạn. Quan sát mở không giới hạn tạo ra sự tăng trưởng bộ nhớ không giới hạn.
Kết Luận
MutationObserver cung cấp cho bạn nguồn gốc cho các thay đổi DOM phía khách hàng, trong khi các định vị viên Playwright xác nhận trạng thái cuối có thể sử dụng. Cài đặt người quan sát trước hành động, tuần tự hóa các bản ghi nhỏ, chỉ theo dõi các loại đột biến có liên quan và ngắt kết nối kịp thời. Chuyển luồng công việc sang Scrapeless thay đổi việc tạo trình duyệt mà không thay đổi việc bắt sự kiện.
Sẵn Sàng Để Bắt Trạng Thái Trang Động?
Tham gia cùng các nhà phát triển xây dựng các quy trình trích xuất dựa trên trình duyệt trong cộng đồng Scrapeless: Discord · Telegram.
Bắt đầu với Scrapeless, xem xét giá Scrapeless, và so sánh luồng công việc này với can thiệp yêu cầu mạng khi trang phơi bày các phản hồi có cấu trúc.
Câu Hỏi Thường Gặp
Q: MutationObserver có tốt hơn Playwright waits không?
Chúng trả lời các câu hỏi khác nhau. Người quan sát ghi lại những gì đã thay đổi; một sự chờ định vị viên xác nhận rằng phần tử hoặc trạng thái bạn cần đã sẵn sàng.
Q: Khi nào tôi nên cài đặt người quan sát?
Trước hành động có thể kích hoạt sự thay đổi. Cài đặt nó sau có thể bỏ lỡ sự kiện duy nhất mà bạn cần.
Q: MutationObserver có thể thấy các phản hồi mạng không?
Không. Nó thấy các đột biến DOM. Sử dụng các sự kiện yêu cầu/phản hồi khi bạn cần tải trọng mạng.
Q: Tại sao nên lọc các thuộc tính được quan sát?
Các framework thay đổi nhiều thuộc tính. Một attributeFilter giữ cho việc bắt giữ bị giới hạn và liên quan.
Q: Tôi có nên lưu trữ các đối tượng MutationRecord một cách trực tiếp không?
Không. Chuyển chúng thành các đối tượng đơn giản nhỏ vì các bản ghi tham chiếu đến các node DOM và không phải là đầu ra tuần tự thuận tiện.
H: Làm thế nào để tôi dừng một observer?
Gọi disconnect() sau khi các bản ghi mong đợi đã đến hoặc tại một thời hạn tác vụ đã định.
H: Có được phép quan sát một trang công khai không?
Không. Xem xét các điều khoản của trang web, chỉ thu thập dữ liệu công khai cần thiết, giới hạn phiên và khối lượng yêu cầu, và tuân thủ chính sách robot khi cần thiết.
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.



