Frontend Development 17 min read

Bypassing Juejin Slider Captcha with Puppeteer and Canvas Image Recognition

This article demonstrates how to use Puppeteer and the Canvas API to automate login on Juejin, extract the slider captcha image, apply grayscale and binarization processing to locate the gap, calculate the required drag distance, and simulate human‑like mouse movements with easing functions for successful verification.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Bypassing Juejin Slider Captcha with Puppeteer and Canvas Image Recognition

Most websites use captchas during login to distinguish real users from bots, and the slider captcha has become popular due to its good user experience. This guide explains why the Juejin slider captcha is chosen for cracking and presents a complete solution using the front‑end automation tool puppeteer together with the Canvas API for image recognition.

Why crack the Juejin slider captcha? The captcha image already contains a missing puzzle piece, the original background image is unavailable, and the slider movement records user trajectories, making simple pixel comparison insufficient. Additionally, the author admits a personal curiosity.

Quick start with Puppeteer

Install Puppeteer:

npm i puppeteer

Create a browser instance (non‑headless for debugging):

const browser = await puppeteer.launch({
    headless: false,
    defaultViewport: { width: 1200, height: 800 },
    args: ['--no-sandbox', '--disable-web-security', `--window-size=1600,800`],
    devtools: true,
});

Open a new page (equivalent to opening a new Chrome tab):

const page = await browser.newPage();

Common page operations include page.goto() , page.waitForSelector() , page.click() , page.type() , page.evaluate() , and mouse actions such as page.mouse.move() , page.mouse.down() , page.mouse.up() . Mastering these APIs is enough to start a web‑scraper.

Canvas API basics

Only two methods are needed: drawImage(img, x, y, width, height) to paint the captcha onto a canvas, and getImageData(x, y, width, height) to retrieve pixel data as an ImageData object containing data , height , and width .

Step‑by‑step cracking process

Use Puppeteer to open the login page, fill in username and password, and click the login button.

When the captcha appears, execute image‑processing code inside page.evaluate() to obtain the slide distance.

Apply grayscale conversion and binarization to highlight the gap.

Count column‑wise transitions from white to black to locate the column with the most changes – this column approximates the gap position.

Simulate a human‑like drag using the calculated distance and an easing curve.

Automatic credential filling

// Jump to Juejin login page
await page.goto('https://juejin.cn/login');
await page.waitForSelector('.other-login-box.clickable');
await page.click('.other-login-box.clickable');
await page.waitForSelector('.input-group input[name="loginPhoneOrEmail"]');
await page.type('.input-group input[name="loginPhoneOrEmail"]', '15000000000');
await page.type('.input-group input[name="loginPassword"]', 'codexu666');
await page.click('.panel.btn');

Image processing and distance calculation

const coordinateShift = await page.evaluate(async () => {
  const image = document.querySelector('#captcha-verify-image') as HTMLImageElement;
  const canvas = document.createElement('canvas');
  canvas.width = image.width;
  canvas.height = image.height;
  const ctx = canvas.getContext('2d');
  await new Promise(resolve => { image.onload = () => resolve(null); });
  ctx.drawImage(image, 0, 0, image.width, image.height);
  const imageData = ctx.getImageData(0, 0, image.width, image.height);
  const data: number[][] = [];
  for (let h = 0; h < image.height; h++) {
    data.push([]);
    for (let w = 0; w < image.width; w++) {
      const index = (h * image.width + w) * 4;
      const r = imageData.data[index] * 0.2126;
      const g = imageData.data[index + 1] * 0.7152;
      const b = imageData.data[index + 2] * 0.0722;
      if (r + g + b > 100) {
        data[h].push(1);
      } else {
        data[h].push(0);
      }
    }
  }
  let maxChangeCount = 0;
  let coordinateShift = 0;
  for (let w = 0; w < image.width; w++) {
    let changeCount = 0;
    for (let h = 0; h < image.height; h++) {
      if (data[h][w] == 0 && data[h][w - 1] == 1) {
        changeCount++;
      }
    }
    if (changeCount > maxChangeCount) {
      maxChangeCount = changeCount;
      coordinateShift = w;
    }
  }
  return coordinateShift;
});

The first loop performs grayscale conversion and binarization, turning each pixel into 0 (black) or 1 (white). The second loop scans each column, counts transitions from white to black, and selects the column with the highest count as the gap location.

Human‑like drag simulation

function easeOutBounce(t: number, b: number, c: number, d: number) {
  if ((t /= d) < 1 / 2.75) {
    return c * (7.5625 * t * t) + b;
  } else if (t < 2 / 2.75) {
    return c * (7.5625 * (t -= 1.5 / 2.75) * t + 0.75) + b;
  } else if (t < 2.5 / 2.75) {
    return c * (7.5625 * (t -= 2.25 / 2.75) * t + 0.9375) + b;
  } else {
    return c * (7.5625 * (t -= 2.625 / 2.75) * t + 0.984375) + b;
  }
}
const drag = await page.$('.secsdk-captcha-drag-icon');
const dragBox = await drag.boundingBox();
const dragX = dragBox.x + dragBox.width / 2 + 2;
const dragY = dragBox.y + dragBox.height / 2 + 2;
await page.mouse.move(dragX, dragY);
await page.mouse.down();
await page.waitForTimeout(300);
const totalSteps = 100;
const stepTime = 5;
for (let i = 0; i <= totalSteps; i++) {
  const t = i / totalSteps;
  const easeT = easeOutBounce(t, 0, 1, 1);
  const newX = dragX + coordinateShift * easeT - 5;
  const newY = dragY + Math.random() * 10;
  await page.mouse.move(newX, newY, { steps: 1 });
  await page.waitForTimeout(stepTime);
}
await page.waitForTimeout(800);
await page.mouse.up();

If verification fails, a retry mechanism can be added:

try {
  await page.waitForSelector('.captcha_verify_message-success', { timeout: 1000 });
} catch (error) {
  await page.waitForTimeout(500);
  await this.handleDrag(page);
}

Other possible approaches include manual verification (keeping the browser non‑headless and saving cookies), pixel‑by‑pixel screenshot comparison (high accuracy but slow), and machine‑learning models (most reliable but resource‑intensive).

Conclusion

Slider captchas balance user experience and security; their good UX makes them relatively easy to bypass. Using Puppeteer for front‑end automation offers a simple, Node‑based alternative to Python scrapers, with higher memory usage but sufficient for everyday tasks. Finally, the author reminds readers to use these techniques responsibly.

PuppeteerautomationcanvascaptchaImage RecognitionWeb Scraping
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.