Обработка изображений и распознавание текста при парсинге в Python

Чтобы распознать текст с картинки на Python, поставьте программу Tesseract с русским языком, библиотеки pytesseract и Pillow и вызовите pytesseract.image_to_string(Image.open("pic.png"), lang="rus+eng") — функция вернёт строку с текстом. На чистых картинках этого хватает. Если фон шумный, неровный или буквы мелкие, сначала подготовьте изображение: переведите в оттенки серого, увеличите, уберите шум и сделайте чёрно-белым через порог. Дальше разберём всё по шагам: установку, первые запуски, подготовку картинок в Pillow и OpenCV, режимы Tesseract, альтернативы и то, как всё это встраивается в парсинг сайтов. Все примеры я запускал сам на Python 3.12-совместимом коде и Tesseract 5.5, вывод в статье — настоящий, не придуманный.

Что такое OCR и зачем он парсеру

OCR (Optical Character Recognition, оптическое распознавание символов) — это перевод картинки с буквами в обычный текст, который можно искать, копировать и сохранять в таблицу. Человек смотрит на фото чека и видит «Итого: 1250». Компьютер видит только сетку цветных точек. OCR-программа ищет в этих точках буквы и собирает из них строку. Обычный парсер (программа, которая собирает данные со страниц сайтов) работает с HTML: находит нужный тег и забирает текст. Но иногда нужные данные лежат не в тексте, а на картинке:
  • цена или телефон нарисованы картинкой, чтобы их не копировали;
  • на сайте выложены сканы документов, прайсов, расписаний;
  • таблица вставлена скриншотом;
  • текст есть на обложке, баннере, инфографике.
В таких случаях HTML-разбор бессилен, и на помощь приходит OCR. Сразу оговорюсь: если картинку сделали специально, чтобы защитить данные от автоматического сбора, — это сигнал остановиться и проверить, можно ли вообще эти данные брать. Об этом есть отдельный раздел ближе к концу.

Инструменты: что понадобится

Для работы нужны четыре вещи. Сначала коротко, потом подробно.
  • Tesseract — сам «движок» распознавания. Это отдельная программа, не библиотека Python. Её ставят в систему.
  • pytesseract — библиотека-обёртка: она запускает Tesseract из Python и возвращает результат строкой или словарём.
  • Pillow — библиотека для работы с картинками: открыть, повернуть, увеличить, сделать серой.
  • OpenCV (модуль cv2) — библиотека компьютерного зрения. Умеет то же, что Pillow, плюс более сильные фильтры и умный порог.
Можно сравнить с переводчиком. Tesseract — это сам переводчик, который знает язык. pytesseract — телефон, по которому вы ему звоните. Pillow и OpenCV — это очки и лампа: помогают переводчику разглядеть мелкий или смазанный текст.

Установка Tesseract на Windows

Готовый установщик для Windows собирает команда UB Mannheim, ссылка есть на странице проекта. При установке в списке «Additional language data» отметьте Russian — иначе русский текст распознаваться не будет. Если забыли, ничего страшного, языковой файл можно добавить позже. Проверить, что всё на месте, можно в командной строке:
tesseract --version
tesseract --list-langs
У меня после установки ответ был такой:
tesseract v5.5.0.20241111
List of available languages in "C:\Program Files\Tesseract-OCR/tessdata/" (2):
eng
osd
Русского языка в списке нет. Я как раз забыл поставить галочку. Решение — скачать файл rus.traineddata из официального репозитория tessdata_fast (быстрая версия моделей) или tessdata (точнее, но медленнее) и положить его в папку tessdata рядом с eng.traineddata. Если прав администратора нет и в Program Files писать нельзя, сделайте свою папку tessdata, сложите туда eng.traineddata и rus.traineddata и укажите путь в переменной окружения TESSDATA_PREFIX. Именно так я и поступил для примеров ниже.

Установка на Linux и macOS

В Ubuntu и Debian всё ставится одной командой, русский язык — отдельным пакетом:
sudo apt install tesseract-ocr tesseract-ocr-rus
На macOS удобнее всего через менеджер пакетов Homebrew. Пакет tesseract-lang добавляет все языки, включая русский:
brew install tesseract tesseract-lang

Установка библиотек Python

Библиотеки ставим через pip. Для OpenCV на сервере без экрана подойдёт облегчённая сборка opencv-python-headless, на домашнем компьютере — обычная opencv-python.
pip install pytesseract pillow opencv-python-headless numpy
Если Tesseract лежит не в системном пути (типичная история на Windows), подскажите pytesseract, где его искать. Это делается одной строкой в начале скрипта:
import os
import pytesseract

pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"
# Если языковые файлы лежат в своей папке:
os.environ["TESSDATA_PREFIX"] = r"D:\ocr\tessdata"

Первое распознавание: чистая картинка

Начнём с простого. Чтобы не зависеть от чужих файлов, сделаем картинку сами прямо в Pillow: белый фон, чёрный шрифт Arial, две строки по-русски.
from PIL import Image, ImageDraw, ImageFont

font = ImageFont.truetype("C:/Windows/Fonts/arial.ttf", 32)
img = Image.new("RGB", (640, 120), "white")
draw = ImageDraw.Draw(img)
draw.text((20, 15), "Цена курса: 4900 руб.", fill="black", font=font)
draw.text((20, 65), "Запись до 15 октября", fill="black", font=font)
img.save("clean.png")
Теперь распознаём:
import pytesseract
from PIL import Image

text = pytesseract.image_to_string(Image.open("clean.png"), lang="rus+eng")
print(text)
Результат:
Цена курса: 4900 руб.
Запись до 15 октября
Всё верно, до последней точки. Параметр lang="rus+eng" говорит Tesseract: «тут могут быть и русские, и английские слова». Для русских сайтов это лучший выбор по умолчанию: в тексте часто встречаются латинские названия, коды, адреса почты. А что будет, если язык забыть? Тот же файл с lang="eng":
LleHa kypca: 4900 py6.
Sanncb fo 15 oxTAOpa
Tesseract честно пытается прочитать кириллицу латинскими буквами, и получается каша. Цифры при этом распознались правильно — они одинаковые в обоих языках. Это полезно запомнить: если вместо русского текста вы видите набор латиницы, первым делом проверьте параметр lang и наличие rus в tesseract --list-langs.

Тот же результат из командной строки

Tesseract работает и без Python. Это удобно, чтобы быстро проверить картинку, не открывая редактор:
tesseract clean.png stdout -l rus+eng
Слово stdout значит «выведи результат в консоль». Вместо него можно указать имя файла, например result, — тогда текст сохранится в result.txt.

Когда картинка не такая чистая

В жизни текст редко бывает идеальным. Tesseract хорошо читает, когда:
  • шрифт обычный, без завитушек и рукописного стиля;
  • буквы чёткие, без размытия и пятен;
  • строки ровные, не наклонены;
  • текст тёмный на светлом фоне, а фон однородный;
  • буквы достаточно крупные: высота строчной буквы хотя бы 20–30 пикселей.
Чтобы проверить на цифрах, я сделал свою «трудную» картинку. Мелкий шрифт 22 пикселя, серый фон с переходом от тёмного к светлому, полторы тысячи случайных чёрных и белых точек, лёгкое размытие и сохранение в JPEG с качеством 40. Так обычно выглядит телефон, который владелец сайта нарисовал картинкой поверх фона.
import random
from PIL import Image, ImageDraw, ImageFont, ImageFilter

random.seed(1)
w, h = 420, 70
img = Image.new("L", (w, h))
for x in range(w):
    for y in range(h):
        img.putpixel((x, y), 150 + int(80 * x / w))  # фон светлеет слева направо

draw = ImageDraw.Draw(img)
font = ImageFont.truetype("C:/Windows/Fonts/arial.ttf", 22)
draw.text((10, 8), "Телефон: 8 (900) 123-45-67", fill=70, font=font)
draw.text((10, 38), "Адрес: ул. Садовая, 12", fill=70, font=font)

for _ in range(1500):  # шум: случайные точки
    img.putpixel((random.randrange(w), random.randrange(h)), random.choice([0, 255]))

img = img.filter(ImageFilter.GaussianBlur(0.8))
img.convert("RGB").save("noisy.jpg", quality=40)
Распознаём «как есть», без подготовки:
print(repr(pytesseract.image_to_string(Image.open("noisy.jpg"), lang="rus+eng")))
''
Пустая строка. Tesseract не нашёл на картинке ни одного слова. Это частая ситуация, и она не значит, что инструмент плохой. Он просто ждёт на входе другое изображение: контрастное, крупное, без мусора. Наша задача — привести картинку к такому виду.

Подготовка изображения в Pillow

