How to Crack Image Click Captchas with Python: A Step‑by‑Step Guide

This article explains a complete Python workflow for solving image‑click captchas by retrieving the base64 image, binarizing it, detecting character contours, generating candidate character images, matching them, and finally returning the click coordinates needed for verification.

Python Crawling & Data Mining
Python Crawling & Data Mining
Python Crawling & Data Mining
How to Crack Image Click Captchas with Python: A Step‑by‑Step Guide

Basic Idea

The method extracts the captcha image (provided as a base64 string), binarizes it, detects character contours, extracts each character, generates candidate character images, compares them, and returns the click coordinates required to solve the image‑click captcha.

Implementation Steps

Get the image.

Binarize the image.

Detect contours.

Recognize characters within contours.

Generate candidate character images.

Match recognized characters to candidates.

Return the click points.

Code

Import dependencies

import base64
import json
import cv2
import numpy as np
import requests
from PIL import Image, ImageDraw, ImageFont

Retrieve image data and decode base64

def getData(capsession: requests.Session):
    resp = capsession.post("captcha_url")
    return resp.json()["repData"]

def getImageFromBase64(b64):
    buffer = base64.b64decode(b64)
    nparr = np.frombuffer(buffer, np.uint8)
    image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
    return image

Binarize the image

def normalizeImage(img):
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    _, img = cv2.threshold(img, 1, 255, cv2.THRESH_BINARY)
    img = cv2.bitwise_not(img)
    return img

Find and filter contours

def findContour(img):
    contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    # ... (inner helper find_if_close omitted for brevity) ...
    # Group close contours, filter by aspect ratio and area
    cnt = list(filter(aspectRatio, unified))
    cnt.sort(key=cv2.contourArea, reverse=True)
    return cnt[:4]

def aspectRatio(cnt):
    _,_,w,h = cv2.boundingRect(cnt)
    return (0.6 < float(w)/h < 1.7) and (cv2.contourArea(cnt) > 200.0)

Extract character images from contours

def extractCharContour(img, contour):
    mult = 1.2
    ret = []
    point = []
    for cnt in contour:
        rect = cv2.minAreaRect(cnt)
        box = cv2.boxPoints(rect)
        box = np.int0(box)
        W = rect[1][0]
        H = rect[1][1]
        Xs = [i[0] for i in box]
        Ys = [i[1] for i in box]
        x1, x2 = min(Xs), max(Xs)
        y1, y2 = min(Ys), max(Ys)
        angle = rect[2]
        rotated = False
        if angle < -45:
            angle += 90
            rotated = True
        center = (int((x1 + x2) / 2), int((y1 + y2) / 2))
        size = (int(mult * (x2 - x1)), int(mult * (y2 - y1)))
        try:
            M = cv2.getRotationMatrix2D((size[0] / 2, size[1] / 2), angle, 1.0)
            cropped = cv2.getRectSubPix(img, size, center)
            cropped = cv2.warpAffine(cropped, M, size)
            croppedW = W if not rotated else H
            croppedH = H if not rotated else W
            croppedRotated = cv2.getRectSubPix(cropped, (int(croppedW * mult), int(croppedH * mult)), (size[0] / 2, size[1] / 2))
            im = cv2.resize(croppedRotated, (20, 20))
            kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]], np.float32)
            im = cv2.filter2D(im, -1, kernel=kernel)
            ret.append(im)
            point.append((rect[0][0], rect[0][1]))
        except:
            pass
    return ret, point

Generate candidate character images

def genCharacter(ch, size):
    img = Image.new("L", size, 0)
    font = ImageFont.truetype("simsun.ttc", min(size))
    draw = ImageDraw.Draw(img)
    draw.text((0, 0), ch, font=font, fill=255)
    return np.asarray(img)

Match recognized characters to candidates

def compareCharImage(words, chars, point, word_list):
    scores = []
    for i, word in enumerate(words):
        for j, char in enumerate(chars):
            scores.append(((i, j), cv2.bitwise_xor(char, word).sum()))
    scores.sort(key=lambda x: x[1])
    word_set, char_set, answers = set(), set(), {}
    for score in scores:
        if (score[0][0] not in word_set) and (score[0][1] not in char_set):
            continue
        word_set.add(score[0][0])
        char_set.add(score[0][1])
        answers[word_list[score[0][0]]] = point[score[0][1]]
    return [{"x": int(answers[w][0]), "y": int(answers[w][1])} for w in word_list]

Submit the solved captcha

def checkCaptcha(captchaSession: requests.Session, data, point):
    enc = encrypt(json.dumps(point).replace(" ", ""), data["secretKey"])
    resp = captchaSession.post(
        "captcha_submit_url",
        json={"token": data["token"], "pointJson": enc},
    )
    return resp.json()
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Image ProcessingCaptchaOpenCVWeb Scraping
Python Crawling & Data Mining
Written by

Python Crawling & Data Mining

Life's short, I code in Python. This channel shares Python web crawling, data mining, analysis, processing, visualization, automated testing, DevOps, big data, AI, cloud computing, machine learning tools, resources, news, technical articles, tutorial videos and learning materials. Join us!

0 followers
Reader feedback

How this landed with the community

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.