Copy từ phần mềm này, nhập sang phần mềm kia
Công việc lâu đời nhất chưa ai tự động hóa trong mọi văn phòng: danh sách nằm ở một phần mềm, form nằm ở phần mềm khác, và một người ngồi gõ lại. Không phần mềm nào có đường xuất mà bên kia nhập được, nên kênh duy nhất giữa chúng chính là kênh một con người vẫn dùng — màn hình, bàn phím, và clipboard.
Trang này dựng đúng flow đó từ đầu tới cuối. Đây là flow thật, 30 node, chạy tới Success.
Mục tiêu
Đọc năm bản ghi khách hàng từ một file văn bản và nhập từng bản ghi vào một form web chạy trong Chrome, với một captcha chắn ngay ở cửa vào.
Nó minh họa:
- Hai cửa sổ giữ cùng lúc, mỗi cửa sổ được gọi bằng alias chứ không phải bằng "cái đang ở trước mặt"
- Một cú
Ctrl+Cmang cả file về thành một biến flow split()biến chuỗi đó thành năm bản ghi, mỗi bản ghi năm trường- Một vòng thử có cận quanh captcha — ba lượt rồi thôi, không bao giờ lặp vô hạn
- Click đúng chỗ Find Image vừa tìm thấy, thay vì một điểm cố định
Điều kiện: đã bật Desktop Mode, và một flow có target là desktop.
Hình dáng của nó
Ba biến, đều phạm vi flow:
| Biến | Kiểu | Mặc định | Chứa |
|---|---|---|---|
vars.noidung | string | "" | Cả file dữ liệu, đúng như đã copy |
vars.i | number | 0 | Đang ở bản ghi thứ mấy |
vars.j | number | 0 | Đang ở lượt captcha thứ mấy |
Bước 1 — Mở cả hai phần mềm, và đặt tên cho chúng
Hai node Open App. Điều quan trọng không phải là chúng khởi chạy được cái gì đó; mà là mỗi node chiếm một cửa sổ dưới một cái tên mà cả flow còn lại dùng đư ợc.
| Form | File dữ liệu | |
|---|---|---|
| Chương trình | chrome.exe | notepad.exe |
| Tham số | --app=http://…/form.html, --window-size=1000,760, --window-position=880,60, một --user-data-dir riêng | …\khach_hang.txt |
| Execution Context | Specific Window | Specific Window |
| Tiêu đề chứa | QLBH | khach_hang |
| Alias | qlbh | nguon |
| Wait after | 1200 ms | 1200 ms |
Hãy bấm Browse… để chọn chương trình thay vì gõ đường dẫn. Nó nhận .exe, nhận .bat, và — rất tiện — nhận cả .lnk, nên cái shortcut trình duyệt sẵn trên Desktop là một câu trả lời hợp lệ.
--user-data-dir trỏ vào chỗ riêng chính là thứ khiến việc này lặp lại được. Không có nó, Chrome giao URL cho bất kỳ cửa sổ nào bạn đang mở sẵn, và flow thừa hưởng luôn tab của bạn, phiên đăng nhập của bạn, các popup thông báo của bạn. Có nó, flow luôn nhận một cửa sổ sạch, còn việc lướt web của bạn không bị đụng tới.
Các alias là toàn bộ ý đồ thiết kế. Mọi node phía sau đều nêu qlbh hay nguon trong Execution Context của chính nó, nên "gõ cái này" và "copy cái kia" rơi đúng phần mềm bất kể trong lúc flow chạy người dùng có click vào đâu. Không node nào trong flow này tác động lên "cửa sổ đang hoạt động".
Bước 2 — Qua captcha, theo cách có cận
Một node Captcha ở chế độ solveAndApply, giới hạn vào một vùng của cửa sổ qlbh, vượt qua cửa thứ nhất. Cửa thứ hai — mã xác minh — được bọc trong một vòng lặp không thể chạy mãi:
Loop "ma", chế độ Count, tối đa 3, biến đếm vars.j
Captcha: đọc mã và gõ vào (timeout 60s / 20s)
Find Image: danh sách sản phẩm hiện chưa?
Success → Break
Failure → Loop End (thử lại)
Loop End
Chính Find Image mới là phần xác minh. Node captcha thành công nghĩa là nó đã gõ một cái gì đó, không phải là trang đã chấp nhận — nên flow đi tìm một thứ chỉ tồn tại ở bên kia cửa, rồi mới thoát vòng.
Ba lượt, rồi vòng lặp tự kết thúc. Thử lại mà không có cận là cách phổ biến nhất khiến một flow không người trông trở thành một flow treo.
Bước 3 — Lấy cả file bằng một cú copy
Đây là mẹo đáng học. Ba node, tất cả đều scope vào nguon:
| Node | Thiết lập | Vì sao |
|---|---|---|
| Click | Một vùng phủ lên ô văn bản, waitAfterMs 400 | Focus. Cửa sổ nổi lên trước không có nghĩa là ô văn bản của nó đang nhận phím |
| Key Press | Ctrl + A | Chọn hết |
| Key Press | Ctrl + C, Lấy vào biến noidung, timeout 3000 ms | Chữ vừa copy rơi vào một biến flow |
Ô Lấy vào biến biến một tổ hợp copy thành một phép đọc. Sau node này, vars.noidung giữ đúng nội dung file — nguyên dấu — và không có OCR nào tham gia, nên không có gì để đọc nhầm.
Nếu không copy được, node rẽ Failure chứ không trả về thứ đang sẵn trên clipboard. Đó là chủ đích: nhìn vào nội dung thì hai thứ đó y hệt nhau, và một flow âm thầm gõ bản copy tuần trước vào form là cái sai không ai phát hiện. Nguyên nhân thường gặp là focus — nên mới có cú Click.
Bước 4 — Đi qua các bản ghi bằng split()
File dữ liệu mỗi dòng một trường, mỗi bản ghi năm trường:
KH001
Nguyễn Văn A
0901234567
12 Lê Lợi, Q1
1500000
KH002
…
Một vòng lặp qua năm bản ghi, và mỗi trường là một chỉ số trong kết quả split:
Loop "don", chế độ Count, tối đa 5, biến đếm vars.i
Input Text split(vars.noidung, "\n")[vars.i * 5 + 0] → Mã KH
Key Press Tab
Input Text split(vars.noidung, "\n")[vars.i * 5 + 1] → Tên KH
Key Press Tab
Input Text split(vars.noidung, "\n")[vars.i * 5 + 2] → Điện thoại
Key Press Tab
Input Text split(vars.noidung, "\n")[vars.i * 5 + 3] → Địa chỉ
Key Press Tab
Input Text split(vars.noidung, "\n")[vars.i * 5 + 4] → Số tiền
Key Press Tab
Key Press Enter → lưu
Loop End
vars.i * 5 + n chính là toàn bộ cách đánh chỉ số: biến đếm vòng lặp chọn bản ghi, hằng số chọn trường. Đổi file sang sáu trường một bản ghi thì chỉ một con số đổi ở năm chỗ.
Tab chuyển giữa các ô vì đó là điều một người vẫn làm — nó không cần tọa độ nào, và nó sống sót qua việc form bị dời hay đổi kích thước.
Bước 5 — Click đúng chỗ vừa tìm thấy ảnh
Bên trong vòng lặp, trước khi gõ, một Find Image định vị dòng sản phẩm trên qlbh, và node Click ngay sau đó không dùng điểm cố định. Mục tiêu của nó là một kết quả Find Image sống — đúng tọa độ lần tìm vừa rồi tạo ra.
Điều này quan trọng vì danh sách có cuộn. Một tọa độ cố định đúng cho tới khi trang xê dịch; một tọa độ đến từ chính lần tìm vừa chạy thì luôn đúng, và nếu lần tìm hỏng thì cú click không xảy ra chút nào.
Kết quả
Flow kết ở một node Stop với trạng thái success, và Desktop Dashboard hiện nó trong hàng đợi với chữ Success màu xanh.