Подготовка (по-английски preprocessing, предобработка) — это несколько простых шагов перед распознаванием. У каждого шага своя цель.
  1. Оттенки серого. Цвет для распознавания букв не нужен, а лишние каналы только мешают. Одна яркость вместо трёх цветов.
  2. Увеличение. Tesseract плохо видит мелкие буквы. Увеличение в 2–3 раза часто даёт больше, чем любой фильтр.
  3. Медианный фильтр. Убирает одиночные точки шума. Каждый пиксель заменяется «средним» из соседей, и случайные точки исчезают, а линии букв остаются.
  4. Порог (по-английски threshold). Всё, что светлее заданного значения, становится белым, остальное — чёрным. Получается чистая чёрно-белая картинка.
Соберём всё в одну функцию:
from PIL import Image, ImageOps, ImageFilter


def prepare(path: str, threshold: int = 140, scale: int = 3) -> Image.Image:
    """Готовит картинку к распознаванию: серый, увеличение, фильтр, порог."""
    img = Image.open(path)
    img = ImageOps.grayscale(img)
    img = img.resize((img.width * scale, img.height * scale), Image.Resampling.LANCZOS)
    img = img.filter(ImageFilter.MedianFilter(3))
    return img.point(lambda p: 255 if p > threshold else 0)


for t in (100, 140, 180):
    text = pytesseract.image_to_string(prepare("noisy.jpg", t), lang="rus+eng")
    print(t, repr(text))
Вывод при трёх разных порогах:
100 '‘Талефси+ < РЕ ty 1.\n\n-\n\nNe\n\n---\n\nPay\n'
140 'Яврефон: 8 (900).123-45. 67\nЗАйрас: ул: Садовая, 12\n'
180 ''
Видно, как сильно всё зависит от порога. При 100 слишком много пикселей стало белыми, и буквы «рассыпались». При 180 наоборот: тёмная часть фона слилась с текстом в чёрное пятно. При 140 уже читаются цифры телефона и адрес почти целиком. Пара букв в словах «Телефон» и «Адрес» перепутана, но номер можно достать.

Подбор порога автоматически

Подбирать порог руками для каждой картинки скучно. Можно поручить это программе. Идея простая: перебрать несколько порогов и выбрать тот, при котором Tesseract сам больше всего уверен в результате. Для этого есть функция image_to_data. Она возвращает не просто строку, а таблицу: каждое найденное слово, его координаты и уверенность от 0 до 100. Чем выше число, тем увереннее Tesseract в слове.
from pytesseract import Output


def mean_confidence(img: Image.Image) -> tuple[int, float]:
    """Возвращает число найденных слов и среднюю уверенность."""
    data = pytesseract.image_to_data(img, lang="rus+eng", output_type=Output.DICT)
    conf = [float(c) for c, w in zip(data["conf"], data["text"]) if w.strip() and float(c) >= 0]
    return len(conf), (sum(conf) / len(conf) if conf else 0.0)


best_t, best_conf = None, -1.0
for t in range(100, 181, 10):
    words, conf = mean_confidence(prepare("noisy.jpg", t))
    print(t, words, round(conf, 1))
    if words and conf > best_conf:
        best_t, best_conf = t, conf

