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: находит нужный тег и забирает текст. Но иногда нужные данные лежат не в тексте, а на картинке:- цена или телефон нарисованы картинкой, чтобы их не копировали;
- на сайте выложены сканы документов, прайсов, расписаний;
- таблица вставлена скриншотом;
- текст есть на обложке, баннере, инфографике.
Инструменты: что понадобится
Для работы нужны четыре вещи. Сначала коротко, потом подробно.- Tesseract — сам «движок» распознавания. Это отдельная программа, не библиотека Python. Её ставят в систему.
- pytesseract — библиотека-обёртка: она запускает Tesseract из Python и возвращает результат строкой или словарём.
- Pillow — библиотека для работы с картинками: открыть, повернуть, увеличить, сделать серой.
- OpenCV (модуль
cv2) — библиотека компьютерного зрения. Умеет то же, что Pillow, плюс более сильные фильтры и умный порог.
Установка 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 пикселей.
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, предобработка) — это несколько простых шагов перед распознаванием. У каждого шага своя цель.- Оттенки серого. Цвет для распознавания букв не нужен, а лишние каналы только мешают. Одна яркость вместо трёх цветов.
- Увеличение. Tesseract плохо видит мелкие буквы. Увеличение в 2–3 раза часто даёт больше, чем любой фильтр.
- Медианный фильтр. Убирает одиночные точки шума. Каждый пиксель заменяется «средним» из соседей, и случайные точки исчезают, а линии букв остаются.
- Порог (по-английски 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.
- Большие объёмы и максимум качества — облачный сервис.
Как повысить точность: чек-лист
Соберу в одном месте всё, что помогло в примерах выше. Идите по списку сверху вниз и проверяйте результат после каждого шага.- Язык. Укажите
lang="rus+eng"и проверьте, чтоrusесть в списке языков. - Размер. Увеличьте картинку в 2–3 раза, если буквы мельче 20 пикселей.
- Серый цвет. Уберите цвет:
ImageOps.grayscaleилиcv2.cvtColor. - Шум. Медианный фильтр:
ImageFilter.MedianFilter(3)илиcv2.medianBlur. - Порог. Ровный фон — обычный порог или Оцу. Неровный фон — адаптивный.
- Режим. Подберите
--psmпод тип картинки: строка, блок, разрозненный текст. - Обрезка. Вырежьте только нужную область (
img.crop((x1, y1, x2, y2))). Чем меньше лишнего, тем меньше ошибок. - Проверка. Проверяйте формат результата: телефон, сумма, дата. Tesseract бывает уверен и в ошибке.
- Источник. Если тот же текст есть в HTML или PDF текстовым слоем — берите оттуда, а не из OCR.
Капча: почему её не обходят
CAPTCHA (капча) — это проверка «человек вы или программа»: искажённые буквы, выбор картинок, галочка «я не робот». Владелец сайта ставит её специально, чтобы защитить формы и данные от автоматических запросов. Поэтому распознавать капчу через OCR и отправлять ответ программой — это не техническая задача, а обход защиты. Такой обход может нарушать условия использования сайта, а в отдельных случаях подпадать под статью 272 УК РФ о неправомерном доступе к компьютерной информации. Если ваш парсер упёрся в капчу, это сигнал сменить подход:- поищите официальный API сайта — данные часто отдают легально и в удобном виде;
- напишите владельцу и попросите разрешение или выгрузку;
- если запросов немного, вводите капчу вручную сами;
- снизьте частоту запросов: капча часто появляется как ответ на слишком быстрый сбор.
Законно ли это: что важно знать
Сам по себе OCR — обычная технология, как сканер. Вопросы появляются тогда, когда вы распознаёте чужие данные с чужих сайтов. Коротко о главном.- Условия сайта и robots.txt. Прочитайте пользовательское соглашение и файл
robots.txt(лежит по адресусайт/robots.txt). Если там запрещён автоматический сбор — лучше не собирать или договориться с владельцем. - Персональные данные. Телефоны, имена, адреса, фото людей на картинках — это персональные данные. Их сбор и хранение регулирует Федеральный закон № 152-ФЗ «О персональных данных». То, что телефон виден на странице, ещё не значит, что его можно собирать в свою базу.
- Права на изображения и тексты. Картинки, обложки и тексты защищает авторское право — часть четвёртая Гражданского кодекса РФ. Распознать для себя и для анализа — одно, опубликовать у себя — совсем другое.
- Обход защиты. Обход капчи, авторизации и других технических барьеров может подпадать под статью 272 УК РФ.
