🎯 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

Set-of-Mark Prompting: Cho một tác nhân trình duyệt thứ gì đó mà nó có thể nhấp vào

Daniel Kim
Daniel Kim

Lead Scraping Automation Engineer

07-Aug-2026

TL;DR:

  • Một mô hình thị giác yêu cầu tọa độ pixel trên một trang danh mục trực tiếp đã không đạt được mục tiêu mỗi lần — 0 trên 5 thử nghiệm trong một phiên, 0 trên 15 trong ba phiên — và không phải ngẫu nhiên: tại nhiệt độ 0, nó trả về gần như cùng một điểm trong mỗi thử nghiệm.
  • Đưa ra Set-of-Mark — đánh số mỗi phần tử có thể nhấp và yêu cầu mô hình chỉ số thay vì tọa độ — đã đạt được 5 trên 5 trong cùng một phiên và 15 trên 15 tổng thể, trên cùng một trang với cùng một mô hình.
  • Đánh dấu không miễn phí: yêu cầu phát triển từ 1,343 đến 2,100 token (+56.4%), khoảng $0.00021 cho mỗi quyết định theo tỷ lệ của mô hình đã thử nghiệm.
  • Trên một trình duyệt CDP từ xa, page.set_viewport_size() không thay đổi kích thước viewport bố cục. Trong mười hai phiên tươi mới với ba lần chạy độc lập, innerWidth không thay đổi bởi cuộc gọi và kích thước cửa sổ thực tế dao động từ 800 px đến 3,840 px — không bao giờ đạt kích thước yêu cầu — trong khi ảnh chụp màn hình luôn đúng kích thước yêu cầu.
  • Đó là nguyên nhân gốc rễ: hình ảnh mà mô hình lý luận về và không gian mà cú nhấp của bạn đáp xuống là các khung tọa độ khác nhau, và độ dịch thay đổi theo phiên. Một chỉ số được giải quyết qua DOM không bị ảnh hưởng bởi tất cả.
  • Kế hoạch miễn phí Scrapeless bao gồm các phiên chạy trên trình duyệt đám mây trong hướng dẫn này.

Một đại lý trình duyệt phải chuyển đổi "mở quyển sách thứ hai" thành một cú nhấp tại một vị trí cụ thể nào đó. Sự chuyển đổi — định hướng — là nơi mà hầu hết các phiên đại lý im lặng sai lầm. Mô hình đọc trang một cách chính xác, giải thích kế hoạch một cách chính xác, và sau đó nhấp vào nơi không phải là thứ mà nó vừa mô tả.

Cách sửa chữa thông thường là Set-of-Mark: vẽ một hộp có số trên mỗi phần tử có thể nhấp, đưa mô hình bức tranh kèm danh sách các số, và để nó trả lời 60 thay vì (564, 623). Kỹ thuật này đến từ bài báo Set-of-Mark của Microsoft, và đến nay nó đã có mặt trong hầu hết các ngăn xếp đại lý dưới một hình thức nào đó.

Điều khó tìm là một phép đo. Hướng dẫn này xây dựng cả hai phương pháp dựa trên cùng một trang trực tiếp với cùng một mô hình, chạy mỗi cái năm lần trong một phiên duy nhất, và báo cáo những gì thực sự xảy ra — bao gồm chi tiết trình duyệt từ xa giải thích tại sao phiên bản tọa độ lại thường xuyên thất bại như vậy.

Tại sao Tọa độ Thất bại

Bắt đầu với phiên bản thất bại, vì hình dạng của nó là phần thú vị.

Nhiệm vụ: trên một danh mục sách trực tiếp, mở trang sản phẩm cho quyển sách có tiêu đề Soumission. Mô hình nhận một ảnh chụp màn hình và được yêu cầu để lấy một điểm nhấp.

python Copy
import base64, json, os, urllib.request
from urllib.parse import urlencode
from playwright.sync_api import sync_playwright

TARGET = "https://books.toscrape.com/"
TASK = "open the product page for the book titled 'Soumission'"
MODEL = "google/gemini-2.5-flash-lite"
CDP = "wss://browser.scrapeless.com/api/v2/browser?" + urlencode({
    "token": os.environ["SCRAPELESS_API_KEY"], "sessionTTL": 300, "proxyCountry": "US"})


