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.
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, ImageFontRetrieve 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 imageBinarize 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 imgFind 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, pointGenerate 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()Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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!
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.
