杰瑞科技汇

Java如何实现12306验证码识别?

这是一个非常经典的、具有挑战性的项目,因为它涉及到反爬虫技术,12306的验证码也在不断升级,所以任何具体的技术方案都可能在未来失效,但理解其背后的原理和实现方法,对于学习图像处理和OCR技术非常有价值。

Java如何实现12306验证码识别?-图1
(图片来源网络,侵删)

我们将分步进行讲解,从原理到实践。


第一部分:核心原理与挑战

12306验证码的主要目的是区分“人”和“机器”,其设计思路就是让机器难以识别,识别它的核心挑战在于:

  1. 高度干扰元素:背景中充满了大量颜色、形状各异的噪点、线条,严重干扰了主体图形的识别。
  2. 字体变形与扭曲:验证码文字(或图标)会进行随机的旋转、拉伸、扭曲,使得标准OCR难以识别。
  3. 多模态识别:早期的12306是文字识别,后来升级为“点选式”或“滑动式”,这不再是简单的图像识别,而是需要图像理解坐标定位的能力,我们这里主要讨论更具挑战性的点选式验证码
  4. 动态更新:12306会定期更换验证码的生成算法,以对抗自动化识别。

点选式验证码的识别流程可以分解为以下几个步骤:

  1. 获取验证码图片:从12306的登录接口请求验证码图片。
  2. 图像预处理:对原始图片进行去噪、二值化、颜色分离等操作,将干扰元素尽可能去除,突出目标物体(文字/图标)。
  3. 目标检测与定位:识别出图片中所有需要点击的物体(“所有包含‘汽车’的图标”),并确定它们在图片中的中心坐标。
  4. 生成点击序列:根据题目要求(请点击所有包含‘汽车’的图片”),将定位到的目标坐标按一定顺序排列。
  5. 模拟点击:使用自动化工具(如Selenium)模拟鼠标点击这些坐标,完成验证。

第二部分:技术选型

我们将使用一个强大的开源图像处理库来处理这些步骤:Tesseract OCR,但请注意,直接使用Tesseract对原始12306验证码进行文字识别,成功率极低,我们需要先用图像预处理技术“净化”图片,再让Tesseract去识别净化后的图片内容。

Java如何实现12306验证码识别?-图2
(图片来源网络,侵删)

主要工具/库:

  1. Java HTTP客户端:用于从12306服务器下载验证码图片。
    • OkHttpApache HttpClient (推荐OkHttp,更现代)
  2. 图像处理库:用于图像预处理。
    • OpenCV:功能最强大,是工业级标准,但需要额外配置。
    • Thumbnailator:轻量级,适合简单操作。
    • Java AWT/Swing:Java自带,可以实现一些基础图像操作。
    • Tesseract OCR for Java (Tess4J):这是我们将要重点使用的,它是对Tesseract OCR引擎的Java封装。
  3. 浏览器自动化工具:用于模拟登录和点击。
    • Selenium:最流行的Web自动化测试框架,可以控制浏览器执行各种操作。

第三部分:实践步骤(以点选式验证码为例)

假设我们要识别一个要求“请点击所有包含‘汽车’的图片”的点选验证码。

步骤 1:获取验证码图片

你需要分析12306的登录请求,找到获取验证码图片的API,这个API通常会返回一个包含图片数据的流,以及一个用于后续提交的answerrandCode参数。

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
import java.io.InputStream;
public class CaptchaFetcher {
    private static final String CAPTCHA_URL = "https://kyfw.12306.cn/passport/captcha/captcha-image?module=login&randCode=..."; // 替换为真实URL
    public static InputStream fetchCaptchaImage() throws IOException {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(CAPTCHA_URL)
                .build();
        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("Unexpected code " + response);
            }
            return response.body().byteStream();
        }
    }
}

步骤 2:图像预处理(核心难点)

这是最关键的一步,原始图片是这样的(示意图):

Java如何实现12306验证码识别?-图3
(图片来源网络,侵删)

我们的目标是把它变成这样(示意图):

预处理通常包括以下技术:

  1. 灰度化:将彩色图片转为灰度图,减少计算量。
  2. 二值化:设定一个阈值,将图片转为纯黑和纯白,可以大大简化图像。
  3. 降噪
    • 中值滤波:可以有效去除孤立的噪点。
    • 形态学操作:如腐蚀和膨胀,可以去除小的噪点或将断开的线条连接起来。
  4. 颜色分离:对于彩色图标,可以根据其主色调(如红色、蓝色)将它们从背景中分离出来。

使用OpenCV进行预处理的代码示例:

import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import java.io.InputStream;
public class ImagePreprocessor {
    // 静态代码块加载OpenCV库
    static {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    }
    public static Mat preprocess(InputStream imageStream) {
        // 1. 读取图片
        Mat src = Imgcodecs.imread("path/to/your/captcha.png"); // 或者从InputStream读取
        // 2. 转为灰度图
        Mat gray = new Mat();
        Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
        // 3. 自适应二值化效果通常比固定阈值更好
        Mat binary = new Mat();
        Imgproc.adaptiveThreshold(gray, binary, 255, Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C, Imgproc.THRESH_BINARY_INV, 11, 2);
        // 4. 中值滤波去噪
        Mat denoised = new Mat();
        Imgproc.medianBlur(binary, denoised, 3);
        // 5. 形态学操作(可选)
        Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3));
        Mat morph = new Mat();
        Imgproc.morphologyEx(denoised, morph, Imgproc.MORPH_CLOSE, kernel);
        return morph; // 返回处理后的Mat对象
    }
}

注意:OpenCV的Java版本需要你手动下载opencv-java.jaropencv-native库文件,并配置到项目中。