def ask(png, prompt):
    body = {"model": MODEL, "max_tokens": 200, "temperature": 0,
            "messages": [{"role": "user", "content": [
                {"type": "text", "text": prompt},
                {"type": "image_url", "image_url": {
                    "url": "data:image/png;base64," + base64.b64encode(png).decode()}}]}]}
    req = urllib.request.Request(
        "https://openrouter.ai/api/v1/chat/completions", data=json.dumps(body).encode(),
        headers={"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}",
                 "Content-Type": "application/json"})
    d = json.load(urllib.request.urlopen(req, timeout=120))
    return d["choices"][0]["message"]["content"].strip(), d["usage"]


with sync_playwright() as p:
    browser = p.chromium.connect_over_cdp(CDP)
    page = browser.new_page()
    page.set_viewport_size({"width": 1280, "height": 1400})
    page.goto(TARGET, wait_until="domcontentloaded")

    answer, usage = ask(page.screenshot(),
        f"This is a 1280x1400 browser screenshot. To {TASK}, give the pixel coordinates "
        f'of the element to click. Reply with JSON only: {{"x":int,"y":int}}')
    print("model answered:", answer)

    c = json.loads(answer[answer.index("{"):answer.rindex("}") + 1])
    landed = page.evaluate(
        "([x,y]) => { const e = document.elementFromPoint(x,y);"
        "return e ? (e.closest('a')?.getAttribute('href') || '<'+e.tagName+'>') : null }",
        [c["x"], c["y"]])
    print(f"click ({c['x']},{c['y']}) lands on -> {landed}")
    print("prompt tokens:", usage["prompt_tokens"])
    browser.close()

Mô hình không từ chối, không lấp lửng, hoặc trả về bất kỳ thứ gì không đúng. Nó trả lời ngay lập tức và tự tin:

text Copy
model answered: {"x": 564, "y": 623}
click (564,623) lands on -> <BODY>
prompt tokens: 1343

<BODY> có nghĩa là cú nhấp không chạm vào gì cả — nền trang rỗng, không phải một liên kết nào. Đại lý bây giờ sẽ báo cáo rằng nó đã nhấp, chờ một quá trình dẫn đến mà không bao giờ xảy ra, và tiếp tục với một kế hoạch được xây dựng trên một bước mà âm thầm không diễn ra.

Chạy nó năm lần trong cùng một phiên và sự thất bại không đạt trung bình:

thử nghiệm câu trả lời phần tử trúng kết quả
1 (564, 623) <BODY> MISS
2 (563, 630) <BODY> MISS
3 (563, 630) <BODY> MISS
4 (563, 630) <BODY> MISS
5 (564, 623) <BODY> MISS

0 trên 5. Một sự thất bại không ổn định có thể sống sót — bạn lấy mẫu ba lần, lấy số đông, và tiếp tục. Sự thất bại này là ổn định. Tại nhiệt độ 0, mô hình trả về cùng một điểm trong vài pixel, và mọi lần lặp đều rơi vào cùng một không gian chết. Bỏ phiếu tự nhất quán sẽ trả về câu trả lời sai ba lần và gọi đó là đồng thuận.

Lặp lại toàn bộ so sánh qua ba phiên riêng biệt đã cho cùng một phán quyết mỗi lần: 0 trên 15 cho tọa độ. Điều thay đổi giữa các phiên chỉ là cách nó thất bại — trong một phiên, mỗi cú nhấp đều rơi vào sản phẩm bên cạnh mục tiêu thay vì vào nền rỗng. Mục tiêu mà nó chạm vào thay đổi theo phiên; việc nó không đạt được thì không.

Sự Không Khớp Khung Ở Dưới

Giải thích rõ ràng là các mô hình thị giác đơn giản là kém về tọa độ chính xác, điều này được tài liệu GUI-grounding ủng hộ dài dòng. Trên một trình duyệt từ xa, có một nguyên nhân thứ hai chồng lên, và điều này đáng biết bởi vì nó cũng phá hủy các phương pháp không liên quan đến mô hình chút nào.

Hãy hỏi trang xem nó nghĩ rằng nó lớn thế nào, trước và sau khi đặt viewport:

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