Một máy, mỗi lúc một flow, và một lần chạy đã xong nằm trong hàng đợi.
Vì sao dựng theo cách này
| Quyết định | Phương án kia, và vì sao không |
|---|---|
| Giữ cửa sổ bằng alias | "Cửa sổ đang hoạt động" — người dùng lỡ click một cái là flow gõ vào email của họ |
| Clipboard, không phải OCR | OCR đọc nhầm được; copy thì không. Dùng OCR khi không có chữ để copy, không phải khi có |
Một cú copy, rồi split() | Năm mươi cặp Shift+End/Ctrl+C — năm mươi cơ hội mất focus |
| Vòng captcha giới hạn 3 | Thử lại không cận là cách một flow biến thành tiến trình treo |
| Find Image xác minh, captcha chỉ hành động | Node captcha gõ được không có nghĩa trang đã nhận |
| Click từ kết quả Find Image | Điểm cố định đúng cho tới khi danh sách cuộn |
Tab giữa các ô | Năm tọa độ sẽ hỏng khi cửa sổ đổi kích thước |
Mẹo
- Test Node trước Test Flow. Từng node kiểm tra riêng được, và khi đó nó tự bind Execution Context của chính nó — nên bạn xác nhận được cú
Ctrl+Ccó thật sự đổ đầynoidungmà không phải ngồi chờ hết captcha. - Để ý các giá trị
waitAfterMs. Chúng nhỏ (105–700 ms) và chúng gánh việc: một form vẽ lại sau Tab cần khoảng nghỉ đó hơn là cần phím gõ nhanh. - Đặt cận vòng lặp mà bạn bảo vệ được.
tối đa 5ở đây là số bản ghi; nếu file có thể dài hơn, hãy đọc ra số lượng thay vì đoán cho cao. - Giữ hình dạng file dữ liệu ở một chỗ. Con
* 5xuất hiện trong năm biểu thức. Nếu nó còn lớn hơn nữa, một node Variable giữ số trường là đáng thêm.
Ghi chú và giới hạn
- Một desktop, mỗi lúc một flow. Các lần chạy desktop là tuần tự — workspace này không có đội instance, nên flow thứ hai phải chờ.
- Clipboard là của cả máy. Một flow khác copy đúng lúc đó thì không phân biệt được với flow này. Chỉ nên có một cú copy đang trong không trung.
- Cửa sổ Chrome
--app=không phải lúc nào cũng mô tả duy nhất được. Nó sinh ra một cửa sổ song sinh vô hình dùng chung cả tiêu đề lẫn class. Nếu trình chọn cửa sổ từ chối chọn, hãy dùng một cửa sổ Chrome thường — cái đó mô tả được. split()không tự trim. Một dòng trống ở cuối file dữ liệu sẽ thành một bản ghi rỗng. Hãytrim()bản copy trước, hoặc bảo đảm file không có ký tự xuống dòng ở cu ối.
Trang liên quan
- Desktop Mode — workspace mà cái này chạy trong đó
- Open App — chiếm một cửa sổ
- Key Press — modifier, và Lấy vào biến
- Biểu thức và hàm —
split()và 28 hàm còn lại - Captcha — solveAndApply