<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Joseph Postman</title>
    <description>The latest articles on DEV Community by Joseph Postman (@joseph_postman_b091ffaccf).</description>
    <link>https://dev.to/joseph_postman_b091ffaccf</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4028522%2Fd3bc9608-0752-460c-b841-c2fdee916416.png</url>
      <title>DEV Community: Joseph Postman</title>
      <link>https://dev.to/joseph_postman_b091ffaccf</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/joseph_postman_b091ffaccf"/>
    <language>en</language>
    <item>
      <title>Harvest: Free Alternative to Firecrawl with Semantic Cache and MCP</title>
      <dc:creator>Joseph Postman</dc:creator>
      <pubDate>Tue, 14 Jul 2026 10:37:12 +0000</pubDate>
      <link>https://dev.to/joseph_postman_b091ffaccf/i-built-a-web-scraper-that-fixes-itself-when-websites-change-then-i-open-sourced-it-4jgd</link>
      <guid>https://dev.to/joseph_postman_b091ffaccf/i-built-a-web-scraper-that-fixes-itself-when-websites-change-then-i-open-sourced-it-4jgd</guid>
      <description>&lt;p&gt;I spent the last month building something I couldn't find: a web scraper that doesn't&lt;br&gt;
break every time a website updates its HTML.&lt;/p&gt;

&lt;p&gt;Three weeks ago I open-sourced it. It's called &lt;a href="https://github.com/zad111ak-ai/harvest" rel="noopener noreferrer"&gt;Harvest&lt;/a&gt;,&lt;br&gt;
it's MIT-licensed, and this is the honest tour — what it does, why it exists, and&lt;br&gt;
the three lessons that cost me the most time.&lt;/p&gt;
&lt;h2&gt;
  
  
  The problem that started it
&lt;/h2&gt;

&lt;p&gt;Every scraper I tried had the same failure mode: the website changes one CSS class,&lt;br&gt;
and suddenly you're debugging at 2 AM. Firecrawl costs $50/month. Crawl4AI (72K stars,&lt;br&gt;
great library) has no MCP server and no semantic cache. ScrapeGraphAI requires a&lt;br&gt;
paid API.&lt;/p&gt;

&lt;p&gt;I wanted something that bypasses Cloudflare, extracts data by plain-language&lt;br&gt;
description (not CSS selectors), and works as an MCP server for AI agents. Free.&lt;/p&gt;

&lt;p&gt;So I built it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Three features that changed how I scrape
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Semantic Cache — same meaning, zero tokens
&lt;/h3&gt;

&lt;p&gt;Every AI-based scraper burns tokens on repeated queries. Ask "get all prices" and&lt;br&gt;
then "extract product prices" — most tools process both from scratch.&lt;/p&gt;

&lt;p&gt;Harvest caches by &lt;em&gt;meaning&lt;/em&gt;, not exact text. Use sentence embeddings to compare&lt;br&gt;
queries. Same intent? Cache hit. Zero tokens.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# First call: 2K tokens consumed
&lt;/span&gt;&lt;span class="n"&gt;harvest&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;extract&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;shop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Get all product prices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Second call: instant, 0 tokens
&lt;/span&gt;&lt;span class="n"&gt;harvest&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;extract&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;shop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Extract prices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Third call: instant, 0 tokens
&lt;/span&gt;&lt;span class="n"&gt;harvest&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;extract&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;shop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Find prices on page&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cache auto-invalidates when the HTML hash changes. I saw 50-70% token reduction&lt;br&gt;
on real workloads.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Self-Healing Parsers — scrapers that repair themselves
&lt;/h3&gt;

&lt;p&gt;This is the one I'm proudest of. When a website changes its HTML structure, Harvest&lt;br&gt;
doesn't crash — it regenerates the CSS selectors via LLM.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Website updated →
  → Old selectors fail validation
  → LLM gets: old HTML fragment + new HTML + old selectors
  → LLM returns: new working selectors
  → New selectors validated against schema
  → Selector saved. User notified.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All selector history is stored in &lt;code&gt;~/.harvest/self_healing/&lt;/code&gt;. You can roll back&lt;br&gt;
any time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Enable self-healing&lt;/span&gt;
harvest llm-extract https://shop.com &lt;span class="nt"&gt;--prompt&lt;/span&gt; &lt;span class="s2"&gt;"Get prices"&lt;/span&gt; &lt;span class="nt"&gt;--self-healing&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Structural Diff — git diff for web pages
&lt;/h3&gt;

&lt;p&gt;The most underrated feature. Before Harvest, when a parser broke I had no idea&lt;br&gt;
&lt;em&gt;what&lt;/em&gt; changed. Now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Capture a snapshot&lt;/span&gt;
harvest snapshot https://shop.com &lt;span class="nt"&gt;--name&lt;/span&gt; v1.0

&lt;span class="c"&gt;# Compare later&lt;/span&gt;
harvest diff https://shop.com v1.0 latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;📊 Structural Diff for https://shop.com

🆕 Added:
  • Block "Recommendations" (after description)
  • Field "Delivery" (in sidebar)

❌ Removed:
  • Field "SKU" (was in header)