CDP = "wss://browser.scrapeless.com/api/v2/browser?" + urlencode({
    "token": os.environ["SCRAPELESS_API_KEY"], "sessionTTL": 300, "proxyCountry": "US"})

with sync_playwright() as p:
    for run in range(1, 5):
        browser = p.chromium.connect_over_cdp(CDP)
        page = browser.new_page()
        before = page.evaluate("() => [innerWidth, innerHeight]")
        page.set_viewport_size({"width": 1280, "height": 900})
        page.goto("https://books.toscrape.com/", wait_until="domcontentloaded")
        after = page.evaluate("() => [innerWidth, innerHeight]")
        shot = page.screenshot()
        print(f"run{run}: innerWH before={before} after={after} png_bytes={len(shot)}")
        browser.close()

Bốn phiên tươi mới:

text Copy
run1: innerWH before=[1550, 1050] after=[1550, 1050] png_bytes=196373
run2: innerWH before=[1240, 560] after=[1240, 560] png_bytes=116304
run3: innerWH before=[1680, 1002] after=[1680, 1002] png_bytes=171659
run4: innerWH before=[3840, 1152] after=[3840, 1152] png_bytes=5289

set_viewport_size() không có tác động nào đến viewport bố cụcinnerWidthinnerHeight là giống hệt nhau trước và sau cuộc gọi trong cả bốn lần chạy. Kích thước cửa sổ thực tế cũng không phải là thứ bạn chọn. Các lần chạy lặp lại cùng một kịch bản đã trả về 2,560×1,408, 1,920×1,070, 1,440×839, 800×570 và 3,840×1,050 giữa những thứ khác; trong mười hai phiên ở ba lần chạy, viewport bố cục chưa từng đạt kích thước yêu cầu, và độ rộng dao động từ 800 px đến 3,840 px.
Ảnh chụp màn hình, trong khi đó, có kích thước chính xác như đã yêu cầu mỗi lần. Xem điều đó diễn ra trong run4: một trang được bố trí cho cửa sổ 3.840 px, được chụp vào một bề mặt 1.280 px, tạo ra một 5 KB PNG trong khi các lần khác tạo ra 116-196 KB. Phần cắt chủ yếu bắt gặp bố cục trống. Ảnh chụp màn hình đó là điều mà mô hình sẽ được yêu cầu suy luận.

Kịch bản giống nhau chống lại một trình duyệt được khởi động tại chỗ hoạt động như bạn mong đợi, điều này khiến việc này dễ bị bỏ qua trong phát triển:

text Copy
local innerWH: [1280, 1400]
after set_viewport_size: [1280, 900]

Tại chỗ, cuộc gọi hoạt động và các khung đồng ý. Gắn vào một trình duyệt từ xa và cuộc gọi lặng lẽ ngừng hoạt động.

Vì vậy, trang tự định hình cho một cửa sổ 3.840 px, PNG được đưa cho mô hình rộng 1.280 px, và tọa độ mà mô hình đọc từ PNG đó được sử dụng chống lại một DOM sử dụng hình học rộng hơn. Hai khung có liên quan bởi một yếu tố thay đổi mỗi khi bạn mở một phiên - đó chính là lý do tại sao sự thiếu sót diễn ra trên một sản phẩm lân cận trong một phiên và trên nền trống trong một phiên khác. Khi connect_over_cdp gắn vào một trình duyệt đang chạy, Playwright là một khách hàng của trình duyệt đó thay vì là chủ sở hữu của nó, và mô phỏng viewport là một thuộc tính của các ngữ cảnh mà nó tự tạo ra - sự phân biệt mà tài liệu ngữ cảnh của Playwright nêu rõ.

Mô hình không đoán một cách ngẫu nhiên. Nó đang đọc hình ảnh một cách chính xác và trả lời trong khung hình của hình ảnh đó, và sau đó con số đó được sử dụng trong một khung khác.

Đánh dấu các phần tử thay thế

Set-of-Mark loại bỏ vấn đề khung bằng cách không bao giờ vận chuyển một tọa độ qua nó. Mô hình trả về một chỉ số; chỉ số được giải quyết trở lại một phần tử bởi chính trang, trong hình học của chính trang đó.

