Vision Models for Scraping#
When the data is a chart, a scanned table, or a UI built to defeat parsing β screenshot it and ask a vision model for JSON.
β± ~8 min read Β· ~15 min hands-on π needs: Playwright & Selenium Β· Structured Output
Some data simply isn’t in the DOM: values baked into an image, a canvas-rendered chart, a scanned PDF page, or a deliberately obfuscated layout. A vision-language model (VLM) reads the rendered pixels the way a person would.
It’s the last resort β slower and costlier than a hidden API or HTML parsing β but it works where everything else fails.
Try it in 5 minutes β screenshot β JSON#
# /// script
# requires-python = ">=3.12"
# dependencies = ["playwright>=1.40"]
# ///
"""Screenshot one element, ready to hand to a vision model.
Setup: uv run --with playwright playwright install chromium
Run: uv run shot.py
"""
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page(viewport={"width": 1280, "height": 900})
page.goto("https://books.toscrape.com/")
page.wait_for_selector(".product_pod")
# Crop to just the region that matters β fewer tokens, better accuracy.
page.query_selector(".product_pod").screenshot(path="element.png")
print("Saved element.png")
browser.close()β
Now send element.png to any vision model with a strict prompt:
Extract the book title, price, and star rating from this image. Return only JSON matching
{"title": str, "price": str, "rating": int}.
Choosing a model#
| Model | Why you’d pick it |
|---|---|
| Qwen3-VL | The leading open-weight VLM family in 2026 β strong OCR (32 languages), documents, forms, multiple sizes |
| InternVL3 | Strongest MIT-licensed option; permissive for commercial work |
| DeepSeek-VL2 | Excellent OCR/document understanding at low compute |
| Hosted (Claude, Gemini, GPT) | Best accuracy with zero setup; you send the image to a third party |
Run open weights locally via Ollama when the images are sensitive or the volume makes API pricing hurt.
Make the output trustworthy#
VLMs hallucinate confidently β a misread digit looks exactly like a correct one. Three defences:
- Force a schema. Demand JSON and validate it β Structured Output. A parse failure is a signal.
- Crop tightly. One element per image beats a full-page screenshot for both cost and accuracy.
- Verify what you can. Do the line items sum to the stated total? Is the date plausible? Cross-check a sample by hand.
βοΈ Screenshotting doesn’t change permissions. The rules that govern scraping the page govern the pixels too β Legal & Ethical Scraping.
When it fails#
| Symptom | Cause | Fix |
|---|---|---|
| Numbers subtly wrong | Low resolution | Screenshot at device_scale_factor=2; crop tighter |
| Model invents fields | Vague prompt | Give an explicit schema; say “return null if absent” |
| Output isn’t valid JSON | No format constraint | Use structured output / JSON mode; retry on parse failure |
| Costs explode | Full-page images every time | Crop; cache by image hash; try HTML first |
| Rotated/skewed scans | Not deskewed | Pre-process β Image Processing Pipeline |
Your turn (β15 min)#
- Run
shot.py, then ask a vision model for{"title","price","rating"}JSON. Compare against the real HTML. - Re-shoot at
device_scale_factor=2and see whether accuracy improves. - Try a full-page screenshot instead of the cropped element β note the accuracy and cost difference.
- Write a validator that rejects any response missing a key or with
ratingoutside 1β5.
Checklist#
- I try hidden APIs and HTML parsing before reaching for pixels.
- I can screenshot a single element with Playwright.
- I always demand a schema and validate the JSON.
- I crop tightly to cut tokens and raise accuracy.
- I know when to run open weights locally instead of a hosted API.
Go deeper#
- Qwen3-VL β the leading open-weight vision family.
- Playwright screenshots β element, full-page, and scale options.
- Structured Output (Week 3) β making model output parseable by construction.