Quay lại blog

Xác thực đầu vào biểu mẫu với Trình duyệt Trích xuất không có rác

Daniel Kim
Daniel Kim

Lead Scraping Automation Engineer

11-Aug-2026

TL;DR:

  • Gán element.value trực tiếp sẽ không kích hoạt bất kỳ sự kiện nào. Trong một lần chạy đã đo lường, việc gán trực tiếp đã tạo ra một danh sách sự kiện rỗng trong khi locator.fill() tạo ra focus, beforeinput, và input — giá trị nằm trong DOM theo cách này hay cách khác, vì vậy sự thất bại là im lặng.
  • Gán trực tiếp vẫn thỏa mãn xác thực ràng buộc HTML5 và vẫn điền FormData. Việc thiết lập value không bị từ chối bởi trình duyệt; nó đơn giản là không được quan sát, và những gì bị hỏng là bất kỳ logic trang nào phản ứng với các sự kiện.
  • Các trường thực sự bị hỏng là những trường mà các trường khác phụ thuộc vào. Việc thiết lập giá trị của <select> trực tiếp đã khiến đầu vào phụ thuộc bị ẩn đi và nút gửi bị vô hiệu hóa, trong khi selectOption() đã hiển thị cả hai.
  • Bộ chọn ngày JavaScript có thể loại bỏ những gì bạn đã nhập. Trên một trường ngày công khai, việc điền rồi nhấn Escape hoặc nhấp ra ngoài đã trả trường về trạng thái chuỗi rỗng; cam kết với Enter đã sản xuất định dạng chuẩn hóa của widget.
  • setInputFiles() hoạt động ngay cả khi trình duyệt không ở trên máy của bạn. Playwright truyền tệp cục bộ đến phiên làm việc từ xa, và việc tải lên hiển thị trong dữ liệu biểu mẫu đã gửi.
  • Lỗi xác thực có thể đọc được mà không cần ảnh chụp màn hình. validity, validationMessage, và một requestSubmit() bị chặn cho thấy chính xác ràng buộc nào đã thất bại và tại sao.
  • Miễn phí để bắt đầu. Các tài khoản Scrapeless mới bao gồm thời gian chạy Trình duyệt Scraping miễn phí — đăng ký tại app.scrapeless.com.

Tại sao một trường đã điền có thể vẫn là một biểu mẫu trống

Một trường biểu mẫu có thể chứa văn bản đúng và vẫn không gửi gì hữu ích. Giá trị nằm trong DOM, một bức ảnh chụp màn hình trông đúng, và trang hành xử như thể trường chưa bao giờ được chạm vào — trường tiếp theo không bao giờ xuất hiện, nút gửi vẫn bị vô hiệu hóa, hoặc một thông điệp xác thực mà trang tự hiển thị không bao giờ xóa.

Nguyên nhân không phải là giá trị. Đó là các sự kiện chưa bao giờ được phát đi cùng với nó. Các biểu mẫu hiện đại hiếm khi đọc các giá trị DOM thô vào thời điểm gửi; chúng đăng ký inputchange, giữ bản sao riêng của trạng thái, và điều khiển mọi thứ khác từ bản sao đó. Ghi vào .value và bạn cập nhật DOM trong khi để mọi đăng ký không biết.

Hướng dẫn này đo lường hành vi đó thay vì khẳng định nó, sau đó làm việc thông qua các lớp widget mà khoảng cách thực sự xuất hiện: chọn lọc phụ thuộc, bộ chọn ngày JavaScript, đầu vào tệp, và các biểu mẫu mà ràng buộc của chúng từ chối gửi ngay lập tức. Tất cả những điều dưới đây chạy trên Playwright kết nối với Trình duyệt Scraping Scrapeless, một 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. Nếu bạn cần những điều cơ bản về điền và gửi trước, hướng dẫn gửi biểu mẫu Puppeteer đã đề cập đến điều đó.