Quá trình đánh dấu đi qua các phần tử tương tác, lọc đến những phần mà người dùng thực sự có thể nhấp, vẽ một hộp có số trên mỗi phần, và trả về một danh sách song song. Lưu nó như mark.js:

javascript Copy
() => {
  const sel = 'a[href], button, input, select, textarea, [role="button"], [onclick]';
  const out = [];
  document.querySelectorAll('#som-layer').forEach(n => n.remove());
  const layer = document.createElement('div');
  layer.id = 'som-layer';
  layer.style.cssText = 'position:fixed;inset:0;pointer-events:none;z-index:2147483647';
  document.body.appendChild(layer);
  const vw = innerWidth, vh = innerHeight;
  let i = 0;
  for (const el of document.querySelectorAll(sel)) {
    const r = el.getBoundingClientRect();
    if (r.width < 8 || r.height < 8) continue;
    if (r.bottom < 0 || r.top > vh || r.right < 0 || r.left > vw) continue;
    const cs = getComputedStyle(el);
    if (cs.visibility === 'hidden' || cs.display === 'none' || cs.opacity === '0') continue;
    const box = document.createElement('div');
    box.style.cssText = `position:absolute;left:${r.left}px;top:${r.top}px;width:${r.width}px;height:${r.height}px;border:2px solid #E11D48;box-sizing:border-box`;
    const tag = document.createElement('div');
    tag.textContent = i;
    tag.style.cssText = `position:absolute;left:${r.left}px;top:${Math.max(0, r.top - 14)}px;background:#E11D48;color:#fff;font:bold 12px monospace;padding:0 4px;line-height:14px`;
    layer.append(box, tag);
    out.push({ i, tag: el.tagName.toLowerCase(),
               name: (el.innerText || el.getAttribute('aria-label') || el.value || '').trim().slice(0, 60),
               x: Math.round(r.left + r.width / 2), y: Math.round(r.top + r.height / 2) });
    i++;
  }
  return out;
}

Các bộ lọc kích thước và khả năng hiển thị quan trọng hơn những gì chúng có vẻ. Không có bài kiểm tra width < 8 || height < 8 bạn đánh dấu các pixel theo dõi và các bộ bọc bị sụp đổ; không có kiểm tra giới hạn viewport bạn đánh dấu toàn bộ chân trang của một trang dài và đưa mô hình các con số cho những thứ mà nó không thể nhìn thấy. Cả hai tạo ra các menu mà các chỉ số và hình ảnh không đồng bộ, điều này tồi tệ hơn không có dấu hiệu gì cả.

Lớp phủ là một position:fixed lớp với pointer-events:none, vì vậy nó vẽ trên trang mà không chặn cú nhấp chuột mà bạn sắp thực hiện và không làm thay đổi bất cứ điều gì bên dưới.

Trung tâm của mỗi phần tử được chụp từ getBoundingClientRect() vào thời điểm đánh dấu, trong hệ thống tọa độ của trang. Đó là con số mà cú nhấp chuột sẽ sử dụng, và nó không bao giờ đi một vòng qua hình ảnh.

Vòng lặp đầy đủ

Đánh dấu, chụp màn hình, yêu cầu một chỉ số, và nhấp vào nó:

python Copy
import base64, json, os, pathlib, urllib.request
from urllib.parse import urlencode
from playwright.sync_api import sync_playwright

TARGET = "https://books.toscrape.com/"
TASK = "open the product page for the book titled 'Soumission'"
MODEL = "google/gemini-2.5-flash-lite"
MARK_JS = pathlib.Path("mark.js").read_text()
CDP = "wss://browser.scrapeless.com/api/v2/browser?" + urlencode({
    "token": os.environ["SCRAPELESS_API_KEY"], "sessionTTL": 300, "proxyCountry": "US"})


def ask(png, prompt):
    body = {"model": MODEL, "max_tokens": 200, "temperature": 0,
            "messages": [{"role": "user", "content": [
                {"type": "text", "text": prompt},
                {"type": "image_url", "image_url": {
                    "url": "data:image/png;base64," + base64.b64encode(png).decode()}}]}]}
    req = urllib.request.Request(
        "https://openrouter.ai/api/v1/chat/completions", data=json.dumps(body).encode(),
        headers={"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}",
                 "Content-Type": "application/json"})
    d = json.load(urllib.request.urlopen(req, timeout=120))
    return d["choices"][0]["message"]["content"].strip(), d["usage"]