print("Лучший порог:", best_t)
print(pytesseract.image_to_string(prepare("noisy.jpg", best_t), lang="rus+eng"))
Вывод:
100 9 38.1
110 10 51.6
120 8 83.2
130 10 77.2
140 8 60.1
150 10 49.7
160 0 0
170 0 0
180 0 0
Лучший порог: 120
Tenecbou: 8 (999; 123-45-57
Адрес: уп: Садовая. 12
Программа выбрала порог 120. Адрес почти целиком верный. А вот с телефоном вышло хуже, чем при ручном пороге 140: «999» вместо «900» и «57» вместо «67». Вывод честный: средняя уверенность — неплохая подсказка, но не гарантия. Tesseract бывает уверен и в ошибке. Поэтому для важных данных (телефоны, суммы, коды) всегда добавляйте проверку формата. Например, регулярным выражением: в российском номере должно быть 11 цифр, а в цене — только цифры и пробелы. И ещё один вывод из этого примера: на очень зашумлённых картинках идеального результата не будет. Здесь шум был сильнее, чем обычно бывает на сайтах. Если у вас такой же случай, скорее всего, пора смотреть на OpenCV или на нейросетевые движки, о них ниже.

Подготовка изображения в OpenCV

OpenCV (Open Source Computer Vision Library, открытая библиотека компьютерного зрения) — инструмент тяжелее Pillow, но и сильнее. Главное, ради чего её берут для OCR, — умные пороги. Разберём на новой картинке. Она проще прошлой: нет случайных точек, но есть то, что ломает обычный порог, — фон, который плавно меняется от тёмно-серого слева до почти белого справа. Такое бывает на фото экрана, скане с тенью или баннере с градиентом.
from PIL import Image, ImageDraw, ImageFont, ImageFilter

w, h = 520, 90
img = Image.new("L", (w, h))
for x in range(w):
    for y in range(h):
        img.putpixel((x, y), 90 + int(150 * x / w))

draw = ImageDraw.Draw(img)
font = ImageFont.truetype("C:/Windows/Fonts/arial.ttf", 26)
draw.text((12, 10), "Скидка 20% до пятницы", fill=40, font=font)
draw.text((12, 48), "Промокод: AUTUMN2026", fill=40, font=font)
img = img.filter(ImageFilter.GaussianBlur(0.7))
img.convert("RGB").save("gradient.jpg", quality=50)
Попробуем три способа: без подготовки, обычный порог Оцу и адаптивный порог.
import cv2

img = cv2.imread("gradient.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 1. в оттенки серого

# Без подготовки
print("Как есть:", repr(pytesseract.image_to_string(gray, lang="rus+eng")))

# 2. Порог Оцу: один порог на всю картинку, подбирается автоматически
t, bw = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
print("Оцу, порог", t, ":", repr(pytesseract.image_to_string(bw, lang="rus+eng")))

# 3. Медианный фильтр, увеличение и адаптивный порог
gray = cv2.medianBlur(gray, 3)
gray = cv2.resize(gray, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC)
bw = cv2.adaptiveThreshold(
    gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 31, 15
)
cv2.imwrite("gradient_clean.png", bw)
print("Адаптивный:", repr(pytesseract.image_to_string(bw, lang="rus+eng")))
Вывод:
Как есть: ''
Оцу, порог 155.0 : ''
Адаптивный: 'Скидка 20% до пятницы\nПромокод: AUTUMN2026\n'
Без подготовки — ничего. С порогом Оцу (это метод, который сам находит одно «среднее» значение яркости между текстом и фоном) — тоже ничего. И понятно почему: один порог на всю картинку не подходит, если слева фон тёмный, а справа светлый. Где-то фон уйдёт в чёрное, где-то буквы станут белыми. Адаптивный порог работает иначе. Он считает свой порог для каждого маленького участка картинки, сравнивая пиксель с соседями. Тёмная буква на тёмном фоне всё равно темнее своих соседей — и остаётся чёрной. Результат: обе строки распознаны без единой ошибки, включая знак процента и латиницу в промокоде. Два числа в adaptiveThreshold стоит понимать:
  • 31 — размер участка в пикселях (нечётное число). Он должен быть больше толщины букв. Для мелкого текста берите 11–21, для крупного — 31–51.
  • 15 — насколько пиксель должен быть темнее соседей, чтобы стать чёрным. Больше число — меньше шума, но тонкие буквы могут пропасть.
Полезно сохранять промежуточную картинку через cv2.imwrite и смотреть на неё глазами. Если вы сами не можете прочитать текст на чёрно-белом варианте, Tesseract тоже не сможет. На компьютере с экраном вместо сохранения можно открыть окно: cv2.imshow("bw", bw) и cv2.waitKey(0). В сборке headless этих функций нет. Что из Pillow и OpenCV выбрать? Для простых случаев (серый, увеличить, порог) хватает Pillow: она легче и проще. Если фон неровный, есть тени, нужен поворот или поиск области с текстом — берите OpenCV. Их можно и сочетать: pytesseract принимает и картинки Pillow, и массивы NumPy из OpenCV.

Режимы разметки страницы: параметр —psm

Прежде чем читать буквы, Tesseract пытается понять устройство страницы: где колонки, где абзацы, где строки. Эта часть называется сегментацией (разбиением на части). Обычно она работает сама, но иногда мешает: на маленькой картинке с одной строкой Tesseract может долго искать колонки, которых нет, и ошибиться. Режим сегментации задаётся параметром --psm (page segmentation mode, режим разбиения страницы). Самые полезные значения:
Значение Что значит Когда брать
3 Автоматический разбор страницы (по умолчанию) Обычные документы, сканы
4 Одна колонка текста разной высоты Чеки, списки
6 Один ровный блок текста Таблицы, абзацы на скриншоте
7 Одна строка Цена, телефон, код
8 Одно слово Отдельные ярлыки, номера
11 Разрозненный текст без порядка Баннеры, обложки, инфографика
Задаётся режим через параметр config:
text = pytesseract.image_to_string(img, lang="rus+eng", config="--psm 7")
В моих простых тестах с одной строкой «AB-7741» и словом «7741» режимы 3, 7 и 8 дали одинаковый верный результат — на чистой картинке Tesseract разобрался и без подсказки. Разница появляется на сложных: на обложках книг, о которых ниже, режим 11 вытащил заметно больше текста, чем режим по умолчанию. Правило простое: начинайте с режима по умолчанию, а если результат пустой или перепутан порядок строк — пробуйте 6, 7 или 11. Ещё одна полезная настройка — ограничить набор символов. Если на картинке точно только цифры, скажите об этом:
config = "--psm 7 -c tessedit_char_whitelist=0123456789"
digits = pytesseract.image_to_string(img, config=config)
Тогда Tesseract не спутает «0» с буквой «О», а «1» — с «l».

Таблица на скриншоте: текст и координаты

Частая задача парсера — достать данные из таблицы, вставленной картинкой. Сделаем такую таблицу в Pillow: товары и цены, разделительные линии, как на скриншоте прайса.
from PIL import Image, ImageDraw, ImageFont

font = ImageFont.truetype("C:/Windows/Fonts/arial.ttf", 26)
rows = [("Товар", "Цена"), ("Клавиатура", "1990"), ("Мышь", "890"), ("Монитор", "15490")]
img = Image.new("RGB", (520, 170), "white")
draw = ImageDraw.Draw(img)
for i, (name, price) in enumerate(rows):
    y = 10 + i * 38
    draw.text((15, y), name, fill="black", font=font)
    draw.text((330, y), price, fill="black", font=font)
    draw.line((5, y + 34, 515, y + 34), fill="gray")
draw.line((310, 5, 310, 160), fill="gray")
img.save("table.png")

print(pytesseract.image_to_string(Image.open("table.png"), lang="rus+eng", config="--psm 6"))
Товар Цена
Клавиатура 1990
Мышь 890
Монитор 15490
Текст распознан полностью. Но строка «Клавиатура 1990» — это ещё не данные. Чтобы разложить её по колонкам, нужны координаты слов. Их даёт уже знакомая image_to_data:
from pytesseract import Output

data = pytesseract.image_to_data(Image.open("table.png"), lang="rus+eng", output_type=Output.DICT)
for word, conf, left, top in zip(data["text"], data["conf"], data["left"], data["top"]):
    if word.strip():
        print(f"{word:<12} уверенность={conf:>5}  x={left:<4} y={top}")
Товар        уверенность=   96  x=15   y=15
Цена         уверенность=   96  x=332  y=15
Клавиатура   уверенность=   96  x=13   y=49
1990         уверенность=   94  x=333  y=53
Мышь         уверенность=   96  x=17   y=91
890          уверенность=   96  x=331  y=91
Монитор      уверенность=   96  x=17   y=129
15490        уверенность=   96  x=333  y=129
Теперь видно: всё, что левее x=310, — название товара, правее — цена. Слова с близким y лежат в одной строке. Сгруппировать их можно так:
from collections import defaultdict

lines: dict[int, dict[str, str]] = defaultdict(dict)
for word, left, top in zip(data["text"], data["left"], data["top"]):
    if not word.strip():
        continue
    row = round(top / 38)            # строки идут с шагом около 38 пикселей
    col = "name" if left < 310 else "price"
    lines[row][col] = (lines[row].get(col, "") + " " + word).strip()

for row in sorted(lines):
    print(lines[row])
Получаем список словарей вида {'name': 'Мышь', 'price': '890'}, который легко сохранить в CSV или базу. Шаг строки и граница колонки у каждой таблицы свои, их проще всего взять из того же вывода image_to_data.

Связь с парсингом: картинки со страниц сайта

Теперь соберём всё вместе с настоящим сбором данных. Для тренировки возьмём books.toscrape.com — учебный сайт-магазин, который специально сделан для практики парсинга. Там можно спокойно отлаживать код, не создавая нагрузку чужому бизнесу.

Скачать картинки через requests и BeautifulSoup

Алгоритм такой: загрузить HTML страницы, найти теги <img>, собрать полные адреса картинок, скачать их и сразу передать в OCR. Сохранять на диск не обязательно — картинку можно открыть прямо из памяти через BytesIO.
from io import BytesIO
from urllib.parse import urljoin

import pytesseract
import requests
from bs4 import BeautifulSoup
from PIL import Image

BASE = "https://books.toscrape.com/"
headers = {"User-Agent": "study-bot/1.0"}

html = requests.get(BASE, timeout=10, headers=headers).text
soup = BeautifulSoup(html, "html.parser")

for tag in soup.select("article.product_pod img")[:3]:
    url = urljoin(BASE, tag["src"])        # относительный путь -> полный адрес
    resp = requests.get(url, timeout=10, headers=headers)
    resp.raise_for_status()
    img = Image.open(BytesIO(resp.content))
    print(tag["alt"], img.size)
    print("  OCR:", repr(pytesseract.image_to_string(img, lang="eng")[:80]))
Вывод:
A Light in the Attic (125, 155)
  OCR: ''
Tipping the Velvet (99, 155)
  OCR: 'AoA\n\nWATERS\n\nTIPPING\nTHE VELVET\n\n'
Soumission (101, 155)
  OCR: ''
Обложки маленькие, около 100×155 пикселей, с рисунками и декоративными шрифтами. Из трёх читается только одна. Применим то, что уже знаем: серый, увеличение в 3 раза и режим --psm 11 для разрозненного текста.
from PIL import ImageOps

img = ImageOps.grayscale(img)
img = img.resize((img.width * 3, img.height * 3), Image.Resampling.LANCZOS)
text = pytesseract.image_to_string(img, lang="eng", config="--psm 11")
Результат по тем же трём обложкам (переносы строк убрал для краткости):
1: A Light | in the* | Attic J | pL | F 6 | deraing bs | Shel Silv verccin
2: ) | Ve | RAH | WATERS | TIPPING | THE: MEEVET | —,
3: FISSION | teses
Стало лучше: на первой обложке появилось название и почти угадывается автор, на второй — фамилия автора и название. Но мусора тоже много, а на третьей вместо «Soumission» вышел обрывок. Вывод для практики: на обложках и баннерах OCR — это подсказка, а не надёжный источник. Если то же название есть в HTML (здесь оно лежит в атрибуте alt), берите его оттуда. OCR нужен там, где текста в HTML действительно нет. Пара правил вежливого скачивания картинок, которые стоит соблюдать всегда:
  • ставьте паузу между запросами (time.sleep(1) и больше), чтобы не нагружать сайт;
  • указывайте понятный User-Agent;
  • не качайте одну и ту же картинку повторно — сохраняйте и проверяйте, есть ли файл;
  • если сайт много — делайте это в несколько потоков аккуратно, с ограничением; про параллельную работу в Python есть отдельная статья.

Распознать текст на скриншоте страницы

Бывает, что текст на странице рисуется скриптом: на холсте <canvas>, в виде SVG-картинки или фоном через CSS. В HTML его нет, и requests его не увидит. Тогда помогает браузер под управлением программы: открываем страницу, делаем скриншот нужного элемента и распознаём. Для тренировки подойдёт quotes.toscrape.com — ещё один учебный сайт той же серии. Вот как сделать скриншот первой цитаты через Playwright (библиотека для управления браузером из Python):
import pytesseract
from PIL import Image
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page(viewport={"width": 1200, "height": 800})
    page.goto("https://quotes.toscrape.com/")
    page.locator("div.quote").first.screenshot(path="quote.png")  # снимок одного элемента
    browser.close()

print(pytesseract.image_to_string(Image.open("quote.png"), lang="eng"))
Перед первым запуском браузер для Playwright нужно скачать командой playwright install chromium. Вывод у меня получился такой (сокращён до первой строки и подписи):
“The world as we have created it is a process of our thinking. ...
by Albert Einstein (about)

Tags: CHD CHENIID CHIN) CD
Основной текст распознан без ошибок, а вот метки-теги внизу превратились в кашу. Причина в оформлении: светлые буквы на цветных «плашках» с закруглёнными краями. Если вам нужны именно такие элементы, снимайте их отдельно и готовьте картинку, как мы делали выше: серый, увеличение, порог. То же самое на Selenium 4. Обратите внимание на синтаксис: в старых статьях встречаются вызовы вида «найти элемент по классу» одной функцией и путь к драйверу в параметрах браузера. В Selenium 4 этих функций больше нет. Элементы ищут через find_element(By..., ...), а драйвер браузера Selenium Manager скачивает сам, указывать путь не нужно.
from io import BytesIO