Các yêu cầu trước

  • Node.js 18 hoặc mới hơn
  • Một tài khoản Scrapeless và khóa API — đăng ký tại app.scrapeless.com
  • Kiến thức làm việc về bộ chọn CSS và mô hình sự kiện DOM

Cài đặt

Trình duyệt là từ xa, vì vậy playwright-core là tất cả những gì bạn cần — không cần tải xuống Chromium gói:

bash Copy
pnpm add playwright-core@1.56.1

Đặt khóa API của bạn từ môi trường thay vì đưa nó vào mã nguồn:

bash Copy
export SCRAPELESS_API_KEY="paste-your-key-here"

Cấu hình: kết nối Playwright với trình duyệt đám mây

Trình duyệt Scraping giao tiếp giao thức Chrome DevTools, do đó connectOverCDP là điểm nhập. Biểu mẫu kiểm tra công khai bên dưới thuộc về dự án Selenium và tồn tại cụ thể cho việc thực hành tự động hóa:

js Copy
import { chromium } from 'playwright-core';

const endpoint = 'wss://browser.scrapeless.com/api/v2/browser?' + new URLSearchParams({
  token: process.env.SCRAPELESS_API_KEY,
  sessionTTL: '300',
});

const browser = await chromium.connectOverCDP(endpoint);
const context = browser.contexts()[0];
const page = context.pages()[0] || await context.newPage();

await page.goto('https://www.selenium.dev/selenium/web/web-form.html', {
  waitUntil: 'domcontentloaded',
});

console.log('chromium:', browser.version());
console.log('title:', await page.title());
console.log('form method:', await page.getAttribute('form', 'method'));
await browser.close();
text Copy
chromium: 140.0.7339.35
title: Web form
form method: get

Một lưu ý cụ thể đối với các phiên từ xa: trang mặc định thực thi các loại tin cậy, vì vậy page.setContent() bị từ chối trước khi nó có thể viết đánh dấu. Điều hướng đến một URL data: là phương án làm việc, và phép đo dưới đây sử dụng chính xác điều đó.


Triển khai cơ bản: đo lường những gì mỗi kỹ thuật phát ra

Thay vì tin tưởng một quy tắc ngón tay cái, hãy công cụ hóa đầu vào. Thiết bị này ghi lại mọi sự kiện mà đến trường, và cũng hiển thị thông điệp xác thực riêng từ một bộ nghe input — mẫu mà khiến các sự thất bại im lặng trở nên rõ ràng:

js Copy
import { chromium } from 'playwright-core';

const endpoint = 'wss://browser.scrapeless.com/api/v2/browser?' + new URLSearchParams({
  token: process.env.SCRAPELESS_API_KEY,
  sessionTTL: '300',
});
const browser = await chromium.connectOverCDP(endpoint);
const context = browser.contexts()[0];
const page = context.pages()[0] || await context.newPage();

const fixture = `<!doctype html><meta charset=utf-8><title>fixture</title>
<form id=f>
  <input id=t name=t type=text required minlength=4>
  <output id=err></output>
</form>
<script>
window.__ev = [];
var el = document.getElementById('t');
['focus','beforeinput','input','change','keydown','keyup'].forEach(function(type){
  el.addEventListener(type, function(){ window.__ev.push(type); });
});
el.addEventListener('input', function(){
  document.getElementById('err').textContent = el.validity.valid ? 'ok' : 'too short';
});
</script>`;
const fixtureUrl = 'data:text/html;charset=utf-8,' + encodeURIComponent(fixture);

async function trial(name, action) {
  await page.goto(fixtureUrl, { waitUntil: 'load' });
  await action();
  await page.waitForTimeout(200);
  const state = await page.evaluate(() => ({
    events: window.__ev.slice(),
    formData: new FormData(document.getElementById('f')).get('t'),
    valid: document.getElementById('t').validity.valid,
    errorUi: document.getElementById('err').textContent,
  }));
  console.log(name);
  console.log('  events  :', JSON.stringify(state.events));
  console.log('  formData:', JSON.stringify(state.formData), '| valid:', state.valid);
  console.log('  error-UI:', JSON.stringify(state.errorUi));
}

