Сообщить об ошибке.

Генерация своей капчи на сайте Flask в Python

Собственная captcha на сайт в Python

CAPTCHA используется любым веб-сайтом, который хочет ограничить использование ботами. В репозиториях Python есть модуль captcha, который может генерировать капчи, а встроить его во Flask не составит труда. Так же есть много других вариантов.

Возникает справедливый вопрос: - зачем изобретать велосипед? Есть несколько вариантов ответов:

  • В связи с развитием машинного зрения, картинки модуля captcha легко распознаются. Да и другие открытые технологии тоже.
  • Меньше зависимостей от сторонних модулей (ведь модуль captcha тащит за собой много дополнительных модулей).
  • Генерируя свою картинку капчи, вы будете знать как она работает и в случае чего сможете добавить больше шума или что-то изменить.
  • Боты натыкаясь на что-то неизвестное начинают вести себя неадекватно - кликать на что попало. А если форма передается на сервер еще и по AJAX, то вообще сходят с ума.

Для тестового приложения с пользовательской капчей создадим следующую структуру:

/test_app
    test.py
    /templates
        capcha.html

Создадим нужные директории и пустые файлы командами bash:

# создаем директории
$ mkdir -p test_app/templates
# создаем пустые файлы
$ touch test_app/test.py test_app/templates/capcha.html

Готово. После этого можно открыть созданные пустые файлы и скопировать туда соответствующий код, представленный ниже. Весь представленный код хорошо прокомментирован, так что разобраться будет несложно.

Пользовательская капча генерируется модулем Pillow, следовательно необходимо установить модуль Pillow в виртуальное окружение, в котором установлен Flask.

Как запускать? Активируем виртуальное окружение с установленными Flask и Pillow. Далее, если ранее директорий test_app создавался в корне, то просто нужно перейти в папку test_app и запустить приложение.

# активируем виртуальное окружение 
$ source .venv/bin/activate
# переходим в папку с приложением
$ cd test_app
# экспортируем приложение
$ export FLASK_APP=test
# запускаем приложение
$ flask run
#  * Serving Flask app 'test'
#  * Debug mode: off
#  * Running on http://127.0.0.1:5000
# Press CTRL+C to quit

Далее переходим по ссылке http://127.0.0.1:5000 и тестируем...

Код приложения для тестирования собственной капчи.

Пользовательскую капчу будем генерировать при помощи стороннего модуля Pillow, следовательно необходимо установить его в виртуальное окружение, в котором установлен Flask.

Весь представленный код хорошо прокомментирован, так что разобраться будет несложно. С подмодулем ImageDraw модуля Pillow, при помощи которого генерируется картинка можно ознакомиться в материале "Подмодуль ImageDraw модуля Pillow в Python"

# файл test_app/test.py
from flask import (Flask, render_template, request, 
                   session, url_for, redirect)
from PIL import Image, ImageDraw, ImageFont
from random import choice, randint
from io import BytesIO
from os import urandom

# объект приложения
app = Flask(__name__)
# создадим ключ для сессий 
app.secret_key = urandom(24)

# главная страница с капчей
@app.route("/", methods=['POST', 'GET'])
def start():
    error = False
    if request.method == "POST":
        # получаем данные формы
        input_captcha = request.values.get('input_captcha')
        # получаем данные сессии
        sess_captcha = session.get('code')
        # сравниваем данные сессии и формы
        if str(input_captcha).lower() == str(sess_captcha).lower():
            # при успехе перенаправляем на 
            # страничку приветствия
            return redirect(url_for('hello'))
        else:
            # при неправильном вводе кода 
            # капчи - показываем надпись
            error = True
    return render_template('capcha.html', error=error)        

# генерация собственной капчи при помощи `Pillow`
@app.route('/captcha.png', methods=['GET'])
def captcha(width=200, height=100):
    # символы для капчи выбираем с таким расчетом, 
    # что бы посетители их не спутали с похожими
    # например букву `l` и цифру `1` легко спутать
    # генерация кода капчи из 5 символов
    code = ''.join([choice('QERTYUPLKJHGFDSAZXCVBN23456789') for i in range(5)])
    # сгенерированный код пишем в сессию
    session['code'] = code

    # создаем подложку    
    img = Image.new('RGB', (width,height), (255,255,255))
    # получаем контекст рисования
    draw = ImageDraw.Draw(img)

    # Подключаем растровый шрифт (укажите свой)
    font = ImageFont.truetype('/usr/share/fonts/truetype/freefont/FreeSans.ttf', 
                              size=50)
    # начальное положение символов кода
    x=0; y=12;
    # наносим код капчи
    for let in code:
        if x == 0: x = 5
        else: x = x + width/5
        # случайное положение по высоте
        y = randint(3,55)
        # наносим символ
        draw.text((x,y), let, font=font, fill=(randint(0,200), randint(0,200), randint(0,200), 128))

    # создаем шум капчи (в данном случае черточки)
    # можно создать шум точками (кому как нравиться)
    for i in range(40):
        draw.line([(randint(0,width),randint(0,height)),
                   (randint(0,width),randint(0,height))], 
                  randint(0, 200), 2, 128)

    # создаем объект в буфере
    f = BytesIO()
    # сохраняем капчу в буфер
    img.save(f, "PNG")
    # возвращаем капчу как байтовый объект 
    return  f.getvalue()

# Станица откроется при 
# правильном вводе капчи
@app.route('/ok')
def hello():
    return '<h1 style="text-align:center;margin-top:150px;">Привет!</h1>'


if __name__ == "__main__":
    app.run(debug=True)

Форма отправки капчи на проверку:

<!-- файл test_app/templates/capcha.html -->
<html>
    <head>
        <meta charset="utf-8">
        <style>
            html,body {height:100%;width:100%;margin:0;}
            body {display:flex;}
            form {margin:auto;}
            #error{color:red;}
            #captcha{width:200px;}
            input{width:200px;font-size:150%;margin-top:15px;}
        </style>
    </head>
    <body>
        <form id="form" method="POST">
            <h3 id="h3">Кликните по картинке для смены изображения.</h3>
            <!-- При вводе неправильного кода будет появляться надпись -->
            {% if error %}
                <h3 id="error">НЕПРАВИЛЬНЫЙ ВВОД!</h3>
            {% endif %}
            <!-- сгенерированную капчу подключим как простую картинку -->
            <!-- так как маршрут определен как route('/captcha.png'...) -->
            <div id="captcha"><img src="/captcha.png"></div>
            <!-- поле ввода кода с картинки -->
            <input name="input_captcha" type="text" placeholder="Буквы и цифры">
            <br>
            <input type="submit" value="Проверить">
        </form>
        <!-- сделаем так, чтобы рисунок капчи обновлялся при клике на нее. -->
        <!-- Для этого подключим jquery по ссылке CDN -->
        <script src="https://code.jquery.com/jquery-3.6.3.min.js"
            integrity="sha256-pvPw+upLPUjgMXY0G+8O0xUf+/Im1MZjXxxgOcBQBXU="
            crossorigin="anonymous"></script>
        <!-- обновлять картинку будем AJAX-ом -->
        <script>
            function reCaptcha() {
                $.ajax({
                    type: 'GET',
                    url: '/captcha.png',
                    success: function(data){
                        $('#captcha').html('<img src="/captcha.png">');
                    }
                });
            }
            $(document).ready(function() {
                // По клику на картинке обновляем последнюю
                $('#captcha').click(function(){
                    reCaptcha(); 
                });
            });
        </script>
    </body>
</html>