Skip to content

易盾滑动拼图验证码识别

依赖环境

toml
[tool.poetry.dependencies]
python = "^3.8"
Pillow = "^9.1.0"
selenium = "^4.1.3"
requests = "^2.27.1"

代码

python
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.by import By
import requests
from detect import get_x_offset
import time
import io

def get_image(url):
	headers = {
		'authority': 'necaptcha.nosdn.127.net',
		'accept': 'image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
		'accept-language': 'en-US,en;q=0.9',
		'referer': 'https://dun.163.com/',
		'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="100", "Microsoft Edge";v="100"',
		'sec-ch-ua-mobile': '?0',
		'sec-ch-ua-platform': '"Windows"',
		'sec-fetch-dest': 'image',
		'sec-fetch-mode': 'no-cors',
		'sec-fetch-site': 'cross-site',
		'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36 Edg/100.0.1185.50'
	}
	return requests.get(url, headers=headers).content

def get_offset(driver):
	a1 = driver.find_element(by=By.CSS_SELECTOR, value="img.yidun_bg-img").get_attribute('src')
	a2 = driver.find_element(by=By.CSS_SELECTOR, value="img.yidun_jigsaw").get_attribute('src')
	offset = get_x_offset(io.BytesIO(get_image(a1)), io.BytesIO(get_image(a2)))
	return offset

def run_it(driver, el_yidun_slider):
	action = ActionChains(driver)
	offset = get_offset(driver)
	# print("find offset", offset)
	action.drag_and_drop_by_offset(el_yidun_slider, offset+10, 0)
	action.perform()

driver = webdriver.Chrome()
driver.get("https://dun.163.com/trial/jigsaw")
el_yidun_slider = driver.find_element(by=By.CSS_SELECTOR, value="div.yidun_slider")
old_x = el_yidun_slider.location['x']
run_it(driver, el_yidun_slider)
for _ in range(5):
	time.sleep(1)
	if el_yidun_slider.location['x'] == old_x:
		run_it(driver, el_yidun_slider)
	else:
		break
python
from PIL import Image
import os
import traceback

COLOR_TRANSPARENT = (0,0,0,0)
COLOR_GREY        = (109,109,105,255)
COLOR_BLACK       = (0,0,0,255)
COLOR_WHITE       = (255,255,255,255)
COLOR_RED         = (255,0,0,255)
COLOR_GREEN       = (0,255,0,255)
COLOR_YELLOW      = (255,255,0,255)

class ImageHelper:

    def __init__(self, im):
        self.im = im
        self.h = self.im.height
        self.w = self.im.width
        self.im_data = self.im.getdata()
        self.pos_data = self._get_posdata()

    def _get_posdata(self):
        i = 0
        posdata = dict()
        for y in range(self.h):
            for x in range(self.w):
                posdata[(x,y)] = self.im_data[i]
                i += 1
        return posdata

    def is_near_color(self, pos, color):
        x, y = pos
        for x,y in [(x,y-1), (x,y+1), (x-1,y), (x+1,y)]:
            if (x,y) in self.pos_data and self.pos_data[(x,y)] == color:
                return True, (x,y)
        return False, None

def show_pos_data(posdata):
    im = Image.new('RGBA', (60, 158), COLOR_GREY)
    for pos,color in posdata.items():
        im.putpixel(pos, color)
    im.show()

def get_image(posdata):
    im = Image.new('RGBA', (60, 158), COLOR_TRANSPARENT)
    for pos,color in posdata.items():
        im.putpixel(pos, color)
    return im

def get_part_outline_posdata(im):
    imh = ImageHelper(im)
    pos_data = dict()
    for pos, color in imh.pos_data.items():
        if imh.pos_data[pos] != COLOR_TRANSPARENT:
            pos_data[pos] = COLOR_BLACK
    return pos_data

def get_bordered_image(im, border_color=COLOR_WHITE, line_color=COLOR_GREEN):
    imh = ImageHelper(im)
    for pos, color in imh.pos_data.items():
        is_near, _ = imh.is_near_color(pos, border_color)
        if is_near and (imh.pos_data[pos] == COLOR_BLACK):
            im.putpixel(pos, line_color)
    return im

def get_line_posdata(im):
    pos_data = get_part_outline_posdata(im)
    im = get_image(pos_data)
    im = get_bordered_image(im, border_color=COLOR_TRANSPARENT, line_color=COLOR_RED)
    im = get_bordered_image(im, border_color=COLOR_RED, line_color=COLOR_GREEN)
    im = get_bordered_image(im, border_color=COLOR_GREEN, line_color=COLOR_YELLOW)
    im = get_bordered_image(im, border_color=COLOR_YELLOW, line_color=COLOR_WHITE)
    # im.save("part-1.png")
    posdata = dict()
    for pos,color in ImageHelper(im).pos_data.items():
        if color == COLOR_WHITE:
            posdata[pos] = color
    return posdata

def grey_img(im, t=195):
    table = list(map(lambda i:0 if i < t else 1, range(256)))
    return im.convert("RGBA").convert("L").point(table, '1').convert("RGBA")

def get_score(img_grey_helper, img_part_line_posdata, offset_x):
    score = 0
    for pos,color in img_part_line_posdata:
        x, y = pos
        new_pos = (x+offset_x,y+1)
        if new_pos in img_grey_helper.pos_data:
            if (img_grey_helper.pos_data[new_pos] == color):
                score += 1
    return score

def get_x_offset(image_name, image_part_name):
    img_grey_helper = ImageHelper(grey_img(Image.open(image_name)))
    img_part_line_posdata = get_line_posdata(Image.open(image_part_name)).items()
    max_score_offset = (0,0)
    for offset in range(320):
        score = get_score(img_grey_helper, img_part_line_posdata, offset)
        if score > max_score_offset[1]:
            max_score_offset = (offset, score)
    return max_score_offset[0]

def merge_save(im1_name, im2_name, c, offset):
    im1 = Image.open(im1_name).convert("RGBA")
    im2 = Image.open(im2_name)
    im3 = im1.copy()
    im3.paste(im2, (offset, 1), im2)
    im3.save(c)

def merge(a, b, c):
    x_offset = get_x_offset(a, b)
    print("Merged", a, "and", b, ",Offset", x_offset, ",Output", c)
    merge_save(a, b, c, x_offset)

def main():
    merge("download.jpg", "download.png", "out.png")

if __name__ == '__main__':
    main()

Released under the MIT License.