await trial('A: el.value = "hello"', () =>
  page.$eval('#t', el => { el.value = 'hello'; }));

await trial('B: el.value + dispatchEvent(input)', () =>
  page.$eval('#t', el => {
    el.value = 'hello';
    el.dispatchEvent(new Event('input', { bubbles: true }));
  }));

await trial('C: locator.fill("hello")', () => page.fill('#t', 'hello'));

await trial('D: locator.pressSequentially("hello")', () =>
  page.locator('#t').pressSequentially('hello'));

await browser.close();
text Copy
A: el.value = "hello"
  events  : []
  formData: "hello" | valid: true
  error-UI: ""
B: el.value + dispatchEvent(input)
  events  : ["input"]
  formData: "hello" | valid: true
  error-UI: "ok"
C: locator.fill("hello")
  events  : ["focus","beforeinput","input"]
  formData: "hello" | valid: true
  error-UI: "ok"
D: locator.pressSequentially("hello")
  events  : ["focus","keydown","beforeinput","input","keyup","keydown","beforeinput","input","keyup","keydown","beforeinput","input","keyup","keydown","beforeinput","input","keyup","keydown","beforeinput","input","keyup"]
  formData: "hello" | valid: true
  error-UI: "ok"

Đọc trường hợp đầu tiên một cách cẩn thận. Việc gán trực tiếp đã tạo ra một danh sách sự kiện rỗng, và tuy FormData mang "hello" và trường báo cáo rằng nó hợp lệ. Giá trị thực sự ở đó. Điều chưa xảy ra là phản ứng riêng của trang: đầu ra lỗi vẫn rỗng vì không có gì thông báo cho nó cập nhật.

Kết quả đó làm thay đổi lời khuyên thông thường. Việc gán .value không phải là cố định - nó là vô hình. Một biểu mẫu HTML đơn giản mà không có kịch bản gửi đúng cách. Một biểu mẫu mà hành vi của nó được kết nối với các sự kiện coi trường là chưa được chạm vào, và triệu chứng xuất hiện hoàn toàn ở một nơi khác.
fill() tạo ra chuỗi ngắn gọn mà hầu hết các trang cần: tập trung, sau đó beforeinput, sau đó input. pressSequentially() tạo ra chuỗi bàn phím đầy đủ theo từng ký tự, điều này chỉ quan trọng khi trang kiểm tra từng lần gõ phím, chẳng hạn như một chức năng tự động hoàn thành truy vấn cho mỗi chữ cái. Tiêu chuẩn sự kiện UI W3C xác định thứ tự mà các kỹ thuật này mô phỏng, và Tiêu chuẩn DOM WHATWG xác định cách một sự kiện được phát đi thủ công lan truyền — bao gồm isTrusted cờ tách biệt nó khỏi hành động thực sự của người dùng.

Nhận khóa API của bạn trên gói miễn phí: app.scrapeless.com


Các mẫu nâng cao: các tiện ích cần hơn một giá trị

Các lựa chọn mở khóa các trường khác

Đây là nơi mà việc gán trực tiếp dừng lại không chỉ là vô hình và bắt đầu gây tổn hại cho dữ liệu của bạn. Bộ công cụ bên dưới tiết lộ một trường thứ hai và kích hoạt nút gửi từ trình xử lý change của lựa chọn:

js Copy
import { chromium } from 'playwright-core';

const endpoint = 'wss://browser.scrapeless.com/api/v2/browser?' + new URLSearchParams({
  token: process.env.SCRAPELESS_API_KEY,
  sessionTTL: '300',
});
const browser = await chromium.connectOverCDP(endpoint);
const context = browser.contexts()[0];
const page = context.pages()[0] || await context.newPage();