import pytesseract
from PIL import Image
from selenium import webdriver
from selenium.webdriver.common.by import By

options = webdriver.ChromeOptions()
options.add_argument("--headless=new")      # без окна браузера
driver = webdriver.Chrome(options=options)   # драйвер подберёт Selenium Manager
try:
    driver.get("https://quotes.toscrape.com/")
    quote = driver.find_element(By.CSS_SELECTOR, "div.quote")
    png = quote.screenshot_as_png             # снимок только этого элемента
    print(pytesseract.image_to_string(Image.open(BytesIO(png)), lang="eng"))
finally:
    driver.quit()
Если драйвер всё же нужно указать вручную (например, нет доступа в интернет), это делается через объект Service:
from selenium.webdriver.chrome.service import Service

driver = webdriver.Chrome(service=Service(r"C:\tools\chromedriver.exe"), options=options)
Что выбрать? Playwright быстрее ставится и сам ждёт загрузки элементов. Selenium старше, и под него больше готовых примеров. Для задачи «снять скриншот и распознать» подойдут оба.

Распознавание текста из PDF

Отдельный частый случай — PDF на сайте. Тут важно не спешить с OCR. Сначала проверьте, есть ли в файле текстовый слой: многие PDF созданы из Word, и текст из них достаётся напрямую, без распознавания, быстро и без ошибок. Для этого подходят библиотеки pdfplumber или PyMuPDF. Если текстового слоя нет (документ отсканирован), страницы превращают в картинки и распознают. С PyMuPDF это выглядит так:
import fitz  # пакет PyMuPDF
import pytesseract
from PIL import Image