with sync_playwright() as p:
    browser = p.chromium.connect_over_cdp(CDP)
    page = browser.new_page()
    page.goto(TARGET, wait_until="domcontentloaded")

    marks = page.evaluate(MARK_JS)
    shot = page.screenshot()
    page.evaluate("() => document.querySelectorAll('#som-layer').forEach(n => n.remove())")
    print(f"marked {len(marks)} interactive elements")

    menu = "\n".join(f"[{m['i']}] <{m['tag']}> {m['name']}" for m in marks)
    answer, usage = ask(shot,
        f"The screenshot has numbered red marks on every clickable element.\n{menu}\n\n"
        f"To {TASK}, which mark do you click? Reply with JSON only: "
        '{"index":int}. Do not explain.')
    print("model answered:", answer)

    idx = json.loads(answer[answer.index("{"):answer.rindex("}") + 1])["index"]
    chosen = next(m for m in marks if m["i"] == idx)
    print(f"chose [{idx}] {chosen['name']!r}")

    with page.expect_navigation(wait_until="domcontentloaded"):
        page.mouse.click(chosen["x"], chosen["y"])
    print("landed on:", page.url)
    print("prompt tokens:", usage["prompt_tokens"])
    browser.close()

Đầu ra:

text Copy
marked 85 interactive elements
model answered: {"index": 60}
chose [60] 'Soumission'
landed on: https://books.toscrape.com/catalogue/soumission_998/index.html
prompt tokens: 2100

Năm thử nghiệm, cùng một phiên, cùng mô hình, cùng một nhiệt độ:

thử nghiệm chỉ số nhãn hạ cánh vào kết quả
1 60 Soumission soumission_998 HIT
2 60 Soumission soumission_998 HIT
3 60 Soumission soumission_998 HIT
4 60 Soumission soumission_998 HIT
5 60 Soumission soumission_998 HIT

5 trên 5, trong phiên mà các tọa độ ghi được 0 trên 5, và 15 trên 15 trong tất cả ba phiên.

Tính xác định giờ đây cắt theo hướng khác. Cùng một thuộc tính khiến sự thất bại tọa độ không thể khôi phục - một câu trả lời ổn định ở nhiệt độ 0 - khiến phiên bản đã đánh dấu luôn đúng thay vì luôn sai.

Mong đợi các con số tuyệt đối sẽ di chuyển giữa các phiên. Vì viewport bố cục là bất kỳ điều gì mà cửa sổ từ xa đã xảy ra, cùng một kịch bản đã đánh dấu 85 phần tử và chọn chỉ số 60 trong một phiên, và 52 phần tử và chỉ số 43 trong một phiên khác. Cả hai đều hạ cánh vào soumission_998. Các số đếm phụ thuộc vào phiên; đích đến thì không, điều này là những gì giải quyết qua DOM mang lại cho bạn.

Chi phí của điều này

Đánh dấu thêm menu phần tử vào lời nhắc, và menu phát triển cùng với trang:

cách tiếp cận token nhắc chi phí cho mỗi quyết định
ảnh chụp màn hình thô → tọa độ 1.343 $0.00014
set-of-mark → chỉ số 2.100 $0.00021
delta +757 (+56.4%) +49.1%

Lời nhắc ảnh chụp màn hình thô là một bậc cố định 1.343 token vì hình ảnh có kích thước cố định; lời nhắc đã đánh dấu tỷ lệ với số lượng phần tử mà bạn quyết định đánh dấu. Năm sáu phần trăm nhiều hơn nhắc cho một trang với 85 phần tử đã đánh dấu, tại tỷ lệ mà một quyết định tiêu tốn khoảng một phần năm của một phần trăm cent.

Việc tỷ lệ này là lập luận thực sự cho các bộ lọc khả năng hiển thị ở trên: mỗi phần tử mà bạn từ chối đánh dấu là token mà bạn không chi tiêu và một chỉ số mà mô hình không thể chọn nhầm. Chống lại một chuyển động từ 0 đến 100% về việc liệu tác nhân có nhấp vào điều đúng hay không, đây không phải là một quyết định dễ dàng.