const fixture = `<!doctype html><meta charset=utf-8><title>conditional</title>
<form id=f>
  <select id=plan name=plan>
    <option value="">choose</option>
    <option value="basic">Basic</option>
    <option value="team">Team</option>
  </select>
  <div id=extra hidden><input id=seats name=seats type=number></div>
  <button id=go type=submit disabled>Continue</button>
</form>
<script>
var plan = document.getElementById('plan');
plan.addEventListener('change', function(){
  document.getElementById('extra').hidden = (plan.value !== 'team');
  document.getElementById('go').disabled = !plan.value;
});
</script>`;
const url = 'data:text/html;charset=utf-8,' + encodeURIComponent(fixture);

async function trial(name, action) {
  await page.goto(url, { waitUntil: 'load' });
  await action();
  await page.waitForTimeout(200);
  console.log(name, '->', JSON.stringify(await page.evaluate(() => ({
    selectValue: document.getElementById('plan').value,
    seatsVisible: !document.getElementById('extra').hidden,
    submitEnabled: !document.getElementById('go').disabled,
  }))));
}

await trial('A: select.value = "team"', () =>
  page.$eval('#plan', el => { el.value = 'team'; }));

await trial('B: select.value + dispatchEvent(change)', () =>
  page.$eval('#plan', el => {
    el.value = 'team';
    el.dispatchEvent(new Event('change', { bubbles: true }));
  }));

await trial('C: locator.selectOption("team")', async () => {
  await page.selectOption('#plan', 'team');
  await page.waitForSelector('#seats', { state: 'visible' });
  await page.fill('#seats', '25');
});

await browser.close();
text Copy
A: select.value = "team" -> {"selectValue":"team","seatsVisible":false,"submitEnabled":false}
B: select.value + dispatchEvent(change) -> {"selectValue":"team","seatsVisible":true,"submitEnabled":true}
C: locator.selectOption("team") -> {"selectValue":"team","seatsVisible":true,"submitEnabled":true}

Trường hợp A là thất bại đáng nhớ. Lựa chọn thực sự giữ "team", vì vậy một kịch bản xác minh công việc của mình bằng cách đọc giá trị ngược lại báo cáo thành công — trong khi trường ghế mà nó lẽ ra phải điền vẫn bị ẩn và nút mà nó lẽ ra phải nhấp vẫn bị vô hiệu hóa. selectOption() tránh toàn bộ loại vấn đề này vì nó phát đi inputchange theo cách mà lựa chọn của người dùng làm.

Hộp kiểm và đài phát thanh

Sử dụng check()uncheck() thay vì click(). Chúng khẳng định trạng thái kết quả thay vì chuyển đổi mù quáng, vì vậy một hộp đã có checked trong cấu trúc không bị đảo ngược theo cách sai lầm:

js Copy
await page.check('#my-check-2');
await page.uncheck('#my-check-1');
await page.check('#my-radio-2');

Bộ chọn ngày loại bỏ những gì bạn đã gõ

Một trường trông giống như một đầu vào ngày thường là một đầu vào văn bản với một lịch JavaScript đính kèm, và lịch đó sở hữu giá trị. Sáu cách nhập cùng một ngày vào một trường ngày công cộng tạo ra bốn giá trị lưu trữ khác nhau:

Kỹ thuật Giá trị trường sau đó Lớp phủ
fill('2026-08-06') "2026-08-06" vẫn mở
fill(...) sau đó Escape "" đã đóng
fill(...) sau đó nhấp vào nơi khác "" đã đóng
fill(...) sau đó Enter "08/06/2026" vẫn mở
el.value = '2026-08-06' "2026-08-06" không bao giờ mở
pressSequentially('2026-08-06') sau đó Escape "10/08/6" đã đóng