doc = fitz.open("scan.pdf")
for page in doc:
    text = page.get_text().strip()
    if text:                                   # текстовый слой есть
        print(text)
        continue
    pix = page.get_pixmap(dpi=300)            # 300 точек на дюйм — хорошее качество для OCR
    img = Image.frombytes("RGB", (pix.width, pix.height), pix.samples)
    print(pytesseract.image_to_string(img, lang="rus+eng"))
Число 300 dpi выбрано не случайно: при меньшем разрешении буквы на скане получаются слишком мелкими, а при большем распознавание идёт медленнее без заметной прибавки в качестве. Этот пример я не запускал на реальном скане, поэтому вывод не привожу.

Альтернативы Tesseract

Tesseract — бесплатный и проверенный инструмент, но не единственный. Иногда другие справляются лучше.

EasyOCR

EasyOCR — библиотека на нейросетях (работает на PyTorch). Поддерживает больше 80 языков, включая русский, лучше Tesseract читает текст на фото, на цветном фоне и под углом. Минусы: тяжёлая установка (PyTorch весит больше гигабайта), без видеокарты работает заметно медленнее, при первом запуске качает модели.
import easyocr

reader = easyocr.Reader(["ru", "en"])           # модели скачаются при первом запуске
for box, text, conf in reader.readtext("gradient.jpg"):
    print(text, round(conf, 2))
