最近在维护公司之前的小红书采集代码,其中详情页采集使用的是web直接采集,由于请求频率蛮高的就有了弹滑块验证的问题,之前靠人手滑,但昨天网站仿佛抽风了一样无限弹滑块验证,于是着手开发自动滑块功能。
滑块验证大概就长这样
在出现这种页面的时候你拿cookie无论怎么访问,都返回不了正确结果,因此需要将这个划开。
第一步肯定是先把图片下载下来,主要是两张图片,背景图和滑块图,就是红框和蓝框部分
然后是要知道滑动距离,大概就是下图图示的长度
那问题来了,怎么拿呢,用的方法是基于opencv中的Imgproc,也就是图像处理。那处理缺口图片的方法就用在小学二年级都学过的 灰度化+二值化,这里用的是阈值二值化。话不多说直接上代码
public static Integer getXiaohongshuDistance(String backgroundPath, String slidePath) {
OpenCV.loadShared();
//处理背景图片
Mat background = Imgcodecs.imread(backgroundPath);
background = resize(background, 400, 200);
Imgcodecs.imwrite("e:/tmp/xiaohongshu_bg_400x200.png", background);
Mat backgroundGrey = new Mat();
//灰度化
Imgproc.cvtColor(background, backgroundGrey, Imgproc.COLOR_RGB2GRAY);
Mat backgroundBit = new Mat();
//阈值二值化
Imgproc.threshold(backgroundGrey, backgroundBit, 175, 255, Imgproc.THRESH_BINARY_INV);
Imgcodecs.imwrite("e:/tmp/xiaohongshu_bg_bit.png", backgroundBit);
//处理滑块图片
Mat slide = Imgcodecs.imread(slidePath, IMREAD_UNCHANGED);
slide = resize(slide, 60, 200);
Imgcodecs.imwrite("e:/tmp/xiaohongshu_bg_60x200.png", slide);
Rect opaqueArea = getOpaqueArea(slide);
Mat crop = crop(slide, opaqueArea);
//将所有不透明的全部变白
fillWhiteBackgroundColor(crop);
Mat slideGrey = new Mat();
Imgproc.cvtColor(crop, slideGrey, Imgproc.COLOR_RGB2GRAY);
Mat slideBit = new Mat();
Imgproc.threshold(slideGrey, slideBit, 254, 255, Imgproc.THRESH_BINARY);
Imgcodecs.imwrite("e:/tmp/xiaohongshu_slide_bit.png", slideBit);
//匹配模板
Mat matchResult = new Mat();
Imgproc.matchTemplate(backgroundBit, slideBit, matchResult, Imgproc.TM_CCOEFF_NORMED);
Rect rect = new Rect(0, opaqueArea.y - 3, matchResult.width(), 7);
matchResult = crop(matchResult, rect);
Core.MinMaxLocResult minMaxLocResult = Core.minMaxLoc(matchResult);
Double x = minMaxLocResult.maxLoc.x;
return x.intValue() - opaqueArea.x;
}
public static Mat resize(Mat img, Integer width, Integer height) {
Mat result = new Mat();
Imgproc.resize(img, result, new Size(width, height));
return result;
}
public static Rect getOpaqueArea(Mat slide) {
Set<Integer> xProjection = new HashSet<>();
Set<Integer> yProjection = new HashSet<>();
for (int i = 0; i < slide.height(); i++) {
for (int j = 0; j < slide.width(); j++) {
Double alpha = slide.get(i, j)[3];
if (alpha >= 255.0) {
xProjection.add(j);
yProjection.add(i);
}
}
}
Integer minX = Collections.min(xProjection);
Integer maxX = Collections.max(xProjection);
Integer minY = Collections.min(yProjection);
Integer maxY = Collections.max(yProjection);
Rect rect = new Rect(minX, minY, maxX - minX + 1, maxY - minY + 1);
return rect;
}
public static Mat crop(Mat slide, Rect rect) {
Mat crop = new Mat(slide, rect);
Mat result = new Mat();
crop.copyTo(result);
return result;
}
public static void fillWhiteBackgroundColor(Mat slide) {
for (int i = 0; i < slide.height(); i++) {
for (int j = 0; j < slide.width(); j++) {
Double alpha = slide.get(i, j)[3];
if (alpha < 255.0) {
double[] whitePoint = {255.0, 255.0, 255.0, 255.0};
slide.put(i, j, whitePoint);
}
}
}
}
得到了滑动距离之后怎么做,直接一步到位直接匀速直线运动全划了?那怕不是直接告诉网站“我是个机器人”。肯定要表现得像个人一样,这里直接用抄来的一段加速度模型。
public static List<Integer> generateTrackers(Integer distance) {
List<Integer> forwardTrackers = new ArrayList<>();
Double overDistance = distance + 20.0;
Double v = 0.0;
Double t = 0.5;
Double current = 0.0;
Double mid = overDistance * 3 / 5;
while (current < overDistance) {
Double a;
if (current < mid) {
a = 2.0;
} else {
a = -3.0;
}
Double s = v * t + 0.5 * a * t * t;
v = v + a * t;
current += s;
Integer sInt = (int) Math.round(s);
forwardTrackers.add(sInt);
}
Integer sum = 0;
for (Integer s : forwardTrackers) {
sum += s;
}
sum = sum - distance - 16;
List<Integer> backTrackers = new ArrayList<>();
backTrackers.add(-3);
backTrackers.add(-3);
backTrackers.add(-2);
backTrackers.add(-2);
backTrackers.add(-2);
backTrackers.add(-2);
backTrackers.add(-2);
for (int i = 0; i < sum; i++) {
backTrackers.add(-1);
}
forwardTrackers.addAll(backTrackers);
return forwardTrackers;
}
这段代码的目的是生成一个单次滑动距离的滑动列表。
拖动按钮滑动
public static void performSlide(ChromeDriver driver, WebElement dragButton, List<Integer> trackers) {
PointerInput defaultMouse = new PointerInput(MOUSE, "default mouse");
Interaction move = defaultMouse.createPointerMove(Duration.ofMillis(100l), PointerInput.Origin.fromElement(dragButton), 0, 0);
Sequence sequence = new Sequence(move.getSource(), 0);
sequence.addAction(move);
Interaction buttonDown = defaultMouse.createPointerDown(PointerInput.MouseButton.LEFT.asArg());
sequence.addAction(buttonDown);
for (int i = 0; i < trackers.size(); i++) {
Interaction moveByOffset = defaultMouse.createPointerMove(Duration.ofMillis(20l+new Random().nextInt(5)), PointerInput.Origin.pointer(), trackers.get(i), new Random().nextInt(2));
sequence.addAction(moveByOffset);
}
Interaction buttonUp = defaultMouse.createPointerUp(PointerInput.MouseButton.LEFT.asArg());
sequence.addAction(buttonUp);
List<Sequence> sequences = new ArrayList<>();
sequences.add(sequence);
driver.perform(sequences);
}