Ba trong số đó là bẫy. Bỏ lớp phủ bằng phím Escape hoặc bằng cách nhấp vào nơi khác đã trả lại trường về chuỗi rỗng, vì tiện ích coi việc từ chối như một chỉnh sửa bị bỏ rơi. Gõ từng ký tự cho phép lịch định dạng lại trường giữa chừng và tạo ra một giá trị không khớp với bất cứ điều gì. Gán trực tiếp giữ lại văn bản nguyên vẹn chỉ vì tiện ích không bao giờ tham gia vào — điều này có nghĩa là trạng thái nội bộ của lịch và trường không đồng nhất.

Cam kết với Enter là kết thúc hoạt động, và tiện ích viết lại giá trị vào định dạng hiển thị của chính nó. Xác minh giá trị đã cam kết thay vì giả định rằng đầu vào của bạn còn sống sót:

js Copy
await page.fill('input[name="my-date"]', '2026-08-06');
await page.press('input[name="my-date"]', 'Enter');
console.log(await page.inputValue('input[name="my-date"]'));
// 08/06/2026

Nơi mà một <input type="date"> thực sự được sử dụng thay thế, bức tranh trở nên đơn giản hơn: giá trị phải là một chuỗi YYYY-MM-DD ISO bất kể định dạng hiển thị cho người dùng.

Đầu vào tệp trên trình duyệt không phải là cục bộ

Một lo ngại hợp lý với trình duyệt đám mây là việc tải tệp không thể hoạt động, vì tệp sống trên máy của bạn và trình duyệt thì không. setInputFiles() xử lý việc chuyển giao qua giao thức, vì vậy không có hộp thoại hệ điều hành nào liên quan:

js Copy
import fs from 'node:fs';
import { chromium } from 'playwright-core';

fs.writeFileSync('upload-sample.txt', 'scrapeless form upload fixture\n');

const endpoint = 'wss://browser.scrapeless.com/api/v2/browser?' + new URLSearchParams({
  token: process.env.SCRAPELESS_API_KEY,
  sessionTTL: '300',
});
const browser = await chromium.connectOverCDP(endpoint);
const context = browser.contexts()[0];
const page = context.pages()[0] || await context.newPage();

await page.goto('https://www.selenium.dev/selenium/web/web-form.html', {
  waitUntil: 'domcontentloaded',
});

await page.setInputFiles('input[name="my-file"]', './upload-sample.txt');

console.log(await page.$eval('input[name="my-file"]', el => ({
  files: el.files.length,
  name: el.files[0].name,
  hasBytes: el.files[0].size > 0,
})));

await Promise.all([
  page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
  page.click('button[type="submit"]'),
]);
console.log('submitted my-file:',
  JSON.stringify(new URL(page.url()).searchParams.get('my-file')));
await browser.close();
text Copy
{ files: 1, name: 'upload-sample.txt', hasBytes: true }
submitted my-file: "upload-sample.txt"

Phiên làm việc từ xa đã nhận tệp, FileList được lấp đầy, và tên tệp xuất hiện trong dữ liệu biểu mẫu được gửi.

Biểu mẫu nhiều bước

Xem mỗi bước như một điều kiện tiên quyết thay vì một sự trì hoãn. Sau khi cam kết trường tiến tới biểu mẫu, hãy chờ một điều khiển chỉ tồn tại ở bước tiếp theo trước khi chạm vào nó:

js Copy
await page.selectOption('#plan', 'team');
await page.waitForSelector('#seats', { state: 'visible' });
await page.fill('#seats', '25');

Chờ trên chính phần tử giữ kịch bản gắn liền với trạng thái thực của biểu mẫu, nguyên tắc tương tự mà khiến trường hợp lựa chọn điều kiện hoạt động.


Đọc trạng thái đã gửi lại

Xác nhận duy nhất có thẩm quyền là những gì máy chủ nhận được. Biểu mẫu này gửi qua GET, vì vậy các giá trị được chấp nhận rơi vào chuỗi truy vấn của trang tiếp theo:

js Copy
import { chromium } from 'playwright-core';