Функция readtext возвращает для каждого куска текста рамку, сам текст и уверенность от 0 до 1. Этот пример я не запускал: ставить PyTorch ради одного сравнения на рабочую машину не стал. Если у вас картинки «из жизни» и Tesseract с подготовкой не справляется, EasyOCR — первое, что стоит попробовать. Ещё одна похожая библиотека — PaddleOCR, она хорошо работает с таблицами и документами.

Облачные сервисы

Если распознавать нужно много и качественно, а ставить ничего не хочется, есть облачные сервисы распознавания с доступом по API, например Yandex Vision OCR, — они платные и требуют отправки картинок на чужой сервер, что важно, если на изображениях есть личные данные.

Что выбрать

  • Скриншоты, сканы, таблицы, ровный текст — Tesseract плюс подготовка в Pillow или OpenCV.
  • Фото, вывески, текст под углом, сложный фон — EasyOCR или PaddleOCR.
  • Большие объёмы и максимум качества — облачный сервис.

Как повысить точность: чек-лист

Соберу в одном месте всё, что помогло в примерах выше. Идите по списку сверху вниз и проверяйте результат после каждого шага.
  1. Язык. Укажите lang="rus+eng" и проверьте, что rus есть в списке языков.
  2. Размер. Увеличьте картинку в 2–3 раза, если буквы мельче 20 пикселей.
  3. Серый цвет. Уберите цвет: ImageOps.grayscale или cv2.cvtColor.
  4. Шум. Медианный фильтр: ImageFilter.MedianFilter(3) или cv2.medianBlur.
  5. Порог. Ровный фон — обычный порог или Оцу. Неровный фон — адаптивный.
  6. Режим. Подберите --psm под тип картинки: строка, блок, разрозненный текст.
  7. Обрезка. Вырежьте только нужную область (img.crop((x1, y1, x2, y2))). Чем меньше лишнего, тем меньше ошибок.
  8. Проверка. Проверяйте формат результата: телефон, сумма, дата. Tesseract бывает уверен и в ошибке.
  9. Источник. Если тот же текст есть в HTML или PDF текстовым слоем — берите оттуда, а не из OCR.