步骤 3:目标检测与识别

现在我们有了一个相对干净的图片,需要识别出每个小图标里是什么,并定位它的位置。

  1. 定位图标位置:通过轮廓检测找到每个小方框的位置。
  2. 裁剪图标:根据轮廓位置,从预处理后的图片中裁剪出每个单独的图标。
  3. 识别图标内容:使用Tesseract OCR识别每个裁剪后图标的内容。

使用Tess4J进行识别:

import net.sourceforge.tess4j.Tesseract;
import net.sourceforge.tess4j.TesseractException;
import java.io.File;
public class CaptchaRecognizer {
    private Tesseract tesseract;
    public CaptchaRecognizer() {
        tesseract = new Tesseract();
        // 设置训练数据文件路径,需要下载chi_sim.traineddata(中文简体)
        tesseract.setDatapath("path/to/tessdata"); 
        // 12306的字体可能特殊,可以尝试设置
        tesseract.setLanguage("chi_sim");
        tesseract.setPageSegMode(Tesseract.PSM.SINGLE_BLOCK); // 设置页面分割模式
    }
    public String recognizeIcon(File imageFile) throws TesseractException {
        return tesseract.doOCR(imageFile);
    }
}

整合定位与识别:

// 在ImagePreprocessor或主逻辑中
Mat processedImage = ImagePreprocessor.preprocess(...);
List<MatOfPoint> contours = new ArrayList<>();
Mat hierarchy = new Mat();
Imgproc.findContours(processedImage, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
// 假设我们已经知道每个图标的大小和位置(或者通过contours的边界框推断)
List<Rect> iconRects = getIconRects(contours); // 需要自己实现这个逻辑
Tesseract tesseract = new Tesseract();
tesseract.setDatapath("path/to/tessdata");
List<String> results = new ArrayList<>();
for (Rect rect : iconRects) {
    // 裁剪图标
    Mat icon = new Mat(processedImage, rect);
    // 将Mat保存为临时文件供Tesseract使用
    File tempFile = File.createTempFile("icon", ".png");
    Imgcodecs.imwrite(tempFile.getAbsolutePath(), icon);
    // 识别
    String result = tesseract.doOCR(tempFile);
    results.add(result.trim());
    tempFile.delete();
}
// results 列表包含了每个图标的识别文本
System.out.println("识别结果: " + results);

步骤 4:生成点击坐标

假设识别结果是 ["汽车", "自行车", "汽车", "卡车"],而题目要求是“点击所有包含‘汽车’的图片”。

  1. 我们需要记录每个Rect的位置。
  2. 遍历results列表,如果识别结果包含“汽车”,就记录下对应的Rect的中心坐标。
List<Point> clickPoints = new ArrayList();
String target = "汽车";
for (int i = 0; i < results.size(); i++) {
    if (results.get(i).contains(target)) {
        Rect rect = iconRects.get(i);
        Point center = new Point(rect.x + rect.width / 2.0, rect.y + rect.height / 2.0);
        clickPoints.add(center);
        System.out.println("找到目标,坐标: " + center);
    }
}

步骤 5:模拟点击

使用Selenium控制浏览器,获取验证码图片在页面上的位置,然后计算出最终的屏幕点击坐标,并进行点击。

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.interactions.Actions;
import java.awt.*;
import java.util.List;
public class SeleniumClicker {
    public static void clickCaptcha(WebDriver driver, List<Point> clickPoints) throws AWTException {
        // 1. 定位到验证码图片元素
        WebElement captchaImage = driver.findElement(By.id("captcha_image_id")); // 替换为真实的ID
        // 2. 获取图片在屏幕上的坐标和尺寸
        Point imageLocation = captchaImage.getLocation();
        Dimension imageSize = captchaImage.getSize();
        // 3. 计算最终的点击坐标
        Actions actions = new Actions(driver);
        Robot robot = new Robot();
        for (Point p : clickPoints) {
            int screenX = imageLocation.x + (int) p.x;
            int screenY = imageLocation.y + (int) p.y;
            System.out.println("将在屏幕坐标 (" + screenX + ", " + screenY + ") 处点击");
            // 使用Robot直接移动鼠标并点击,或者使用Selenium的Actions
            // robot.mouseMove(screenX, screenY);
            // robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
            // robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
            // 更推荐的方式
            actions.moveByOffset((int)p.x, (int)p.y).click().build().perform();
            // 注意:Actions的坐标是相对于元素的,所以需要先重置偏移
            actions.moveByOffset(-(int)p.x, -(int)p.y).build().perform();
        }
    }
}

第四部分:总结与重要提醒

  1. 成功率不是100%:由于验证码的复杂性和动态变化,任何自动化识别方案的成功率都无法达到100%,预处理和OCR的准确性是关键瓶颈。
  2. 12306的反爬:12306有非常严格的反爬机制,频繁请求验证码IP会被封禁,在识别之间必须加入随机延时
  3. 法律与道德:自动化工具12306账号登录可能违反其服务条款,请确保你有权对目标账号进行操作,并仅用于学习和研究目的。
  4. 动态性:当12306升级验证码后,这套流程很可能失效,你需要重新分析新的验证码类型,并调整预处理和识别策略,如果升级为纯图形识别(不包含文字),Tesseract就完全无效了,可能需要更高级的深度学习模型(如CNN)进行图像分类。
  5. 替代方案:对于个人使用,12306官方提供了图形验证码的“语音播报”功能,这是一个更简单、更合规的绕过方式。

这个项目是一个极好的综合练习,涵盖了网络请求、图像处理、OCR和Web自动化等多个领域,祝你学习顺利!

分享:
扫描分享到社交APP
上一篇
下一篇