const endpoint = 'wss://browser.scrapeless.com/api/v2/browser?' + new URLSearchParams({
  token: process.env.SCRAPELESS_API_KEY,
  sessionTTL: '300',
});
const browser = await chromium.connectOverCDP(endpoint);
const context = browser.contexts()[0];
const page = context.pages()[0] || await context.newPage();

await page.goto('https://www.selenium.dev/selenium/web/web-form.html', {
  waitUntil: 'domcontentloaded',
});

await page.fill('#my-text-id', 'Ada Lovelace');
await page.selectOption('select[name="my-select"]', '2');
await page.check('#my-check-2');
await page.uncheck('#my-check-1');
await page.check('#my-radio-2');
await page.fill('input[name="my-date"]', '2026-08-06');
await page.press('input[name="my-date"]', 'Enter');

await Promise.all([
  page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
  page.click('button[type="submit"]'),
]);

const params = Object.fromEntries(new URL(page.url()).searchParams.entries());
for (const key of ['my-text', 'my-select', 'my-check', 'my-date']) {
  console.log(key.padEnd(10), JSON.stringify(params[key]));
}
await browser.close();
text Copy
my-text    "Ada Lovelace"
my-select  "2"
my-check   "on"
my-date    "08/06/2026"

Kiểm tra cái nhìn của máy chủ cho bạn biết biểu mẫu đã được chấp nhận, không chỉ rằng các trường đã được điền. Lưu ý rằng my-date mang định dạng đã cam kết của tiện ích, không phải chuỗi mà đã được gõ.


Đọc lỗi xác thực từ DOM

Khi một bài nộp bị từ chối, trình duyệt đã giữ một giải thích có cấu trúc. API xác thực ràng buộc trong Tiêu chuẩn HTML thể hiện quy tắc nào đã thất bại và thông điệp mà trình duyệt sẽ hiển thị, vì vậy không cần chụp lại màn hình trang và đọc các pixel:

js Copy
import { chromium } from 'playwright-core';

const endpoint = 'wss://browser.scrapeless.com/api/v2/browser?' + new URLSearchParams({
  token: process.env.SCRAPELESS_API_KEY,
  sessionTTL: '300',
});
const browser = await chromium.connectOverCDP(endpoint);
const context = browser.contexts()[0];
const page = context.pages()[0] || await context.newPage();

const fixture = `<!doctype html><meta charset=utf-8><title>validation</title>
<form id=f>
  <input id=email name=email type=email required>
  <input id=code name=code type=text required minlength=4>
  <button type=submit>Send</button>
</form>
<script>
window.__submitted = false;
document.getElementById('f').addEventListener('submit', function(e){
  e.preventDefault(); window.__submitted = true;
});
</script>`;
await page.goto('data:text/html;charset=utf-8,' + encodeURIComponent(fixture),
  { waitUntil: 'load' });

await page.fill('#email', 'not-an-address');
await page.fill('#code', 'ab');

const report = await page.evaluate(() => {
  const form = document.getElementById('f');
  form.requestSubmit();
  return {
    submitted: window.__submitted,
    fields: [...form.elements]
      .filter(el => el.willValidate && !el.checkValidity())
      .map(el => ({
        name: el.name,
        message: el.validationMessage,
        failed: Object.keys(ValidityState.prototype)
          .filter(k => k !== 'valid' && el.validity[k]),
      })),
  };
});
console.log(JSON.stringify(report, null, 1));
await browser.close();
text Copy
{
 "submitted": false,
 "fields": [
  {
   "name": "email",
   "message": "Please include an '@' in the email address. 'not-an-address' is missing an '@'.",
   "failed": [
    "typeMismatch"
   ]
  },
  {
   "name": "code",
   "message": "Please lengthen this text to 4 characters or more (you are currently using 2 characters).",
   "failed": [
    "tooShort"
   ]
  }
 ]
}