🔄 Changed:
  • Price: &amp;lt;span class="price"&amp;gt; → &amp;lt;div class="price-container"&amp;gt;

💡 Recommendation:
  Update your extractor: .price → .price-container .price-value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Script Generator trick
&lt;/h2&gt;

&lt;p&gt;This one saved me the most operational cost. One LLM call analyzes a page and&lt;br&gt;
generates a standalone Python script with hardcoded CSS selectors. Zero tokens&lt;br&gt;
at runtime. Pure Scrapling + BeautifulSoup.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# One-time: 4K tokens&lt;/span&gt;
harvest generate https://catalog.com &lt;span class="nt"&gt;--fields&lt;/span&gt; title price image

&lt;span class="c"&gt;# Forever: 0 tokens per run&lt;/span&gt;
./scrape_generated.py https://catalog.com/page/1

&lt;span class="c"&gt;# Batch mode&lt;/span&gt;
./scrape_generated.py urls.txt &lt;span class="nt"&gt;--csv&lt;/span&gt; prices.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before: 1000 runs = 2M tokens ($0.30-2.00 in LLM costs).&lt;br&gt;
After: 1000 runs = $0.00.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why MCP matters
&lt;/h2&gt;

&lt;p&gt;Most scraping tools are libraries. Harvest runs as an &lt;strong&gt;MCP server&lt;/strong&gt; — any&lt;br&gt;
MCP-compatible client (Claude, Cursor, Hermes, custom agents) can use it&lt;br&gt;
out of the box.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;".[mcp]"&lt;/span&gt;
harvest-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Available tools: &lt;code&gt;scrape&lt;/code&gt;, &lt;code&gt;extract&lt;/code&gt;, &lt;code&gt;llm_extract&lt;/code&gt;, &lt;code&gt;batch&lt;/code&gt;, &lt;code&gt;crawl&lt;/code&gt;, &lt;code&gt;monitor&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;contacts&lt;/code&gt;, &lt;code&gt;snapshot&lt;/code&gt;, &lt;code&gt;diff&lt;/code&gt;, &lt;code&gt;cache-stats&lt;/code&gt;, &lt;code&gt;generate&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;p&gt;I'm not going to pretend this solves everything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Turnstile checkbox&lt;/strong&gt; (behavioral biometrics) can still block. Regular Cloudflare JS challenges work fine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Requires Chromium&lt;/strong&gt; (auto-downloaded by Scrapling, ~300MB)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM extraction&lt;/strong&gt; needs an endpoint — Ollama, OmniRoute, or any OpenAI-compatible API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brand new&lt;/strong&gt; — 0 GitHub stars as of writing. No community yet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But within those bounds, it works. I use it daily for price monitoring, content&lt;br&gt;
extraction, and web research.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Harvest&lt;/th&gt;
&lt;th&gt;Crawl4AI&lt;/th&gt;
&lt;th&gt;Firecrawl&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Semantic Cache&lt;/td&gt;
&lt;td&gt;✅ Meaning-based&lt;/td&gt;
&lt;td&gt;❌ URL-only&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-Healing Parsers&lt;/td&gt;
&lt;td&gt;✅ Auto-LLM repair&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Structural Diff&lt;/td&gt;
&lt;td&gt;✅ DOM change detection&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Script Generator (0 tokens)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP Server&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare bypass&lt;/td&gt;
&lt;td&gt;✅ Built-in&lt;/td&gt;
&lt;td&gt;⚠️ Basic&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM extraction (plain language)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Free&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Free&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$50/mo&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Quick start
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;scrapling aiohttp
git clone https://github.com/zad111ak-ai/harvest
&lt;span class="nb"&gt;cd &lt;/span&gt;harvest
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Full page content&lt;/span&gt;
harvest scrape https://news.ycombinator.com

&lt;span class="c"&gt;# AI extraction — just describe&lt;/span&gt;
harvest llm-extract https://books.toscrape.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--prompt&lt;/span&gt; &lt;span class="s2"&gt;"Get all book titles and prices"&lt;/span&gt;

&lt;span class="c"&gt;# Monitor for changes&lt;/span&gt;
harvest monitor https://example.com/pricing

&lt;span class="c"&gt;# Zero-token scraper&lt;/span&gt;
harvest generate https://shop.com &lt;span class="nt"&gt;--fields&lt;/span&gt; title price
./scrape_generated.py https://shop.com/page/1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;If I were starting today:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Better docs first.&lt;/strong&gt; I wrote README after the code. Docs-first would have caught API inconsistencies earlier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More tests earlier.&lt;/strong&gt; 116 tests now, but the first 30 would have prevented three regression bugs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Release earlier.&lt;/strong&gt; The first public version was v0.5.0. I should have shipped at v0.1.0 and iterated.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;pip install harvest-agent&lt;/p&gt;

&lt;p&gt;Or clone from &lt;a href="https://github.com/zad111ak-ai/harvest" rel="noopener noreferrer"&gt;github.com/zad111ak-ai/harvest&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;MIT licensed. 38 commits, 9.5K lines of Python. Built in 3 weeks.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Donation addresses in README if you find it useful — but the real contribution is feedback and issues.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
