集成验证码 OCR 识别:添加 doLoginWithCaptcha 方法,通过 Ollama qwen3-vl:4b 自动识别验证码并登录

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Niko 2026-05-04 23:59:50 +08:00
parent 882c1b9b38
commit 437dbe78db

View File

@ -4,11 +4,17 @@ import com.microsoft.playwright.*;
import com.microsoft.playwright.options.LoadState; import com.microsoft.playwright.options.LoadState;
import com.microsoft.playwright.options.WaitUntilState; import com.microsoft.playwright.options.WaitUntilState;
import java.io.BufferedReader;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Base64;
import static java.nio.file.Files.createDirectories; import static java.nio.file.Files.createDirectories;
@ -22,8 +28,10 @@ public class EtsScraper {
private static final String USERNAME = "sccw"; private static final String USERNAME = "sccw";
private static final String PASSWORD = "slife@123"; private static final String PASSWORD = "slife@123";
private static final Path SCREENSHOT_DIR = Path.of("screenshots"); private static final Path SCREENSHOT_DIR = Path.of("screenshots");
private static final String OLLAMA_URL = "http://10.0.1.39:11434";
private static final String OLLAMA_MODEL = "qwen3-vl:4b";
public static void main(String[] args) { public static void main(String[] args) throws Exception {
try { try {
createDirectories(SCREENSHOT_DIR); createDirectories(SCREENSHOT_DIR);
} catch (Exception e) { } catch (Exception e) {
@ -65,8 +73,8 @@ public class EtsScraper {
// Close dialog again after page reload // Close dialog again after page reload
closeNotificationDialog(page); closeNotificationDialog(page);
// Perform login // Recognize captcha and perform login
boolean loggedin = doLogin(page); boolean loggedin = doLoginWithCaptcha(page);
if (loggedin) { if (loggedin) {
System.out.println("[+] Login successful!"); System.out.println("[+] Login successful!");
@ -147,6 +155,85 @@ public class EtsScraper {
return true; return true;
} }
} }
private static boolean doLoginWithCaptcha(Page page) throws Exception {
// Find and fill username
String usernameInput = findInput(page, new String[]{
"input[placeholder*='用户名']",
"input[placeholder*='username']",
"input[placeholder*='账号']",
"input[name*='user']",
"input[name='username']",
"input[type='text']",
});
if (usernameInput == null) {
System.out.println("[-] Could not find username input");
return false;
}
// Find and fill password
String passwordInput = findInput(page, new String[]{
"input[placeholder*='密码']",
"input[placeholder*='password']",
"input[name*='pass']",
"input[name='password']",
"input[name='pwd']",
"input[type='password']",
});
if (passwordInput == null) {
System.out.println("[-] Could not find password input");
return false;
}
// Find and fill captcha
String captchaInput = findInput(page, new String[]{
"input[placeholder*='验证码']",
"input[placeholder*='captcha']",
"input[name*='captcha']",
"input[name='code']",
"input[type='text']",
});
if (captchaInput == null) {
System.out.println("[-] Could not find captcha input");
return false;
}
// Recognize captcha
Path captchaPath = SCREENSHOT_DIR.resolve("captcha.png");
System.out.println("[*] Recognizing captcha with Ollama...");
String captchaText = recognizeCaptcha(captchaPath);
if (captchaText == null || captchaText.isEmpty()) {
System.out.println("[-] Failed to recognize captcha");
return false;
}
System.out.println("[+] Captcha recognized: " + captchaText);
System.out.println("[*] Filling credentials...");
page.locator(usernameInput).first().fill(USERNAME);
page.locator(passwordInput).first().fill(PASSWORD);
page.locator(captchaInput).first().fill(captchaText);
sleep(500);
// Click submit or press Enter
String submitBtn = findSubmit(page);
if (submitBtn != null) {
System.out.println("[*] Clicking submit button: " + submitBtn);
page.locator(submitBtn).first().click();
} else {
System.out.println("[*] No submit button found, pressing Enter");
page.locator(captchaInput).first().press("Enter");
}
try {
page.waitForLoadState(LoadState.DOMCONTENTLOADED,
new Page.WaitForLoadStateOptions().setTimeout(10000));
return true;
} catch (Exception e) {
System.out.println("[!] Navigation timed out, but credentials were submitted");
return true;
}
}
private static void downloadCaptcha(Page page) { private static void downloadCaptcha(Page page) {
try { try {
// Set up listener FIRST, then reload to trigger the request // Set up listener FIRST, then reload to trigger the request
@ -170,8 +257,6 @@ public class EtsScraper {
} }
} }
private static void closeNotificationDialog(Page page) { private static void closeNotificationDialog(Page page) {
// Find the frame that contains the notification dialog // Find the frame that contains the notification dialog
Frame dialogFrame = null; Frame dialogFrame = null;
@ -204,8 +289,6 @@ public class EtsScraper {
System.out.println("[*] Notification dialog closed"); System.out.println("[*] Notification dialog closed");
} }
private static String findInput(Page page, String[] selectors) { private static String findInput(Page page, String[] selectors) {
for (String selector : selectors) { for (String selector : selectors) {
try { try {
@ -259,4 +342,54 @@ public class EtsScraper {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
} }
public static String recognizeCaptcha(Path imagePath) throws Exception {
byte[] imageBytes = Files.readAllBytes(imagePath);
String base64 = Base64.getEncoder().encodeToString(imageBytes);
String json = "{"
+ "\"model\":\"" + OLLAMA_MODEL + "\","
+ "\"messages\":["
+ " {"
+ " \"role\":\"user\","
+ " \"content\":\"识别图中的验证码文字,只返回文字内容,不要有其他解释\","
+ " \"images\":[\"" + base64 + "\"]"
+ " }"
+ "]"
+ "}";
URL url = new URL(OLLAMA_URL + "/api/chat");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setConnectTimeout(15000);
conn.setReadTimeout(30000);
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/json; charset=utf-8");
conn.getOutputStream().write(json.getBytes("utf-8"));
conn.getOutputStream().flush();
conn.getOutputStream().close();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream(), "utf-8"))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
String response = sb.toString();
// Parse "content":"..." from the JSON response
int contentIdx = response.indexOf("\"content\":");
if (contentIdx >= 0) {
int start = response.indexOf('"', contentIdx + 10) + 1;
int end = response.indexOf('"', start);
if (start > 0 && end > start) {
return response.substring(start, end).trim();
}
}
return null;
} finally {
conn.disconnect();
}
}
} }