submittedfalse vì trình duyệt đã chặn bài nộp trước khi trình xử lý chạy, và mỗi trường thất bại chỉ ra quy tắc hỏng của riêng nó. Điều đó cho bạn một lý do có thể đọc được bằng máy để ghi lại thay vì sự vắng mặt không giải thích được của kết quả. Tham khảo MDN về xác thực biểu mẫu phía client bao gồm tập hợp các ràng buộc rộng hơn mà các cờ này ánh xạ đến.

Các trang tự tạo văn bản lỗi của riêng họ thay vì dựa vào trình duyệt là trường hợp mà phép đo đầu tiên đã giải thích: những thông điệp đó chỉ xuất hiện nếu các sự kiện mà chúng lắng nghe đã được phát.


Nơi một trình duyệt địa phương hết

Mọi kỹ thuật ở trên đều là Playwright thông thường và cũng chạy trên một Chromium địa phương. Tài liệu đầu vào Playwright mô tả các phương pháp tương tự. Điều mà một trình duyệt địa phương không cung cấp cho bạn là một môi trường nhất quán để biểu mẫu được đánh giá.

Các biểu mẫu là phần của một trang web gắn liền nhất với các kiểm soát gian lận và lạm dụng, và các kiểm soát đó nhìn vào phiên làm việc thay vì đánh dấu: địa chỉ rời, dấu vân tay, và liệu trình duyệt có trình bày như một cài đặt chính hãng hay không. Đó là phần mà Scrapeless Scraping Browser cung cấp: một trình duyệt đám mây có thể tùy chỉnh, chống phát hiện, được hỗ trợ bởi Chromium tự phát triển, được cấu hình từng phiên tại thời điểm kết nối thông qua các tham số như sessionTTL, tất cả đều dưới cùng một connectOverCDP mà mã của bạn đã sử dụng. Không có gì trong việc xử lý widget thay đổi; chỉ có môi trường là thay đổi. Trang sản phẩm Scraping Browsertài liệu bao quát toàn bộ bề mặt phiên.


Khắc phục sự cố

Triệu chứng Nguyên nhân Cách khắc phục
Trường giữ giá trị nhưng không có gì khác trên trang phản ứng Phân công .value trực tiếp không phát đi bất kỳ sự kiện nào Sử dụng fill(), hoặc phát inputchange một cách rõ ràng
Trường phụ không bao giờ xuất hiện Trình xử lý change làm lộ nó chưa bao giờ chạy Sử dụng selectOption(), sau đó chờ đợi cho bộ chọn phụ
Nút gửi vẫn bị vô hiệu hóa Trang kích hoạt nó từ một sự kiện bạn không phát Điều khiển trường bằng phương pháp vị trí
Trường ngày trống sau khi điền Bộ chọn xem xét việc từ chối như một chỉnh sửa bị bỏ dở Cam kết bằng Enter và đọc giá trị trở lại
Giá trị ngày nhìn như bị lộn xộn Lịch đã định dạng lại trường giữa các lần gõ phím Sử dụng fill() cộng với Enter thay vì gõ từng ký tự
page.setContent() bị từ chối Trang mặc định áp dụng Trusted Types Điều hướng đến một URL data: thay vào đó
Bài nộp im lìm không làm gì cả Xác thực ràng buộc đã chặn nó Đọc validationMessagevalidity cho từng trường

Kết luận

Các vấn đề tự động hóa biểu mẫu trông bí ẩn thường là một điều đo được: một giá trị tồn tại trong DOM mà không có các sự kiện làm cho trang chú ý đến nó. Phân công trực tiếp thiết lập giá trị, thỏa mãn xác thực ràng buộc, và lấp đầy FormData — và không nói với ai cả. Các phương pháp vị trí như fill, check, và selectOption phát ra những gì mà trang đang lắng nghe, đó là lý do tại sao chúng vẫn hoạt động khi một biểu mẫu phát triển thành một trường phụ, một widget bộ chọn, hoặc một lớp xác thực.