Nơi điều này vẫn gặp vấn đề

Set-of-Mark sửa lỗi grounding. Nó không sửa mọi thứ.

Bề mặt Canvas và WebGL không có phần tử nào để đánh dấu. Một bản đồ, một canvas biểu đồ, hoặc một trò chơi hiển thị trên pixel mà không có cấu trúc DOM ở dưới. Việc đánh dấu không tìm thấy gì và bạn quay lại tọa độ, hoặc bất kỳ móc truy cập nào mà thành phần công bố.

Đánh dấu bị giới hạn bởi viewport. Mọi thứ dưới vùng nhìn đều không được đánh dấu theo thiết kế, vì vậy một tác nhân cần một phần tử sâu hơn phải cuộn và đánh dấu lại. Hãy coi việc đánh dấu là một bước cho mỗi quan sát, không phải là thiết lập một lần.

Các trang dày đặc tạo ra menu dài. Một trang với 400 điều khiển tạo ra một menu dài 400 dòng, và hóa đơn token rơi vào mỗi bước của vòng lặp. Lọc theo vai trò, theo khu vực, hoặc theo sự gần gũi với nhiệm vụ trước khi bạn đánh dấu.

Chỉ số chỉ tốt giống như nhãn. Một phần tử có tên truy cập trống xuất hiện dưới dạng [31] <a> và mô hình không có gì để suy luận. Đó là vấn đề đặt tên tương tự mà các trình đọc màn hình gặp phải, và sửa chữa là cùng một cái mà tính toán Tên và Mô tả Có thể Truy cập đã chỉ định: ưu tiên aria-label, quay lại tiêu đề hoặc văn bản gần đó.

Đánh dấu lại sau mỗi lần điều hướng là bắt buộc. Các chỉ số là vị trí và được gán lại trên lần hiển thị tiếp theo. Giữ một chỉ số qua một lần chuyển trang là một lỗi trông như thất bại grounding.

Chạy nó trên trình duyệt đám mây

Mọi thứ ở trên đã chạy trên Trình duyệt Scraping ScrapeLess qua CDP, đó là lý do tại sao việc tìm viewport đã nổi lên — một chromium.launch() địa phương cho bạn viewport mà bạn đã yêu cầu và ẩn vấn đề cho đến khi bạn triển khai.

Kết nối là một URL WebSocket:

python Copy
import os
from urllib.parse import urlencode

CDP = "wss://browser.scrapeless.com/api/v2/browser?" + urlencode({
    "token": os.environ["SCRAPELESS_API_KEY"],
    "sessionTTL": 300,
    "proxyCountry": "US",
})
print(CDP.split("?")[0])

proxyCountry gán khu vực xuất ra, điều này quan trọng cho bất kỳ danh mục nào định địa phương hóa giá cả hoặc tính khả dụng. Phần còn lại của vòng lặp không thay đổi — API của Playwright giống nhau cho dù nó khởi động trình duyệt hay gắn vào một cái, vì cả hai đều nói Giao thức DevTools của Chrome ở dưới. Nếu giao thức ở dưới không quen thuộc, cẩm nang CDP bao quát việc vận chuyển, và vòng lặp tác nhân sử dụng máy tính bao quát chu trình quan sát-quyết định-hành động mà bước grounding này nằm vào.

Hai thói quen sẽ được tiếp tục từ những phép đo ở trên. Đọc innerWidthinnerHeight từ trang thay vì giả định viewport bạn đã yêu cầu — trong một phiên từ xa, đó là những con số khác nhau. Và giải quyết từng lần nhấp qua một phần tử mà trang đã đưa cho bạn, không bao giờ qua một tọa độ được tính từ hình ảnh.

Kết luận

Phép đo là thô bạo. Trên cùng một trang, với cùng một mô hình và cùng một lời nhắc, yêu cầu tọa độ pixel đã nhận được phần tử đúng 0 lần trong 15 lần; yêu cầu một chỉ số đã nhận được phần tử đúng 15 lần trong 15 lần. Sự thất bại tọa độ không phải là tiếng ồn mà việc lấy mẫu lặp lại sẽ làm mượt — ở nhiệt độ 0 nó trả về cùng một điểm và lỡ như mỗi lần.

