How to Extract Text from Images and Screenshots in Bulk

Uses Image to Text OCR — Extract Text from Photos & Screenshots

Short answer: Send image URLs to the Image to Text OCR actor, use the sparse segmentation mode for screenshots, and read the text and confidence per image. It costs $3 per 1,000 images.

Copying text out of one screenshot is easy. Doing it for a few thousand support screenshots, receipt photos or saved social posts is not, and the results vary a lot depending on whether the image is a clean UI capture or a skewed phone photo.

Generic OCR settings are tuned for full pages of text, so they often miss the scattered labels in a screenshot or merge columns in a receipt. The Image to Text OCR actor runs Tesseract 5 with a choice of page segmentation modes, returns a confidence score per image, and can return every word with its pixel box.

This guide picks settings for different image types, filters out unreliable results in Python, and uses word boxes to find and redact sensitive text.

Step by step

  1. Get public links to the images

    The actor reads images by URL: PNG, JPEG, TIFF, WebP, GIF, BMP and JPEG 2000. HEIC and AVIF photos from phones and SVG are not supported, so convert photos to JPEG first. Files can be up to 100 MB each.

  2. Pick a segmentation mode for the image type

    pageSegmentation controls how Tesseract looks for text. auto suits photos of documents and scanned pages. sparse looks for text anywhere and is the best start for UI screenshots, forms and infographics. single-block suits receipts and short snippets, and single-column suits uniform text. If you are unsure, run a small sample of images in two modes and compare confidence scores.

    {
      "urls": [
        "https://github.com/tesseract-ocr/tessdoc/raw/main/images/eurotext.png"
      ],
      "languages": ["eng"],
      "pageSegmentation": "sparse",
      "minWordConfidence": 50
    }
  3. Set languages and noise filtering

    Choose up to 4 of the 29 languages in languages; screenshots from a localised app may need two. minWordConfidence drops words Tesseract is unsure about, which helps with compression artefacts, icons mistaken for letters and background patterns. Start around 40-60 and lower it if real words go missing.

  4. Keep reliable results and queue the rest

    Each image is one row with text, wordCount and confidence. Very low confidence or very few words usually means a blurry photo, handwriting or an image with no real text. Split results into accepted and needs-review lists.

    import os
    import requests
    
    url = "https://api.apify.com/v2/acts/actor-forge~image-to-text-ocr/run-sync-get-dataset-items"
    payload = {
        "urls": [
            "https://github.com/tesseract-ocr/tessdoc/raw/main/images/eurotext.png",
            "https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif",
        ],
        "pageSegmentation": "sparse",
    }
    resp = requests.post(url, params={"token": os.environ["APIFY_TOKEN"]}, json=payload, timeout=300)
    resp.raise_for_status()
    
    accepted, review = [], []
    for row in resp.json():
        conf = row.get("confidence") or 0
        if row.get("error") or conf < 75 or (row.get("wordCount") or 0) < 3:
            review.append(row)
        else:
            accepted.append(row)
    
    print(len(accepted), "accepted;", len(review), "need review")
  5. Find and redact sensitive text with word boxes

    Turn on includeWords to get every recognised word with its confidence and pixel box (left, top, width, height). The exact field names inside each word entry can be checked in your first run's dataset; the snippet below reads them defensively. With Pillow you can then draw filled rectangles over matching words, for example email addresses, before sharing the screenshot.

    import io, re
    import requests
    from PIL import Image, ImageDraw
    
    EMAIL = re.compile(r"[\w.+-]+@[\w-]+\.[\w.]+")
    
    def redact(row, out_path):
        img = Image.open(io.BytesIO(requests.get(row["url"], timeout=60).content)).convert("RGB")
        draw = ImageDraw.Draw(img)
        for w in row.get("words") or []:
            text = w.get("text", "")
            if EMAIL.search(text):
                box = w.get("bbox") or w
                l, t = box["left"], box["top"]
                draw.rectangle([l, t, l + box["width"], t + box["height"]], fill="black")
        img.save(out_path)
  6. Mix in scanned PDFs if you need to

    Scanned PDFs work in this actor too, page by page. If your batches regularly mix PDFs and images, OCR to Text uses the same engine and pricing and is set up for mixed document intake.

Sample input

{
  "urls": [
    "https://github.com/tesseract-ocr/tessdoc/raw/main/images/eurotext.png",
    "https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif"
  ],
  "languages": [
    "eng"
  ],
  "pageSegmentation": "sparse",
  "includeWords": true,
  "minWordConfidence": 50
}

Sample output

Each image becomes one row with its file type, word count, confidence and the extracted text, as in the sample below.