Hãy xây dựng thói quen xác nhận kết quả thay vì đầu vào: đọc giá trị đã cam kết trở lại từ một widget, chờ đợi cho phần tử mà sự thay đổi đã được dự kiến ​​rõ ràng, và kiểm tra validity khi một bài nộp không đi đến đâu. Chạy trên Scrapeless Scraping Browser giữ môi trường nhất quán bên dưới tất cả những điều đó, vì vậy biến số duy nhất còn lại là logic bộ chọn của bạn.


Sẵn sàng để xây dựng Pipeline dữ liệu của bạn với AI?

Tham gia cộng đồng để yêu cầu một kế hoạch miễn phí và so sánh ghi chú với các nhà phát triển tự động hóa các quy trình làm việc dựa trên biểu mẫu: Discord · Telegram.
Đăng ký tại app.scrapeless.com để nhận thời gian chạy Scraping Browser miễn phí, và xem giá cả khi bạn mở rộng.


FAQ

Q: Việc thiết lập element.value trực tiếp có bao giờ hoạt động không?
Có, đối với những mẫu đơn đọc DOM vào thời điểm gửi. Trong lần đo lường, việc gán trực tiếp đã làm đầy FormData và vượt qua kiểm tra ràng buộc. Nó thất bại ở bất kỳ đâu trang giữ bản sao riêng của trạng thái hoặc hiển thị bất cứ điều gì từ một input hoặc change listener, vì việc gán không phát ra bất kỳ sự kiện nào.

Q: Tôi cần phát ra sự kiện nào nếu tôi phải thiết lập một giá trị thủ công?
Phát ra một sự kiện input bùng phát cho các trường văn bản và một sự kiện change bùng phát cho các lựa chọn, ô kiểm và radio. Một số khung theo dõi giá trị trước đó trên phần tử và bỏ qua một sự kiện mà họ tin rằng đã ghi lại; gọi setter nguyên mẫu gốc trước khi phát ra giúp tránh điều đó. Sử dụng fill(), check(), hoặc selectOption() hoàn toàn tránh được vấn đề.

Q: Tại sao trường ngày của tôi lại trống sau khi tôi đã điền?
Một widget lịch JavaScript có khả năng đã quay lại nó. Việc điền trường và sau đó nhấn Escape hoặc nhấp vào nơi khác đã trả về một chuỗi trống trong thử nghiệm, vì widget coi một overlay bị từ chối như một chỉnh sửa bị bỏ lại. Cam kết giá trị bằng Enter và đọc lại nó trước khi gửi.

Q: Tôi có thể tải lên một tệp khi trình duyệt chạy trên đám mây không?
Có. setInputFiles() chuyển tệp cục bộ đến phiên từ xa qua giao thức, vì vậy không có hộp thoại tệp hệ điều hành nào tham gia. FileList đã được điền và dữ liệu biểu mẫu được gửi đều xác nhận việc tải lên.

Q: Làm thế nào tôi biết tại sao mẫu không chịu gửi?
Lặp qua các phần tử của mẫu, lọc những phần tử mà checkValidity() trả về false, và đọc từng validationMessagevalidity flag của chúng. Điều đó cho bạn biết về ràng buộc cụ thể thất bại, chẳng hạn như typeMismatch hoặc tooShort, mà không cần kiểm tra pixel đã hiển thị.

Q: Tôi có nên sử dụng fill() hay gõ từng ký tự một?
Ưu tiên fill(). Nó phát ra sự chú ý, beforeinput, và input, đó là những gì hầu hết mọi mẫu nghe. Chỉ nên sử dụng nhập từng ký tự một khi trang phản ứng với từng lần gõ phím, chẳng hạn như một autocomplete truy vấn trên mỗi chữ cái - và kiểm tra kết quả, vì một số widget định dạng lại trường giữa các lần gõ phí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.

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

Danh mục