Dưới sự yếu kém trong grounding của mô hình là một lỗi rõ ràng hơn: trên một trình duyệt từ xa, ảnh chụp màn hình và DOM không nằm trong cùng một hệ tọa độ, và set_viewport_size() sẽ không đặt chúng ở đó. Đánh số các phần tử sẽ lách qua cả hai vấn đề với giá của một vài trăm token.

Sẵn sàng để xây dựng vòng lặp chống lại một trình duyệt được quản lý? Bắt đầu miễn phí với Scrapeless — gói miễn phí bao phủ mỗi lần chạy trong hướng dẫn này, và giá cả có thể mở rộng từ đó.

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

Q: Tại sao đại lý trình duyệt của tôi lại nhấp vào phần tử sai mặc dù nó mô tả đúng phần tử đó?

Bởi vì mô tả và định vị là hai khả năng riêng biệt. Mô hình đọc trang chính xác và sau đó phải phát ra một số chính xác, và số chính xác từ một hình ảnh là đầu ra yếu nhất của nó. Thêm một trình duyệt từ xa, nơi ảnh chụp màn hình và DOM sử dụng các khung tọa độ khác nhau, và lỗi không còn là ngẫu nhiên và trở thành hệ thống — trong các lần chạy ở trên, nó đã bỏ qua trong mỗi một trong 15 lần cố gắng.

Q: Set-of-Mark có tốt hơn việc gửi DOM hoặc cây khả năng truy cập không?
Họ giải quyết các phần khác nhau. Một đại diện văn bản cho mô hình biết cái gì tồn tại; các dấu hiệu cho nó biết những thứ đó nằm ở đâu trên bức tranh mà nó đang nhìn vào, và cung cấp cho nó một mã thông báo mà giải quyết lại thành một yếu tố thực. Các dấu hiệu cũng giữ cho kích thước nhỏ — một chỉ số và một nhãn ngắn cho mỗi yếu tố — trong khi HTML thô trên một trang danh mục khiêm tốn chạy vào năm con số mã thông báo.

Q: Có bao nhiêu mã thông báo được thêm vào khi đánh dấu?

Trên trang đã thử nghiệm, 757 mã thông báo, đưa lời nhắc từ 1.343 lên 2.100 — khoảng 56% hơn cho 85 yếu tố được đánh dấu. Lời nhắc ảnh chụp thô được cố định vì kích thước hình ảnh được cố định; lời nhắc đã đánh dấu thay đổi theo số lượng dấu hiệu, vì vậy bộ lọc độ hiển thị và kích thước thực hiện kiểm soát chi phí cũng như kiểm soát độ chính xác.

Q: Điều này có hoạt động trên trình duyệt từ xa hoặc đám mây không?

Nó hoạt động tốt hơn ở đó, và nó cần thiết hơn ở đó. Bởi vì chỉ số giải quyết thông qua getBoundingClientRect() bên trong trang, nó không bị ảnh hưởng bởi sự không khớp giữa kích thước ảnh chụp và viewport bố cục thực tế của cửa sổ từ xa — sự không khớp đã làm cho phương pháp tọa độ thất bại mỗi lần.

Q: Tôi có cần đánh dấu lại sau mỗi hành động không?

Có. Chỉ số được gán theo thứ tự tài liệu trên các yếu tố hiện đang hiển thị, vì vậy chúng được gán lại sau bất kỳ điều hướng, cuộn hoặc cập nhật DOM nào. Đánh dấu như một phần của mỗi bước quan sát; việc tái sử dụng một chỉ số từ một ảnh chụp trước đó là cách mà kỹ thuật này thường bị thực hiện sai lầm nhất.

Q: Điều gì xảy ra trên các trang không có yếu tố có thể nhấp để đánh dấu?

Canvas, WebGL và các bề mặt video kết xuất mà không có cấu trúc DOM để liệt kê, vì vậy việc đánh dấu không trả về điều gì hữu ích. Những thứ đó cần một chiến lược khác — các móc truy cập mà thành phần cung cấp cho chúng, hoặc một phương pháp tọa độ với sự không khớp của khung được điều chỉnh rõ ràng.

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