Cách trích xuất dữ liệu IndexedDB với Playwright và Scrapeless
Specialist in Anti-Bot Strategies
Tóm tắt ngắn gọn:
- IndexedDB có thể chứa dữ liệu cấu trúc của trình duyệt mà không bao giờ xuất hiện trong HTML ban đầu. Để đọc dữ liệu này cần phải thực thi mã bên trong nguồn của trang sau khi ứng dụng mở cơ sở dữ liệu của nó.
- Việc trích xuất IndexedDB nên bắt đầu bằng việc khám phá cơ sở dữ liệu và sơ đồ. Liệt kê các cơ sở dữ liệu, cửa hàng đối tượng và chỉ mục trước khi đọc các bản ghi thay vì giả định tên của chúng.
- Playwright có thể cầu nối các yêu cầu dựa trên callback của IndexedDB vào quy trình trích xuất có thể chờ. Bọc các yêu cầu như
indexedDB.open()vàgetAll()trong Promises bên trongpage.evaluate(). - DOM đã được kết xuất và IndexedDB có thể tiết lộ các tập hợp khác nhau của cùng một trạng thái ứng dụng. Giữ cả hai số liệu để phát hiện lọc, phân trang hoặc dữ liệu giao diện người dùng cũ.
- Các cửa hàng lớn hoặc nhạy cảm yêu cầu việc trích xuất có giới hạn. Ưu tiên các khoảng khóa, số liệu hoặc con trỏ hơn là gọi
getAll()không hạn chế, và chỉ xem xét các phiên duyệt web được ủy quyền. - Cùng một truy vấn IndexedDB hoạt động cả ở chế độ địa phương và qua Scrapeless. Chuyển sang Scrapeless Scraping Browser thay đổi cách tạo trình duyệt, không phải là logic cơ sở dữ liệu bên trang.
- Miễn phí để bắt đầu. Các 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: Dữ liệu trình duyệt không dừng lại ở DOM
IndexedDB có thể chứa các bản ghi cấu trúc mà không bao giờ xuất hiện trong HTML ban đầu của trang. Hướng dẫn này sử dụng Playwright để liệt kê một cơ sở dữ liệu công khai thực sự, kiểm tra cửa hàng đối tượng và chỉ mục của nó, đọc tất cả các bản ghi và so sánh chúng với bảng đã được kết xuất trước khi chuyển cùng việc trích xuất sang Scrapeless Scraping Browser.
IndexedDB thêm gì cho việc trích xuất web
IndexedDB là một cơ sở dữ liệu không đồng bộ, giới hạn ở nguồn gốc được tích hợp vào trình duyệt. Khác với bảng tên chuỗi của localStorage, nó lưu trữ các giá trị JavaScript có cấu trúc trong các cửa hàng đối tượng và hỗ trợ chỉ mục, khóa và giao dịch. Hướng dẫn IndexedDB của MDN mô tả mô hình đó.
Các ứng dụng sử dụng nó cho các bản ghi ngoại tuyến, dữ liệu API được lưu vào bộ nhớ đệm, hàng đợi và trạng thái có thể quá lớn hoặc cấu trúc cho Lưu trữ Web. Một trình trích xuất trình duyệt có thể đọc trạng thái ứng dụng công khai sau khi trang đã mở cơ sở dữ liệu của nó. Truy cập đó không biến các cơ sở dữ liệu người dùng cá nhân thành các mục tiêu thu thập hợp lý; hướng dẫn này chỉ sử dụng dữ liệu liên lạc công cộng của MDN.
Tại sao sử dụng Playwright với Scrapeless
Playwright đánh giá một hàm bất đồng bộ trong nguồn gốc trang, vì vậy hàm có thể gọi indexedDB.open, chờ các callback yêu cầu và trả về dữ liệu thuần cho Python. Scrapeless Scraping Browser giữ API bên trang đó bên trong một phiên Chromium được lưu trữ.
Playwright kết nối qua connect_over_cdp. Khi trang mục tiêu được tải, truy vấn IndexedDB không thay đổi.
Yêu cầu trước
- Python 3.10 trở lên.
playwrightphiên bản 1.59.0 hoặc phiên bản tương thích hiện tại.- Chrome/Chromium cục bộ cho đường dẫn có thể chạy hoàn chỉnh.
- Một tài khoản Scrapeless đã được tài trợ và
SCRAPELESS_API_KEYcho kết nối đám mây. Tài khoản xác minh trả về mã 14500 cho các phiên duyệt web mới, vì vậy kết nối đó là một khoảng trống yêu cầu được ghi nhãn.
Cài đặt
bash
python -m pip install "playwright==1.59.0"
Kết nối với Scrapeless Scraping Browser
Lưu ý: Kết nối này yêu cầu 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 lại trước. Việc trích xuất IndexedDB hoàn chỉnh bên dưới đã chạy trên 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]
# Điều hướng và đánh giá truy vấn IndexedDB bên dưới.
browser.close()
Sử dụng tài liệu phát triển Scrapeless để biết các tham số kết nối hiện tại.
Bước 1 — Khám phá các cơ sở dữ liệu của Nguồn gốc
Ví dụ IDBIndex công khai của MDN tạo một cơ sở dữ liệu sau khi tải:
python
TARGET_URL = (
"https://mdn.github.io/dom-examples/"
"indexeddb-examples/idbindex/"
)
page.goto(TARGET_URL, wait_until="domcontentloaded")
page.wait_for_selector("tbody tr")
databases = page.evaluate("indexedDB.databases()")
print("databases:", databases)
text
databases: [{'name': 'contactsList', 'version': 1}]
indexedDB.databases() được mô tả bởi tham khảo databases của IDBFactory. Kiểm tra hỗ trợ trình duyệt trước khi dựa vào nó trên các engine cũ hơn.
Bước 2 — Kiểm tra Object Store và Indexes
Mở cơ sở dữ liệu đã phát hiện và trả về metadata schema:
python
schema = page.evaluate("""async () => {
const opened = await new Promise((resolve, reject) => {
const request = indexedDB.open('contactsList');
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
const storeName = opened.objectStoreNames[0];
const transaction = opened.transaction(storeName, 'readonly');
const store = transaction.objectStore(storeName);
const result = {
storeName,
indexes: Array.from(store.indexNames),
};
opened.close();
return result;
}""")
print("object store:", schema["storeName"])
print("indexes:", schema["indexes"])
text
object store: contactsList
indexes: ['age', 'company', 'eMail', 'fName', 'jTitle', 'lName', 'phone']
Mẫu này sử dụng contactsList cho cả cơ sở dữ liệu và object store duy nhất của nó. Việc liệt kê cả hai vẫn quan trọng: nhiều ứng dụng sử dụng các tên khác nhau, và giả định chúng giống nhau có thể nhắm đúng object store.
Bước 3 — Đọc Hồ sơ Từ Object Store
IDBObjectStore.getAll() trả về một yêu cầu, không phải một Promise. Bọc các callback thành công/lỗi của nó để Playwright có thể đợi:
python
records = page.evaluate("""async () => {
const database = await new Promise((resolve, reject) => {
const request = indexedDB.open('contactsList');
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
const transaction = database.transaction('contactsList', 'readonly');
const store = transaction.objectStore('contactsList');
const rows = await new Promise((resolve, reject) => {
const request = store.getAll();
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
database.close();
return rows;
}""")
print("số lượng hồ sơ:", len(records))
print("đầu tiên:", records[0])
print("cuối cùng:", records[-1])
Tham khảo getAll lưu ý rằng nó trả về tất cả hồ sơ khi không có truy vấn hoặc số lượng nào được cung cấp. Giữ các phép đọc được giới hạn trên các ứng dụng thực; một con trỏ hoặc số lượng là an toàn hơn cho các store lớn.
Bước 4 — So sánh IndexedDB Với DOM Được Kết Xuất
python
table_rows = page.locator("tbody tr").count()
arkham_rows = [row for row in records if row["company"] == "Arkham"]
ages = [row["age"] for row in records]
print("số hàng trong bảng:", table_rows)
print("số hàng trong cơ sở dữ liệu:", len(records))
print("số hàng Arkham:", len(arkham_rows))
print("phạm vi tuổi:", min(ages), max(ages))
text
số hàng trong bảng: 10
số hàng trong cơ sở dữ liệu: 10
số hàng Arkham: 2
phạm vi tuổi: 24 55
Sự khớp về số lượng chứng minh rằng mẫu này đã kết xuất đầy đủ cơ sở dữ liệu. Một ứng dụng khác có thể chỉ kết xuất trang hiện tại hoặc một tập con đã lọc, vì vậy hãy giữ lại cả hai số lượng thay vì buộc sự bằng nhau.
Sẵn sàng truy vấn trạng thái trình duyệt trong một phiên đã lưu trữ? Tạo tài khoản Scrapeless miễn phí và chỉ cần thay thế việc tạo trình duyệt.
Bộ Rút Gọn Có Thể Chạy Hoàn Chỉnh
python
from playwright.sync_api import sync_playwright
TARGET_URL = (
"https://mdn.github.io/dom-examples/"
"indexeddb-examples/idbindex/"
)
QUERY = """async () => {
const databases = await indexedDB.databases();
const database = await new Promise((resolve, reject) => {
const request = indexedDB.open('contactsList');
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
const storeName = database.objectStoreNames[0];
const transaction = database.transaction(storeName, 'readonly');
const store = transaction.objectStore(storeName);
const indexes = Array.from(store.indexNames);
const rows = await new Promise((resolve, reject) => {
const request = store.getAll();
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
database.close();
return {databases, storeName, indexes, rows};
}"""
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("tbody tr")
result = page.evaluate(QUERY)
rows = result["rows"]
table_rows = page.locator("tbody tr").count()
vi
arkham_rows = [row for row in rows if row["company"] == "Arkham"]
ages = [row["age"] for row in rows]
assert result["databases"] == [{"name": "contactsList", "version": 1}]
assert result["storeName"] == "contactsList"
assert len(result["indexes"]) == 7
assert len(rows) == table_rows == 10
assert len(arkham_rows) == 2
print("tiêu đề:", page.title())
print("cơ sở dữ liệu:", result["databases"])
print("cửa hàng đối tượng:", result["storeName"])
print("chỉ mục:", result["indexes"])
print("số hàng trong cơ sở dữ liệu:", len(rows))
print("số hàng trong bảng:", table_rows)
print("liên hệ đầu tiên:", rows[0]["fName"], rows[0]["lName"])
print("liên hệ cuối cùng:", rows[-1]["fName"], rows[-1]["lName"])
print("số hàng Arkham:", len(arkham_rows))
print("phạm vi tuổi:", min(ages), max(ages))
browser.close()
Kết quả trực tiếp chứa một cơ sở dữ liệu phiên bản-1, một cửa hàng contactsList, bảy chỉ mục, mười bản ghi cơ sở dữ liệu, mười hàng đã được hiển thị, hai liên hệ Arkham, và độ tuổi từ 24 đến 55.
Những gì bạn nhận được
Extractor hoàn chỉnh trả về danh tính cơ sở dữ liệu, siêu dữ liệu lược đồ, các bản ghi đã lưu, và so sánh DOM từ cùng một phiên trình duyệt:
json
{
"database": {
"name": "contactsList",
"version": 1
},
"object_store": "contactsList",
"index_count": 7,
"database_rows": 10,
"rendered_rows": 10,
"matching_company_rows": 2,
"age_range": {
"minimum": 24,
"maximum": 55
}
}
Số liệu phù hợp của cơ sở dữ liệu và bảng cho thấy rằng bản dựng công khai đã hiển thị mọi bản ghi đã lưu. Các ứng dụng sản xuất có thể chỉ hiển thị một tập hợp lọc hoặc phân trang, vì vậy hãy giữ danh tính cơ sở dữ liệu, tên cửa hàng đối tượng, URL trang, và cả hai số hàng cùng với kết quả đã trích xuất.
Vấn đề thường gặp khi trích xuất IndexedDB
Danh sách cơ sở dữ liệu trống
Chờ ứng dụng mở hoặc tạo cơ sở dữ liệu của nó. Một danh sách trống ngay lập tức sau khi điều hướng có thể cho thấy thời gian chờ, API khám phá không được hỗ trợ, hoặc nguồn gốc sai.
Cửa hàng đối tượng lớn
Không gọi getAll() mà không giới hạn trên một cửa hàng không có giới hạn. Sử dụng khoảng khóa, số lượng, hoặc con trỏ và dừng lại sau khi lấy mẫu tập dữ liệu mà nhiệm vụ của bạn cần.
Giá trị chứa các kiểu không phải JSON
Playwright tuần tự hóa các giá trị được hỗ trợ trở lại Python. Các Blob, thể hiện lớp phức tạp, và cấu trúc vòng có thể cần ánh xạ cấp trường bên trong hàm đánh giá.
Trang và cơ sở dữ liệu không đồng nhất
Trang có thể đã được lọc, phân trang, hoặc cũ. Lưu danh tính cơ sở dữ liệu, tên cửa hàng đối tượng, URL trang, và thời gian trích xuất để có thể điều tra sự không khớp.
Ranh giới trích xuất an toàn
IndexedDB thường chứa dữ liệu ứng dụng ngoại tuyến cá nhân. Chỉ sử dụng kỹ thuật này trên các hệ thống và phiên bạn được ủy quyền kiểm tra. Đừng công khai thông tin xác thực, tin nhắn, hoặc hồ sơ cá nhân. Các liên hệ công khai MDN là dữ liệu công khai tổng hợp.
Kết luận
Việc trích xuất IndexedDB bắt đầu bằng việc khám phá và kiểm tra lược đồ, không phải bằng một tên cửa hàng đoán trước. Mở cơ sở dữ liệu của nguồn gốc, liệt kê các cửa hàng và chỉ mục, cầu nối các phản hồi yêu cầu thành một Promise, và xác thực kết quả chống lại một bề mặt có thể nhìn thấy nơi một bề mặt tồn tại. Di chuyển quy trình làm việc đến Scrapeless thay đổi quy trình tạo trình duyệt trong khi vẫn giữ truy vấn phía trang.
Sẵn sàng trích xuất trạng thái trình duyệt có cấu trúc?
Tham gia cùng các nhà phát triển xây dựng 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á cả Scrapeless, và đọc những gì CDP phơi bày trước khi chuyển cùng quy trình làm việc IndexedDB đến một trình duyệt được lưu trữ.
Câu hỏi thường gặp
H: Playwright có thể đọc IndexedDB không?
Có. Đánh giá một hàm bất đồng bộ trong nguồn gốc trang và bọc các phản hồi yêu cầu IndexedDB trong Promises.
H: Tại sao phải liệt kê các cơ sở dữ liệu trước?
Điều này xác nhận nguồn gốc và danh tính cơ sở dữ liệu trước khi bạn giả định một tên hoặc phiên bản. Không phải tất cả các trình duyệt đều phơi bày indexedDB.databases(), vì vậy hãy giữ một tên dự phòng đã biết khi tính tương thích đòi hỏi điều đó.
H: Cửa hàng đối tượng có giống như một cơ sở dữ liệu không?
Không. Một cơ sở dữ liệu chứa một hoặc nhiều cửa hàng đối tượng. Trong lược đồ MDN, cả cơ sở dữ liệu và cửa hàng đối tượng của nó đều được đặt tên là contactsList.
H: Khi nào nên sử dụng con trỏ thay vì getAll?
Sử dụng một con trỏ hoặc truy vấn có giới hạn khi một cửa hàng có thể lớn, khi bạn cần thứ tự, hoặc khi bạn có thể dừng lại sau một tập hợp nhỏ.
H: Tại sao so sánh các hàng IndexedDB với DOM?
Sự so sánh này cho thấy liệu giao diện người dùng có hiển thị mọi bản ghi, một chế độ xem lọc, hoặc chỉ một trang. Không bên nào nên âm thầm ghi đè lên bên kia.
Hỏi: Liệu IndexedDB có tồn tại qua các phiên duyệt web không?
Nó có thể tồn tại trong một hồ sơ trình duyệt được giữ lại cho đến khi nguồn gốc hoặc trình duyệt xóa nó. Một ngữ cảnh tạm thời có thể biến mất khi ngữ cảnh đó đóng lại.
Hỏi: Có luôn cho phép trích xuất dữ liệu từ IndexedDB không?
Không. Chỉ kiểm tra các phiên được ủy quyền và dữ liệu công cộng cần thiết, giảm thiểu các trường được giữ lại, tuân theo các điều khoản của trang web và chính sách robots khi có thể, và nhận tư vấn pháp lý cho các mục đích nhạy cảm.
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.



