<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/teaching-an-ai-scraper-from-human-corrections-bajtuglno" -->

---
title: Teaching an AI Scraper from Human Corrections | daily.dev
description: An admin's edit to a scraped event value is treated as an answer key for fixing the underlying extraction rule, not just the one event. The system normalizes...
canonical: https://daily.dev/posts/teaching-an-ai-scraper-from-human-corrections-bajtuglno
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Teaching an AI Scraper from Human Corrections | daily.dev
og:description: An admin's edit to a scraped event value is treated as an answer key for fixing the underlying extraction rule, not just the one event. The system normalizes...
og:url: https://daily.dev/posts/teaching-an-ai-scraper-from-human-corrections-bajtuglno
og:image: https://api.daily.dev/og/posts/BaJtuGlnO.png
og:image:alt: Teaching an AI Scraper from Human Corrections
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Teaching an AI Scraper from Human Corrections

**[RUBYLAND](https://daily.dev/sources/rubyla)** · 13 min read · 1 upvotes · 2 comments

## Summary

An admin's edit to a scraped event value is treated as an answer key for fixing the underlying extraction rule, not just the one event. The system normalizes text (stripping zero-width characters, mapping dashes, collapsing whitespace) before comparing values, then uses a small LLM judge for remaining differences. It diagnoses failures like over-scoped or under-scoped selectors with plain string logic, and anchors corrected values by searching the saved page for the admin's exact text. The correction loop runs up to 3 attempts; if mismatches remain, the run fails and nothing is written.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://www.ombulabs.ai/blog/teaching-ai-scraper-from-human-corrections.html>

## Questions this post answers

### How do you decide if an admin's edited value actually differs from what was extracted, ignoring invisible character differences?

Normalize both strings before comparing: apply Unicode NFKC normalization to fold compatibility characters like non-breaking spaces, strip zero-width characters and byte-order marks, map en dashes, em dashes and minus signs to a plain hyphen, then collapse whitespace runs. If the normalized values are equal, the field matches without needing an LLM call.

_Building text comparison logic for scraped or user-edited data? Find more extraction engineering writeups on daily.dev._

### How many times should an automated correction loop retry before giving up when fixing a web scraping rule with an LLM?

Cap retries at three attempts. Each attempt asks the LLM for revised extraction rules, re-extracts, and compares against the admin's corrected values; if mismatches remain after three tries, the run is marked failed and nothing gets written, leaving the original rules and sample events untouched.

_Designing retry limits for LLM-based pipelines? daily.dev surfaces practical patterns for bounding agent loops._

### How can you tell whether a CSS selector grabbed too much text or too little when scraping a webpage?

Compare the normalized extracted text against the normalized expected text using substring checks: if the expected value sits inside the extracted one, the selector is over-scoped and grabbed extra surrounding text; if the extracted value sits inside the expected one, it is under-scoped and only captured a fragment. This mechanical check needs no LLM call.

_Debugging selector accuracy in a scraper? daily.dev collects hands-on write-ups on extraction failure diagnosis._

## Community discussion

Top comments from developers on daily.dev.

**@nark3d** · 0 upvotes

> I agree with the approach.  Sending the corrected examples on that first attempt is worth keeping.  Without them the model gets a free hand to fix the failures with a selector that breaks what was working.  Later attempts dropping them makes sense because the prompt grows with every page of HTML.  Sending all of them on a retry would just make the failures compete with the successes for attention.

**@kartiknvj** · 0 upvotes

> Failing the judge closed makes sense, but with 3 attempts a judge outage can eat one on a value that was already right. I'd record judge errors as their own state next to mismatches, so the failure page shows whether the rules lost or the judge never answered.

## Similar posts on daily.dev

- [Behind the scenes of an AI-Driven Web Scraping System](https://daily.dev/posts/behind-the-scenes-of-an-ai-driven-web-scraping-system-apsqb0lnn) · RUBYLAND · 0 upvotes · 0 comments
- [I trained my local LLM on its own failures, and now it learns from every mistake](https://daily.dev/posts/i-trained-my-local-llm-on-its-own-failures-and-now-it-learns-from-every-mistake-5imf9fizg) · XDA Developers · 0 upvotes · 0 comments
- [An AI meant to learn from its mistakes exploited a mistake in the test](https://daily.dev/posts/an-ai-meant-to-learn-from-its-mistakes-exploited-a-mistake-in-the-test-skqk1r4qs) · The Next Web · 0 upvotes · 0 comments
- [From spot fixes to measured gains: improving document extraction accuracy with ai\_extract and MLflow Prompt Registry](https://daily.dev/posts/from-spot-fixes-to-measured-gains-improving-document-extraction-accuracy-with-ai-extract-and-mlflow-vkmp90a1m) · databricks · 1 upvotes · 0 comments

---

Tags: [#python](https://daily.dev/tags/python), [#llm](https://daily.dev/tags/llm), [#prompt-engineering](https://daily.dev/tags/prompt-engineering), [#crawling](https://daily.dev/tags/crawling)

[View this post on daily.dev](https://daily.dev/posts/teaching-an-ai-scraper-from-human-corrections-bajtuglno)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Teaching an AI Scraper from Human Corrections","url":"https://daily.dev/posts/teaching-an-ai-scraper-from-human-corrections-bajtuglno","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/teaching-an-ai-scraper-from-human-corrections-bajtuglno"},"datePublished":"2026-10-28T20:24:26.909Z","dateModified":"2026-10-28T21:34:42.464Z","description":"An admin's edit to a scraped event value is treated as an answer key for fixing the underlying extraction rule, not just the one event. The system normalizes...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/2ba27cab9c6b09d98d3e492dcecaa20d?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/2ba27cab9c6b09d98d3e492dcecaa20d?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"RUBYLAND","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"RUBYLAND","logo":"https://media.daily.dev/image/upload/t_logo,f_auto/v1/logos/rubyla","url":"https://daily.dev/sources/rubyla"},"commentCount":2,"discussionUrl":"https://daily.dev/posts/teaching-an-ai-scraper-from-human-corrections-bajtuglno","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":1},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":2}],"keywords":"python,llm,prompt-engineering,crawling","timeRequired":"PT13M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"RUBYLAND","item":"https://daily.dev/sources/rubyla"},{"@type":"ListItem","position":3,"name":"Teaching an AI Scraper from Human Corrections"}]}
{"@context":"https://schema.org","@type":"WebPage","@id":"https://daily.dev/posts/teaching-an-ai-scraper-from-human-corrections-bajtuglno","comment":[{"@type":"Comment","text":"I agree with the approach.  Sending the corrected examples on that first attempt is worth keeping.  Without them the model gets a free hand to fix the failures with a selector that breaks what was working.  Later attempts dropping them makes sense because the prompt grows with every page of HTML.  Sending all of them on a retry would just make the failures compete with the successes for attention.","datePublished":"2026-10-30T11:32:55.972Z","url":"https://daily.dev/posts/BaJtuGlnO#c-tBBRo5PiA","author":{"@type":"Person","name":"Adam","url":"https://daily.dev/nark3d","image":"https://avatars.githubusercontent.com/u/2162621?v=4"}},{"@type":"Comment","text":"Failing the judge closed makes sense, but with 3 attempts a judge outage can eat one on a value that was already right. I’d record judge errors as their own state next to mismatches, so the failure page shows whether the rules lost or the judge never answered.","datePublished":"2026-10-30T13:34:19.430Z","url":"https://daily.dev/posts/BaJtuGlnO#c-NllX6nWBN","author":{"@type":"Person","name":"kartik-nvjk","url":"https://daily.dev/kartiknvj","image":"https://media.daily.dev/image/upload/s--3gGgsVCw--/f_auto/v1781456774/avatars/avatar_TvTVeiMdkRCqWUDullFmy?_a=BAMAMiWQ0"}}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/teaching-an-ai-scraper-from-human-corrections-bajtuglno#faq","mainEntity":[{"@type":"Question","name":"How do you decide if an admin's edited value actually differs from what was extracted, ignoring invisible character differences?","acceptedAnswer":{"@type":"Answer","text":"Normalize both strings before comparing: apply Unicode NFKC normalization to fold compatibility characters like non-breaking spaces, strip zero-width characters and byte-order marks, map en dashes, em dashes and minus signs to a plain hyphen, then collapse whitespace runs. If the normalized values are equal, the field matches without needing an LLM call. Building text comparison logic for scraped or user-edited data? Find more extraction engineering writeups on daily.dev."}},{"@type":"Question","name":"How many times should an automated correction loop retry before giving up when fixing a web scraping rule with an LLM?","acceptedAnswer":{"@type":"Answer","text":"Cap retries at three attempts. Each attempt asks the LLM for revised extraction rules, re-extracts, and compares against the admin's corrected values; if mismatches remain after three tries, the run is marked failed and nothing gets written, leaving the original rules and sample events untouched. Designing retry limits for LLM-based pipelines? daily.dev surfaces practical patterns for bounding agent loops."}},{"@type":"Question","name":"How can you tell whether a CSS selector grabbed too much text or too little when scraping a webpage?","acceptedAnswer":{"@type":"Answer","text":"Compare the normalized extracted text against the normalized expected text using substring checks: if the expected value sits inside the extracted one, the selector is over-scoped and grabbed extra surrounding text; if the extracted value sits inside the expected one, it is under-scoped and only captured a fragment. This mechanical check needs no LLM call. Debugging selector accuracy in a scraper? daily.dev collects hands-on write-ups on extraction failure diagnosis."}}]}
```