urlfileTypepagesOcrconfidencewordCounttext
https://github.com/tesseract-ocr/tessdoc/raw/main/images/eurotext.pngpng189.766The (quick) [brown] {fox} jumps!\nOver the $43,456.78 … #90 dog\n& duck/goose, as 12.5% of E-mail…
https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tiftiff196.360This is a lot of 12 point text to test the\nocr code and see if it works on all types\nof file format.

Run it from code

cURL

curl -X POST "https://api.apify.com/v2/acts/actor-forge~image-to-text-ocr/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://github.com/tesseract-ocr/tessdoc/raw/main/images/eurotext.png", "https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif"], "languages": ["eng"], "pageSegmentation": "sparse", "includeWords": true, "minWordConfidence": 50}'

JavaScript (Node.js)

// Node.js 18+ (built-in fetch). Set APIFY_TOKEN in your environment.
const input = {
  "urls": [
    "https://github.com/tesseract-ocr/tessdoc/raw/main/images/eurotext.png",
    "https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif"
  ],
  "languages": [
    "eng"
  ],
  "pageSegmentation": "sparse",
  "includeWords": true,
  "minWordConfidence": 50
};

const res = await fetch(
  "https://api.apify.com/v2/acts/actor-forge~image-to-text-ocr/run-sync-get-dataset-items",
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.APIFY_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(input),
  }
);
const items = await res.json();
console.log(items.length, items[0]);

Python

# pip install apify-client   (set APIFY_TOKEN in your environment)
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run_input = {
    "urls": [
        "https://github.com/tesseract-ocr/tessdoc/raw/main/images/eurotext.png",
        "https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif"
    ],
    "languages": [
        "eng"
    ],
    "pageSegmentation": "sparse",
    "includeWords": True,
    "minWordConfidence": 50
}

run = client.actor("actor-forge/image-to-text-ocr").call(run_input=run_input)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

The run-sync-get-dataset-items endpoint waits for the run (up to 5 minutes) and returns the dataset rows directly. For longer runs, start the run asynchronously and read the dataset when it finishes, as the Python example does. Replace APIFY_TOKEN with your own token from the Apify console.

What it costs

Extracting text from 2,500 support screenshots costs 2,500 x $0.003 = $7.50, with no compute charge on top. Images that fail to download or turn out blank are not charged.

Charged forWhenPricePer 1,000Worked example
Page ocrCharged per image or scanned PDF page read by OCR$0.003$3A 10-page scanned PDF = $0.03; 1,000 receipts = $3.00
Page textCharged per PDF page that already has a text layer (read directly, no OCR)$0.0003$0.30A 100-page digital PDF = $0.03
Actor Start (platform start fee)Charged when the Actor starts running. Number of events charged depends on Actor memory (one event per GB, minimum one event).$0.00005—One run at 4 GB = $0.0002

Ready to try it? Image to Text OCR — Extract Text from Photos & Screenshots runs on Apify, pay per result.

Run on Apify → Read the full actor page

Tips and pitfalls

FAQ

What is the best OCR setting for screenshots?

Start with pageSegmentation set to sparse, which looks for text anywhere on the image rather than assuming a page layout. Compare it with auto on a few samples and keep the one with higher confidence.

Can I extract text from images on a web page?

Yes, if you have the direct image URLs. Collect the image links first, then pass them in urls. Links that return a web page instead of an image are reported and not charged.

Does OCR keep the layout of the screenshot?

It keeps line and paragraph breaks in reading order, not the visual layout. For positions, turn on includeWords to get each word's pixel box.

How do I process thousands of images?

Pass them in batches via urls; maxFiles defaults to 1,000 per run and can go up to 10,000. Set a maximum total charge so a large batch stops cleanly at your budget.

Actors used in this guide

Image to Text OCR — Extract Text from Photos & Screenshots

Extract text from images (PNG, JPEG, TIFF, WebP, GIF, BMP) with Tesseract 5 OCR in 29 languages: screenshots, photos of documents, receipts, scans…

$3 per 1,000 · page ocr

OCR — Scanned PDF & Image to Text (Tesseract, 29 Languages)

Extract text from scanned PDFs and images (PNG, JPEG, TIFF, WebP, GIF, BMP) with Tesseract 5 OCR in 29 languages. PDF pages that already have text…

$3 per 1,000 · page ocr

Related guides

How to OCR Scanned PDFs to Text with an API

Send scanned PDF links to the PDF OCR actor, get text back one row per page with a confidence score, and use that score to flag pages for review.…

How to Convert PDF to Markdown for RAG Pipelines

Send your PDF links to the Any URL to Markdown actor, read the markdown field of each result, then split it by heading into token-sized chunks…

All guides