Хорошая привычка — сохранять картинку после каждого шага подготовки и смотреть на неё. Так сразу видно, на каком шаге текст испортился.

Капча: почему её не обходят

CAPTCHA (капча) — это проверка «человек вы или программа»: искажённые буквы, выбор картинок, галочка «я не робот». Владелец сайта ставит её специально, чтобы защитить формы и данные от автоматических запросов. Поэтому распознавать капчу через OCR и отправлять ответ программой — это не техническая задача, а обход защиты. Такой обход может нарушать условия использования сайта, а в отдельных случаях подпадать под статью 272 УК РФ о неправомерном доступе к компьютерной информации. Если ваш парсер упёрся в капчу, это сигнал сменить подход:
  • поищите официальный API сайта — данные часто отдают легально и в удобном виде;
  • напишите владельцу и попросите разрешение или выгрузку;
  • если запросов немного, вводите капчу вручную сами;
  • снизьте частоту запросов: капча часто появляется как ответ на слишком быстрый сбор.

Законно ли это: что важно знать

Сам по себе OCR — обычная технология, как сканер. Вопросы появляются тогда, когда вы распознаёте чужие данные с чужих сайтов. Коротко о главном.
  • Условия сайта и robots.txt. Прочитайте пользовательское соглашение и файл robots.txt (лежит по адресу сайт/robots.txt). Если там запрещён автоматический сбор — лучше не собирать или договориться с владельцем.
  • Персональные данные. Телефоны, имена, адреса, фото людей на картинках — это персональные данные. Их сбор и хранение регулирует Федеральный закон № 152-ФЗ «О персональных данных». То, что телефон виден на странице, ещё не значит, что его можно собирать в свою базу.
  • Права на изображения и тексты. Картинки, обложки и тексты защищает авторское право — часть четвёртая Гражданского кодекса РФ. Распознать для себя и для анализа — одно, опубликовать у себя — совсем другое.
  • Обход защиты. Обход капчи, авторизации и других технических барьеров может подпадать под статью 272 УК РФ.
Это не юридическая консультация. Если проект коммерческий или данные чувствительные, лучше обсудить его с юристом до запуска.

Итог

Распознавание текста на Python укладывается в короткую схему: Tesseract с нужным языком, pytesseract для вызова из кода и подготовка картинки перед распознаванием. На чистых картинках всё работает с первой строки кода. На трудных решает подготовка: серый цвет, увеличение, медианный фильтр, правильный порог. И здесь OpenCV с адаптивным порогом часто выигрывает у простого порога Pillow. Для парсинга OCR — запасной путь. Сначала ищите текст в HTML, атрибутах, API и текстовом слое PDF. Распознавайте картинки только там, где по-другому данные не получить, и только там, где их вообще можно брать. Потренироваться удобно на учебных сайтах вроде books.toscrape.com и quotes.toscrape.com: там можно ошибаться сколько угодно. Индивидуальное и групповое обучение «Аналитик данных» Если вы хотите стать экспертом в аналитике, могу помочь. Запишитесь на мой курс «Аналитик данных» и начните свой путь в мир ИТ уже сегодня! Контакты Для получения дополнительной информации и записи на курсы свяжитесь со мной: Телеграм: https://t.me/Vvkomlev Email: victor.komlev@mail.ru Объясняю сложное простыми словами. Даже если вы никогда не работали с ИТ и далеки от программирования, теперь у вас точно все получится! Проверено десятками примеров моих учеников. Гибкий график обучения. Я предлагаю занятия в мини-группах и индивидуально, что позволяет каждому заниматься в удобном темпе. Вы можете совмещать обучение с работой или учебой. Практическая направленность. 80%: практики, 20% теории. У меня множество авторских заданий, которые фокусируются на практике. Вы не просто изучаете теорию, а сразу применяете знания в реальных проектах и задачах. Разнообразие учебных материалов: Теория представлена в виде текстовых уроков с примерами и видео, что делает обучение максимально эффективным и удобным. Понимаю, что обучение информационным технологиям может быть сложным, особенно для новичков. Моя цель – сделать этот процесс максимально простым и увлекательным. У меня персонализированный подход к каждому ученику. Максимальный фокус внимания на ваши потребности и уровень подготовки.
Понравилась статья? Поделиться с друзьями:
Школа Виктора Комлева
Добавить комментарий

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.