Bypassing Slide Captcha with Puppeteer: A Step‑by‑Step Guide
This article explains how to use the Node.js‑based Puppeteer library to automate the solving of slide‑captcha challenges by opening the target page, extracting canvas data, calculating the missing piece position, simulating a human‑like drag motion, and verifying the result, while comparing Puppeteer with Selenium/WebDriver.
The author, Liu Guanyu, a senior front‑end engineer at 360 Qiwuchuan, introduces a practical method for cracking slide captchas using the popular Puppeteer automation tool, contrasting it with the WebDriver standard and Selenium framework.
Puppeteer, a Node.js package maintained by Google, communicates directly with Chrome via the DevTools Protocol, offering faster and more straightforward APIs than Selenium, which implements the language‑agnostic WebDriver protocol.
The solution follows three main steps: (1) identify the target position of the captcha gap, (2) perform a drag operation that mimics human behavior, and (3) verify the outcome and handle failures.
Step 1 – Open the page and prepare the environment
const URL = "http://www.geetest.com/type/";
let browser;
const init = async () => {
if (browser) { return; }
browser = await puppeteer.launch({ "headless": false, "args": ["--start-fullscreen"] });
};
const configPage = async (page) => {
await page.setViewport({ width: 1280, height: 1040 });
};
const toRightPage = async (page) => {
await page.goto(URL);
await page.evaluate(_ => {
let rect = document.querySelector(".products-content").getBoundingClientRect();
window.scrollTo(0, rect.top - 30);
});
await page.waitFor(1000);
await page.click(".products-content li:nth-child(2)");
};
(async () => {
await configPage(page);
await toRightPage(page);
})();The script launches a non‑headless Chrome, navigates to the captcha page, and scrolls to the relevant element.
Step 2 – Extract canvas data to locate the missing piece
The captcha consists of two canvas elements: .geetest_canvas_bg (the visible background with a gap) and .geetest_canvas_fullbg (the full background). By injecting a helper script that reads pixel data via getImageData , the code obtains the color matrices of both canvases.
const injectedScript = `
const getCanvasValue = (selector) => {
let canvas = document.querySelector(selector);
let ctx = canvas.getContext('2d');
let [width, height] = [canvas.width, canvas.height];
let rets = [...Array(height)].map(_ => [...Array(width)].map(_ => 0));
for (let i = 0; i < height; ++i) {
for (let j = 0; j < width; ++j) {
rets[i][j] = Object.values(ctx.getImageData(j,i,1,1).data);
}
}
return rets;
}
`;
await page.addScriptTag({ content: injectedScript });
const full = await page.evaluate(() => getCanvasValue('.geetest_canvas_fullbg'));
const bg = await page.evaluate(() => getCanvasValue('.geetest_canvas_bg'));After obtaining the two pixel arrays, the script compares them pixel by pixel, using a threshold (e.g., 70) to tolerate minor color differences, and records the coordinates where the arrays diverge. The left‑most differing x‑coordinate is the target drag distance.
const THRESHOLD = 70;
const _equals = (a, b) => {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; ++i) {
if (Math.abs(a[i] - b[i]) > THRESHOLD) return false;
}
return true;
};
const _differentSet = (a1, a2) => {
let rets = [];
a1.forEach((el, y) => {
el.forEach((el2, x) => {
if (!_equals(el2, a2[y][x])) {
rets.push({ x, y, v: el2, v2: a2[y][x] });
}
});
});
return rets;
};
const diff = _differentSet(full, bg);
const target = diff.reduce((min, cur) => cur.x < min.x ? cur : min, diff[0]);The starting point is always the slider button .geetest_slider_button located at the left edge.
Step 3 – Simulate a human‑like drag
Puppeteer does not provide a direct drag API, but it allows low‑level mouse control. The script obtains the slider’s bounding box, moves the mouse to the button, presses down, and then moves the mouse along a trajectory generated by a generator that follows a uniformly accelerated motion (S = ½ a t²).
let _moveTrace = function*(dis) {
let trace = [];
let t0 = 0.2, curr = 0, step = 0, a = 0.8;
while (curr < dis) {
let t = t0 * ++step;
curr = parseFloat(((0.5 * a * t * t)).toFixed(2));
trace.push(curr);
}
for (let i = 0; i < trace.length; ++i) {
yield trace[i];
}
};
let slider = await page.waitFor('.geetest_slider_button');
let sliderInfo = await slider.boundingBox();
let m = page.mouse;
await m.move(sliderInfo.x + 5, sliderInfo.y + 6);
await m.down();
let gen = _moveTrace(target.x);
for (let offset of gen) {
await m.move(sliderInfo.x + offset, sliderInfo.y + 6);
}
await m.up();After the drag, the script checks whether the success animation element .geetest_success_animate appears to determine if the captcha was solved. If not, it can retry or refresh the page.
let isSuccess = await page.evaluate(() => !!document.querySelector('.geetest_success_animate'));
if (!isSuccess) {
// retry logic here
}Finally, the author saves the script and runs it, observing the automated browser solving the slide captcha successfully.
Overall, the guide demonstrates a complete pipeline—from page loading, canvas data extraction, target calculation, realistic drag simulation, to result verification—showcasing how Puppeteer can be leveraged for web‑automation tasks that involve visual challenges.
360 Tech Engineering
Official tech channel of 360, building the most professional technology aggregation platform for the brand.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.