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

我们将分步进行讲解,从原理到实践。
第一部分:核心原理与挑战
12306验证码的主要目的是区分“人”和“机器”,其设计思路就是让机器难以识别,识别它的核心挑战在于:
- 高度干扰元素:背景中充满了大量颜色、形状各异的噪点、线条,严重干扰了主体图形的识别。
- 字体变形与扭曲:验证码文字(或图标)会进行随机的旋转、拉伸、扭曲,使得标准OCR难以识别。
- 多模态识别:早期的12306是文字识别,后来升级为“点选式”或“滑动式”,这不再是简单的图像识别,而是需要图像理解和坐标定位的能力,我们这里主要讨论更具挑战性的点选式验证码。
- 动态更新:12306会定期更换验证码的生成算法,以对抗自动化识别。
点选式验证码的识别流程可以分解为以下几个步骤:
- 获取验证码图片:从12306的登录接口请求验证码图片。
- 图像预处理:对原始图片进行去噪、二值化、颜色分离等操作,将干扰元素尽可能去除,突出目标物体(文字/图标)。
- 目标检测与定位:识别出图片中所有需要点击的物体(“所有包含‘汽车’的图标”),并确定它们在图片中的中心坐标。
- 生成点击序列:根据题目要求(请点击所有包含‘汽车’的图片”),将定位到的目标坐标按一定顺序排列。
- 模拟点击:使用自动化工具(如Selenium)模拟鼠标点击这些坐标,完成验证。
第二部分:技术选型
我们将使用一个强大的开源图像处理库来处理这些步骤:Tesseract OCR,但请注意,直接使用Tesseract对原始12306验证码进行文字识别,成功率极低,我们需要先用图像预处理技术“净化”图片,再让Tesseract去识别净化后的图片内容。

主要工具/库:
- Java HTTP客户端:用于从12306服务器下载验证码图片。
OkHttp或Apache HttpClient(推荐OkHttp,更现代)
- 图像处理库:用于图像预处理。
OpenCV:功能最强大,是工业级标准,但需要额外配置。Thumbnailator:轻量级,适合简单操作。Java AWT/Swing:Java自带,可以实现一些基础图像操作。- Tesseract OCR for Java (Tess4J):这是我们将要重点使用的,它是对Tesseract OCR引擎的Java封装。
- 浏览器自动化工具:用于模拟登录和点击。
Selenium:最流行的Web自动化测试框架,可以控制浏览器执行各种操作。
第三部分:实践步骤(以点选式验证码为例)
假设我们要识别一个要求“请点击所有包含‘汽车’的图片”的点选验证码。
步骤 1:获取验证码图片
你需要分析12306的登录请求,找到获取验证码图片的API,这个API通常会返回一个包含图片数据的流,以及一个用于后续提交的answer或randCode参数。
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:图像预处理(核心难点)
这是最关键的一步,原始图片是这样的(示意图):

我们的目标是把它变成这样(示意图):
预处理通常包括以下技术:
- 灰度化:将彩色图片转为灰度图,减少计算量。
- 二值化:设定一个阈值,将图片转为纯黑和纯白,可以大大简化图像。
- 降噪:
- 中值滤波:可以有效去除孤立的噪点。
- 形态学操作:如腐蚀和膨胀,可以去除小的噪点或将断开的线条连接起来。
- 颜色分离:对于彩色图标,可以根据其主色调(如红色、蓝色)将它们从背景中分离出来。
使用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.jar和opencv-native库文件,并配置到项目中。
步骤 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:生成点击坐标
假设识别结果是 ["汽车", "自行车", "汽车", "卡车"],而题目要求是“点击所有包含‘汽车’的图片”。
- 我们需要记录每个
Rect的位置。 - 遍历
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();
}
}
}
第四部分:总结与重要提醒
- 成功率不是100%:由于验证码的复杂性和动态变化,任何自动化识别方案的成功率都无法达到100%,预处理和OCR的准确性是关键瓶颈。
- 12306的反爬:12306有非常严格的反爬机制,频繁请求验证码IP会被封禁,在识别之间必须加入随机延时。
- 法律与道德:自动化工具12306账号登录可能违反其服务条款,请确保你有权对目标账号进行操作,并仅用于学习和研究目的。
- 动态性:当12306升级验证码后,这套流程很可能失效,你需要重新分析新的验证码类型,并调整预处理和识别策略,如果升级为纯图形识别(不包含文字),Tesseract就完全无效了,可能需要更高级的深度学习模型(如CNN)进行图像分类。
- 替代方案:对于个人使用,12306官方提供了图形验证码的“语音播报”功能,这是一个更简单、更合规的绕过方式。
这个项目是一个极好的综合练习,涵盖了网络请求、图像处理、OCR和Web自动化等多个领域,祝你学习顺利!
