<?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: Oaida Adrian</title>
    <description>The latest articles on DEV Community by Oaida Adrian (@darksider4all_afa2428f63d0).</description>
    <link>https://dev.to/darksider4all_afa2428f63d0</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%2F4014906%2Fc97aa091-845d-4fe5-b6fd-5a98bf7a23fa.jpg</url>
      <title>DEV Community: Oaida Adrian</title>
      <link>https://dev.to/darksider4all_afa2428f63d0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/darksider4all_afa2428f63d0"/>
    <language>en</language>
    <item>
      <title>Building a Romanian Business Registry API: 4.2M Companies as Queryable JSON</title>
      <dc:creator>Oaida Adrian</dc:creator>
      <pubDate>Wed, 26 Aug 2026 11:39:48 +0000</pubDate>
      <link>https://dev.to/darksider4all_afa2428f63d0/building-a-romanian-business-registry-api-42m-companies-as-queryable-json-1dh3</link>
      <guid>https://dev.to/darksider4all_afa2428f63d0/building-a-romanian-business-registry-api-42m-companies-as-queryable-json-1dh3</guid>
      <description>&lt;p&gt;Every Romanian company is registered in the &lt;strong&gt;ONRC&lt;/strong&gt; (the national trade register), and the state publishes the whole thing as open data on &lt;a href="https://data.gov.ro" rel="noopener noreferrer"&gt;data.gov.ro&lt;/a&gt;. That's 4.2 million companies, 19.3 million CAEN activity records, 3.68 million legal representatives and 4.64 million status entries — a genuinely useful corpus for lead generation, market analysis and "who actually owns this company" lookups.&lt;/p&gt;

&lt;p&gt;The catch: it ships as a pile of enormous CSVs, and the moment you try to &lt;em&gt;query&lt;/em&gt; it, Romanian orthography punches you in the face. I built a small free API around it, and this is how.&lt;/p&gt;

&lt;h2&gt;
  
  
  The source
&lt;/h2&gt;

&lt;p&gt;No scraping, no vendor. The official snapshot is a set of CKAN files, the biggest of which is &lt;code&gt;OD_FIRME.csv&lt;/code&gt; at ~690 MB and &lt;code&gt;OD_CAEN_AUTORIZAT.csv&lt;/code&gt; at ~425 MB. Combined, raw, that's well over a gigabyte of flat files. Loaded into SQLite with a full-text index it becomes a 3.6 GB database that answers a search in milliseconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  The build
&lt;/h2&gt;

&lt;p&gt;The load is boring and that's the point: stream the CSVs into SQLite, then create an &lt;strong&gt;FTS5&lt;/strong&gt; virtual table so &lt;code&gt;lookup_business("dedeman")&lt;/code&gt; is a real full-text query rather than a &lt;code&gt;LIKE '%dedeman%'&lt;/code&gt; scan. Two tables matter most:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;firme&lt;/code&gt; — companies, keyed by &lt;strong&gt;CUI&lt;/strong&gt; (the tax identifier) and &lt;strong&gt;registration code&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reprezentanti_legali&lt;/code&gt; — the legal representatives, so you can search &lt;em&gt;by person&lt;/em&gt; to find every company a director is attached to&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On top of that sits a thin &lt;strong&gt;FastAPI&lt;/strong&gt; wrapper exposing two read-only endpoints — no auth, no keys:&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;# by company name or CUI&lt;/span&gt;
curl &lt;span class="s2"&gt;"https://onrc-api.adrianhomelab.com/lookup_business?query=DEDEMAN&amp;amp;max_results=3"&lt;/span&gt;

&lt;span class="c"&gt;# by director / legal-representative name&lt;/span&gt;
curl &lt;span class="s2"&gt;"https://onrc-api.adrianhomelab.com/lookup_director?name=popescu"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The traps (this is the real content)
&lt;/h2&gt;

&lt;p&gt;Three things will bite you, in order of pain:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Diacritics.&lt;/strong&gt; Romanian uses &lt;code&gt;ă â î ș ț&lt;/code&gt;, and the registry stores them faithfully. &lt;code&gt;Popescu&lt;/code&gt; and &lt;code&gt;Popéscu&lt;/code&gt; are different strings to a naive matcher — but a human searching for "Popescu" expects both. The FTS index has to be &lt;strong&gt;diacritic-insensitive&lt;/strong&gt;: normalize &lt;code&gt;ș&lt;/code&gt;→&lt;code&gt;s&lt;/code&gt;, &lt;code&gt;ț&lt;/code&gt;→&lt;code&gt;t&lt;/code&gt;, &lt;code&gt;ă&lt;/code&gt;→&lt;code&gt;a&lt;/code&gt; on both the indexed text &lt;em&gt;and&lt;/em&gt; the query. Miss this and your "obvious" search returns nothing for half the surnames in the country.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. CUI vs registration code.&lt;/strong&gt; Every firm has a CUI (digits, sometimes zero-padded) &lt;em&gt;and&lt;/em&gt; a registration code like &lt;code&gt;J1992002621040&lt;/code&gt; (court + year + serial). They are not interchangeable, and newcomers constantly pass one where the API expects the other. Supporting both on a single &lt;code&gt;query&lt;/code&gt; parameter means testing numeric input against the CUI column first, then falling back to the registration code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. CSV scale and encoding.&lt;/strong&gt; The raw dumps are &lt;code&gt;;&lt;/code&gt;-delimited and large enough that &lt;code&gt;csv.DictReader&lt;/code&gt; in a tight loop will keep you up all night. Stream with &lt;code&gt;csv.reader&lt;/code&gt;, batch your &lt;code&gt;executemany&lt;/code&gt; inserts, and watch your transaction size — a single 690 MB file has to be committed incrementally or SQLite's WAL balloons.&lt;/p&gt;

&lt;h2&gt;
  
  
  What came out of it
&lt;/h2&gt;

&lt;p&gt;A live, free endpoint:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GET /lookup_business?query=&amp;lt;name|CUI&amp;gt;&amp;amp;max_results=N&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /lookup_director?name=&amp;lt;name&amp;gt;&amp;amp;max_results=N&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /docs&lt;/code&gt; — interactive OpenAPI docs&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /health&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same data is also available as an &lt;strong&gt;MCP server&lt;/strong&gt; (for AI agents) at &lt;code&gt;https://hermes.adrianhomelab.com/mcp&lt;/code&gt;, and the code is open. If you're doing anything with Romanian business data — or you just want a worked example of turning a big government CSV dump into a queryable service — the endpoint is live and free to hit. Source code and loaders are on GitHub: &lt;a href="https://github.com/darksider4all/leadgen-mcp" rel="noopener noreferrer"&gt;https://github.com/darksider4all/leadgen-mcp&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>sqlite</category>
      <category>api</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Building a SEC EDGAR Filings Scraper: 10-K Risk Factors and 13F Holdings as Clean JSON</title>
      <dc:creator>Oaida Adrian</dc:creator>
      <pubDate>Wed, 19 Aug 2026 11:11:51 +0000</pubDate>
      <link>https://dev.to/darksider4all_afa2428f63d0/building-a-sec-edgar-filings-scraper-10-k-risk-factors-and-13f-holdings-as-clean-json-1paj</link>
      <guid>https://dev.to/darksider4all_afa2428f63d0/building-a-sec-edgar-filings-scraper-10-k-risk-factors-and-13f-holdings-as-clean-json-1paj</guid>
      <description>&lt;p&gt;US public companies file everything with the SEC, and EDGAR is free to use. But the raw archive is hostile to analysis: filings are HTML documents with table-heavy layouts, and the full-text search UI is built for humans, not pipelines. I built an actor that turns a ticker into a clean corpus of JSON records — one per filing — ready for financial alt-data, risk analysis, RAG corpora, or LLM fine-tuning.&lt;/p&gt;

&lt;h2&gt;
  
  
  The source
&lt;/h2&gt;

&lt;p&gt;No API key, no data vendor. Three official SEC endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;company_tickers.json&lt;/code&gt; — ticker to CIK resolution&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;EDGAR full-text search JSON API&lt;/strong&gt; (&lt;code&gt;efts.sec.gov/LATEST/search-index&lt;/code&gt;) — query by ticker, form type, and date window&lt;/li&gt;
&lt;li&gt;the SEC Archives for the documents themselves&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two traps surfaced immediately. First, &lt;strong&gt;SEC fair-access enforcement is real&lt;/strong&gt;: a bare curl or python-requests User-Agent gets a 403, while a descriptive UA (&lt;code&gt;Darknezz Research admin@…&lt;/code&gt;) gets a 200. The actor sets a descriptive UA, paces at ~5 requests/second (well under the 10 rps limit), and backs off on 429s. Second, the &lt;code&gt;ciks&lt;/code&gt; search parameter &lt;strong&gt;requires the 10-digit zero-padded CIK&lt;/strong&gt; — &lt;code&gt;320193&lt;/code&gt; returns zero hits, &lt;code&gt;0000320193&lt;/code&gt; returns the filing. Padding is mandatory.&lt;/p&gt;

&lt;h2&gt;
  
  
  The structuring
&lt;/h2&gt;

&lt;p&gt;Raw HTML is only the beginning. Each filing becomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Metadata&lt;/strong&gt; — accession number, ticker, company, CIK, form type, filing date, period ending, 8-K event items, document and index URLs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sections&lt;/strong&gt; — split on Item headings (&lt;code&gt;Item 1.&lt;/code&gt;, &lt;code&gt;Item 1A.&lt;/code&gt;, &lt;code&gt;Item 7.&lt;/code&gt;, &lt;code&gt;Item 2.02&lt;/code&gt;…), per-section capped so one enormous filing can't blow a record&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk factors&lt;/strong&gt; — the full 10-K/10-Q Item 1A text, verified on a real Apple 10-K at the 60k-character cap&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Holdings&lt;/strong&gt; — 13F-HR InfoTable XML parsed into issuer, CUSIP, value, shares, and voting authority. The XML is namespace-prefixed (&lt;code&gt;&amp;lt;ns1:infoTable&amp;gt;&lt;/code&gt;), so every regex is namespace-tolerant&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Summary&lt;/strong&gt; — a deterministic, LLM-ready markdown digest: metadata, section map, risk excerpt, holdings preview. No external LLM call, no extra cost&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The smoke test
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AAPL 8-K ×3&lt;/strong&gt; — sections Item 2.02 and 9.01, summaries 476 chars each&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AAPL 10-K ×1&lt;/strong&gt; — 10+ sections, Item 1A risk factors 60,038 chars, summary 3,267 chars&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JPM 13F-HR&lt;/strong&gt; — 34,064 positions parsed locally; the mega-manager cap (20,000 positions) proven, with &lt;code&gt;holdingsCount&lt;/code&gt; and &lt;code&gt;holdingsTruncated&lt;/code&gt; flags so the record stays under Apify's item size limit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All cloud runs succeeded with non-zero, well-formed output.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest bits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Each filing yields its &lt;strong&gt;primary document only&lt;/strong&gt;; exhibits (press releases, contracts) stay one click away via the filing index URL.&lt;/li&gt;
&lt;li&gt;The summary is deterministic, not generative — great for pipelines, not a substitute for an LLM pass.&lt;/li&gt;
&lt;li&gt;Mega-manager 13Fs are truncated at 20,000 positions (flagged), because a 9 MB JSON record doesn't ship.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://apify.com/darknezz/sec-edgar-filings-scraper" rel="noopener noreferrer"&gt;&lt;strong&gt;SEC EDGAR Filings Scraper on Apify Store&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More from me
&lt;/h2&gt;

&lt;p&gt;While you're here, these might be worth a read:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-stopped-scraping-business-directories-and-built-an-mcp-server-on-official-registry-data-4caf"&gt;I Stopped Scraping Business Directories and Built an MCP Server on Official Registry Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/scraping-romanian-public-contracts-a-native-language-tender-awarded-deals-scraper-4lob"&gt;Scraping Romanian Public Contracts: A Native-Language Tender &amp;amp; Awarded Deals Scraper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-12-city-us-building-permits-scraper-with-python-138b"&gt;Building a 12-City US Building Permits Scraper With Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-an-eu-safety-gate-rapex-product-recall-scraper-with-python-2m26"&gt;Building an EU Safety Gate (RAPEX) Product Recall Scraper With Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-i-built-a-water-utility-risk-intelligence-tool-with-python-and-mcp-1hj0"&gt;How I Built a Water Utility Risk Intelligence Tool With Python and MCP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-an-aviation-hub-api-airports-airlines-live-flights-weather-from-six-keyless-sources-58j9"&gt;Building an Aviation Hub API: Airports, Airlines, Live Flights &amp;amp; Weather From Six Keyless Sources&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-a-canada-product-recalls-safety-alerts-scraper-that-reads-open-government-data-3p59"&gt;I Built a Canada Product Recalls &amp;amp; Safety Alerts Scraper That Reads Open Government Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-a-telegram-members-scraper-that-reads-public-chat-stats-without-login-34mc"&gt;I Built a Telegram Members Scraper That Reads Public Chat Stats Without Login&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-whois-dns-lookup-tool-domain-intelligence-in-one-api-call-2n5b"&gt;Building a WHOIS &amp;amp; DNS Lookup Tool: Domain Intelligence in One...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-an-ai-web-crawler-that-outputs-llm-ready-content-chunks-4ghg"&gt;Building an AI Web Crawler That Outputs LLM-Ready Content Chunks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-real-time-press-release-monitor-with-python-and-rss-aggregation-76l"&gt;Building a Real-Time Press Release Monitor with Python and RSS...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-universal-property-listing-scraper-with-python-and-json-ld-3mdj"&gt;Building a Universal Property Listing Scraper with Python and ...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/tracking-tech-sentiment-in-real-time-with-vader-and-python-3adl"&gt;Tracking Tech Sentiment in Real-Time with VADER and Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-i-built-a-product-hunt-scraper-that-tracks-launches-in-real-time-jkn"&gt;How I Built a Product Hunt Scraper That Tracks Launches in Rea...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/5-apis-every-developer-needs-for-content-processing-rss-extraction-sitemaps-ai-2630"&gt;5 APIs Every Developer Needs for Content Processing (RSS, Extraction, Sitemaps, AI)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-to-extract-clean-content-from-any-website-sitemap-for-seo-audits-ai-training-15a9"&gt;How to Extract Clean Content From Any Website Sitemap (For SEO...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/scraping-187000-romanian-businesses-building-a-b2b-lead-generation-tool-176n"&gt;Scraping 187,000 Romanian Businesses: Building a B2B Lead Gene...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/make-any-website-ai-readable-generating-llmstxt-files-with-python-3jop"&gt;Make Any Website AI-Readable: Generating llms.txt Files with&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-an-rss-aggregator-that-extracts-full-article-content-not-just-summaries-ifl"&gt;I Built an RSS Aggregator That Extracts Full Article Content (...&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>webscraping</category>
      <category>finance</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a Price &amp; Competitor Monitor That Diffs Shopify and AliExpress Prices</title>
      <dc:creator>Oaida Adrian</dc:creator>
      <pubDate>Wed, 19 Aug 2026 11:11:47 +0000</pubDate>
      <link>https://dev.to/darksider4all_afa2428f63d0/building-a-price-competitor-monitor-that-diffs-shopify-and-aliexpress-prices-519l</link>
      <guid>https://dev.to/darksider4all_afa2428f63d0/building-a-price-competitor-monitor-that-diffs-shopify-and-aliexpress-prices-519l</guid>
      <description>&lt;p&gt;Most price watching is a spreadsheet: paste a URL, check it today, forget it tomorrow. The moment you want a &lt;em&gt;history&lt;/em&gt; — what did this product cost a month ago, did it dip last Tuesday — snapshots are useless. I built an actor that keeps a real per-URL price timeline, diffs every run against the last one, and pings a webhook the instant something changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The design
&lt;/h2&gt;

&lt;p&gt;For each URL the actor:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fetches the product&lt;/strong&gt; — Shopify stores expose &lt;code&gt;/products/&amp;lt;handle&amp;gt;.json&lt;/code&gt; plus &lt;code&gt;/cart.js&lt;/code&gt; for the store currency; AliExpress item pages are client-rendered, so the price comes from the search page matched back by product id&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reads the previous snapshot&lt;/strong&gt; from a durable key-value store&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Computes the diff&lt;/strong&gt; — &lt;code&gt;NEW&lt;/code&gt;, &lt;code&gt;PRICE_CHANGED&lt;/code&gt;, &lt;code&gt;AVAILABILITY_CHANGED&lt;/code&gt;, or &lt;code&gt;UNCHANGED&lt;/code&gt;, with absolute and percentage change&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Appends the new point&lt;/strong&gt; to history, pruning anything older than &lt;code&gt;historyDays&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Emits one row per URL&lt;/strong&gt; with snapshot + diff + recent history, and POSTs a compact JSON alert to your webhook if the row changed&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first run of a URL is the baseline (&lt;code&gt;NEW&lt;/code&gt;, no alert). Alerts fire only on real changes after that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The persistence trap
&lt;/h2&gt;

&lt;p&gt;This is the part that bit: Apify's &lt;strong&gt;default key-value store is per-run for API-started runs&lt;/strong&gt; — it's purged at run start and deleted after the run. A naive implementation would lose its history between runs and re-emit everything as &lt;code&gt;NEW&lt;/code&gt; every time. The fix is a &lt;strong&gt;named store&lt;/strong&gt;: &lt;code&gt;Actor.open_key_value_store(name='price-competitor-monitor-history')&lt;/code&gt; (the &lt;code&gt;name=&lt;/code&gt; argument is keyword-only in SDK 3.4). That store persisted across three cloud runs in testing, which is what turns scheduled runs into a real timeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  The smoke test
&lt;/h2&gt;

&lt;p&gt;Three URLs — two Shopify stores and an AliExpress dock:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run 1: allbirds &lt;strong&gt;NEW 91.00 USD&lt;/strong&gt;, colourpop &lt;strong&gt;NEW 29.00 USD&lt;/strong&gt;, non-zero ✓ (the AliExpress item page was x5sec-challenged from the datacenter egress — honest error row, no false price)&lt;/li&gt;
&lt;li&gt;Run 2: both Shopify products &lt;strong&gt;UNCHANGED&lt;/strong&gt; — durable history proven in the cloud&lt;/li&gt;
&lt;li&gt;Run 3 (with a modified snapshot): colourpop &lt;strong&gt;PRICE_CHANGED, previous 36.25 → 29.00, −20%, direction down, alert true&lt;/strong&gt; ✓&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A fetch that fails cleanly (blocked page, deleted product) emits a &lt;code&gt;status: "error"&lt;/code&gt; row and leaves the previous snapshot untouched — a transient block never masquerades as a price crash.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest bits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AliExpress item pages are fully client-rendered: the price is recovered from the search page by product id, so an item absent from search is reported without a price until it reappears.&lt;/li&gt;
&lt;li&gt;Availability on AliExpress isn't reported yet (&lt;code&gt;available: null&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;History is capped at 500 points per URL as a safety limit.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://apify.com/darknezz/price-competitor-monitor" rel="noopener noreferrer"&gt;&lt;strong&gt;Price &amp;amp; Competitor Monitor on Apify Store&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More from me
&lt;/h2&gt;

&lt;p&gt;While you're here, these might be worth a read:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-stopped-scraping-business-directories-and-built-an-mcp-server-on-official-registry-data-4caf"&gt;I Stopped Scraping Business Directories and Built an MCP Server on Official Registry Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/scraping-romanian-public-contracts-a-native-language-tender-awarded-deals-scraper-4lob"&gt;Scraping Romanian Public Contracts: A Native-Language Tender &amp;amp; Awarded Deals Scraper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-12-city-us-building-permits-scraper-with-python-138b"&gt;Building a 12-City US Building Permits Scraper With Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-an-eu-safety-gate-rapex-product-recall-scraper-with-python-2m26"&gt;Building an EU Safety Gate (RAPEX) Product Recall Scraper With Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-i-built-a-water-utility-risk-intelligence-tool-with-python-and-mcp-1hj0"&gt;How I Built a Water Utility Risk Intelligence Tool With Python and MCP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-an-aviation-hub-api-airports-airlines-live-flights-weather-from-six-keyless-sources-58j9"&gt;Building an Aviation Hub API: Airports, Airlines, Live Flights &amp;amp; Weather From Six Keyless Sources&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-a-canada-product-recalls-safety-alerts-scraper-that-reads-open-government-data-3p59"&gt;I Built a Canada Product Recalls &amp;amp; Safety Alerts Scraper That Reads Open Government Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-a-telegram-members-scraper-that-reads-public-chat-stats-without-login-34mc"&gt;I Built a Telegram Members Scraper That Reads Public Chat Stats Without Login&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-whois-dns-lookup-tool-domain-intelligence-in-one-api-call-2n5b"&gt;Building a WHOIS &amp;amp; DNS Lookup Tool: Domain Intelligence in One...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-an-ai-web-crawler-that-outputs-llm-ready-content-chunks-4ghg"&gt;Building an AI Web Crawler That Outputs LLM-Ready Content Chunks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-real-time-press-release-monitor-with-python-and-rss-aggregation-76l"&gt;Building a Real-Time Press Release Monitor with Python and RSS...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-universal-property-listing-scraper-with-python-and-json-ld-3mdj"&gt;Building a Universal Property Listing Scraper with Python and ...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/tracking-tech-sentiment-in-real-time-with-vader-and-python-3adl"&gt;Tracking Tech Sentiment in Real-Time with VADER and Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-i-built-a-product-hunt-scraper-that-tracks-launches-in-real-time-jkn"&gt;How I Built a Product Hunt Scraper That Tracks Launches in Rea...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/5-apis-every-developer-needs-for-content-processing-rss-extraction-sitemaps-ai-2630"&gt;5 APIs Every Developer Needs for Content Processing (RSS, Extraction, Sitemaps, AI)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-to-extract-clean-content-from-any-website-sitemap-for-seo-audits-ai-training-15a9"&gt;How to Extract Clean Content From Any Website Sitemap (For SEO...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/scraping-187000-romanian-businesses-building-a-b2b-lead-generation-tool-176n"&gt;Scraping 187,000 Romanian Businesses: Building a B2B Lead Gene...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/make-any-website-ai-readable-generating-llmstxt-files-with-python-3jop"&gt;Make Any Website AI-Readable: Generating llms.txt Files with&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-an-rss-aggregator-that-extracts-full-article-content-not-just-summaries-ifl"&gt;I Built an RSS Aggregator That Extracts Full Article Content (...&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>webscraping</category>
      <category>ecommerce</category>
      <category>automation</category>
    </item>
    <item>
      <title>Building a Vertical Corpus Builder: Clean JSONL Datasets for LLM Fine-Tuning</title>
      <dc:creator>Oaida Adrian</dc:creator>
      <pubDate>Wed, 19 Aug 2026 11:11:46 +0000</pubDate>
      <link>https://dev.to/darksider4all_afa2428f63d0/building-a-vertical-corpus-builder-clean-jsonl-datasets-for-llm-fine-tuning-2p32</link>
      <guid>https://dev.to/darksider4all_afa2428f63d0/building-a-vertical-corpus-builder-clean-jsonl-datasets-for-llm-fine-tuning-2p32</guid>
      <description>&lt;p&gt;Raw web pages are terrible training data. Nav bars, cookie banners, "related articles" and ads drown the signal, and near-identical syndicated text pollutes the corpus. If you're fine-tuning a domain model — legal reasoning, medical QA, financial analysis — you want clean vertical text with provenance, not a pile of HTML. I built an actor that goes from seed URLs to a token-aware JSONL dataset in one run.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;seed URLs → crawl (same-domain BFS) → clean (boilerplate strip)
  → dedupe (exact + near) → token-aware chunk → JSONL with provenance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three steps matter most:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Boilerplate strip&lt;/strong&gt; — drop &lt;code&gt;script&lt;/code&gt;/&lt;code&gt;style&lt;/code&gt;/&lt;code&gt;nav&lt;/code&gt;/&lt;code&gt;footer&lt;/code&gt;/&lt;code&gt;header&lt;/code&gt;/&lt;code&gt;aside&lt;/code&gt; and utility blocks (breadcrumbs, shares, comments, menus). One detail that earned its keep: paragraphs where more than half the links are anchor text get dropped too — those are link farms, not content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dedupe, exact and near&lt;/strong&gt; — exact duplicates collapse by normalised hash; near-duplicates by 6-gram Jaccard similarity (default 0.95), so syndicated copies of the same opinion appear once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token-aware chunking&lt;/strong&gt; — chunks split at paragraph boundaries and hard-split on sentence/word boundaries only when a single paragraph exceeds the budget (default 512 tokens). Each chunk stays coherent, which is what fine-tuning actually wants.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every record carries &lt;code&gt;source&lt;/code&gt; (hostname), &lt;code&gt;url&lt;/code&gt;, &lt;code&gt;domain&lt;/code&gt; (your vertical), &lt;code&gt;title&lt;/code&gt;, and &lt;code&gt;chunk_index&lt;/code&gt; — so you can filter, cite, or re-weight the corpus later.&lt;/p&gt;

&lt;h2&gt;
  
  
  The seed-source reality check
&lt;/h2&gt;

&lt;p&gt;Not every legal text source works from a datacenter IP. CourtListener sits behind an AWS WAF JavaScript challenge, and Justia 403s datacenter egress — both unusable as plain-HTTP seeds. &lt;strong&gt;Cornell LII&lt;/strong&gt; (&lt;code&gt;law.cornell.edu/supremecourt/text/…&lt;/code&gt;) serves full opinions as clean HTML and works beautifully. The honest move was documenting that in the README and defaulting the examples to LII rather than pretending every source is reachable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The smoke test
&lt;/h2&gt;

&lt;p&gt;Three LII Supreme Court opinions (Dobbs, Bruen, WV v EPA):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local: 559 chunks, all 6 provenance keys present, chunk sizes 63–2,054 chars, zero duplicates after global dedupe&lt;/li&gt;
&lt;li&gt;Cloud (2 seeds): &lt;strong&gt;SUCCEEDED, 443 items&lt;/strong&gt;, every record with non-empty text, &lt;code&gt;domain=legal&lt;/code&gt;, source &lt;code&gt;law.cornell.edu&lt;/code&gt;, 0 duplicate texts&lt;/li&gt;
&lt;li&gt;KV store: &lt;code&gt;output.jsonl&lt;/code&gt; — 443 lines / ~811 KB, plus a &lt;code&gt;SUMMARY&lt;/code&gt; record: 443 chunks, 2 pages, ~173,000 estimated tokens, no failed sources&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The honest bits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Seed URLs must serve server-rendered HTML. JS-only SPAs and WAF'd sites need a browser-rendering actor instead.&lt;/li&gt;
&lt;li&gt;Chunk size is an estimate (~4 chars/token), not a byte-exact tokeniser count.&lt;/li&gt;
&lt;li&gt;Near-dedupe is O(n²) in chunk count — cap large crawls with &lt;code&gt;maxChunksPerPage&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The crawler follows same-domain links only; cross-domain citations aren't chased.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://apify.com/darknezz/vertical-corpus-builder" rel="noopener noreferrer"&gt;&lt;strong&gt;Vertical Corpora Builder on Apify Store&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More from me
&lt;/h2&gt;

&lt;p&gt;While you're here, these might be worth a read:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-stopped-scraping-business-directories-and-built-an-mcp-server-on-official-registry-data-4caf"&gt;I Stopped Scraping Business Directories and Built an MCP Server on Official Registry Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/scraping-romanian-public-contracts-a-native-language-tender-awarded-deals-scraper-4lob"&gt;Scraping Romanian Public Contracts: A Native-Language Tender &amp;amp; Awarded Deals Scraper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-12-city-us-building-permits-scraper-with-python-138b"&gt;Building a 12-City US Building Permits Scraper With Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-an-eu-safety-gate-rapex-product-recall-scraper-with-python-2m26"&gt;Building an EU Safety Gate (RAPEX) Product Recall Scraper With Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-i-built-a-water-utility-risk-intelligence-tool-with-python-and-mcp-1hj0"&gt;How I Built a Water Utility Risk Intelligence Tool With Python and MCP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-an-aviation-hub-api-airports-airlines-live-flights-weather-from-six-keyless-sources-58j9"&gt;Building an Aviation Hub API: Airports, Airlines, Live Flights &amp;amp; Weather From Six Keyless Sources&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-a-canada-product-recalls-safety-alerts-scraper-that-reads-open-government-data-3p59"&gt;I Built a Canada Product Recalls &amp;amp; Safety Alerts Scraper That Reads Open Government Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-a-telegram-members-scraper-that-reads-public-chat-stats-without-login-34mc"&gt;I Built a Telegram Members Scraper That Reads Public Chat Stats Without Login&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-whois-dns-lookup-tool-domain-intelligence-in-one-api-call-2n5b"&gt;Building a WHOIS &amp;amp; DNS Lookup Tool: Domain Intelligence in One...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-an-ai-web-crawler-that-outputs-llm-ready-content-chunks-4ghg"&gt;Building an AI Web Crawler That Outputs LLM-Ready Content Chunks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-real-time-press-release-monitor-with-python-and-rss-aggregation-76l"&gt;Building a Real-Time Press Release Monitor with Python and RSS...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-universal-property-listing-scraper-with-python-and-json-ld-3mdj"&gt;Building a Universal Property Listing Scraper with Python and ...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/tracking-tech-sentiment-in-real-time-with-vader-and-python-3adl"&gt;Tracking Tech Sentiment in Real-Time with VADER and Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-i-built-a-product-hunt-scraper-that-tracks-launches-in-real-time-jkn"&gt;How I Built a Product Hunt Scraper That Tracks Launches in Rea...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/5-apis-every-developer-needs-for-content-processing-rss-extraction-sitemaps-ai-2630"&gt;5 APIs Every Developer Needs for Content Processing (RSS, Extraction, Sitemaps, AI)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-to-extract-clean-content-from-any-website-sitemap-for-seo-audits-ai-training-15a9"&gt;How to Extract Clean Content From Any Website Sitemap (For SEO...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/scraping-187000-romanian-businesses-building-a-b2b-lead-generation-tool-176n"&gt;Scraping 187,000 Romanian Businesses: Building a B2B Lead Gene...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/make-any-website-ai-readable-generating-llmstxt-files-with-python-3jop"&gt;Make Any Website AI-Readable: Generating llms.txt Files with&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-an-rss-aggregator-that-extracts-full-article-content-not-just-summaries-ifl"&gt;I Built an RSS Aggregator That Extracts Full Article Content (...&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>llm</category>
      <category>data</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a Change &amp; Alert Engine That Webhooks Any Diff on Any URL</title>
      <dc:creator>Oaida Adrian</dc:creator>
      <pubDate>Wed, 19 Aug 2026 11:11:44 +0000</pubDate>
      <link>https://dev.to/darksider4all_afa2428f63d0/building-a-change-alert-engine-that-webhooks-any-diff-on-any-url-44ip</link>
      <guid>https://dev.to/darksider4all_afa2428f63d0/building-a-change-alert-engine-that-webhooks-any-diff-on-any-url-44ip</guid>
      <description>&lt;p&gt;Most data jobs re-pull the whole source every run and waste time and money on data that didn't move. A change-detection engine wakes up, checks what actually changed, and pushes only the diff. I built a generic one: point it at any URL — a JSON feed, an RSS/Atom feed, or a plain HTML page — and it emits only the changes, with before and after values, to the dataset and/or your webhook.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Poll&lt;/strong&gt; — fetch &lt;code&gt;sourceUrl&lt;/code&gt; once per run (the interval is your schedule; the actor is a single-shot poll)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parse&lt;/strong&gt; — content is sniffed automatically: JSON arrays used directly; object feeds auto-detect common list keys (&lt;code&gt;items&lt;/code&gt;, &lt;code&gt;results&lt;/code&gt;, &lt;code&gt;data&lt;/code&gt;, &lt;code&gt;records&lt;/code&gt;…) or take an &lt;code&gt;itemsPath&lt;/code&gt; dot path; RSS/Atom parsed with stable ids (guid or link); HTML becomes one monitored item keyed on a content hash&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diff&lt;/strong&gt; — each item gets a stable &lt;code&gt;item_id&lt;/code&gt; (from &lt;code&gt;idField&lt;/code&gt;, natural keys like &lt;code&gt;guid&lt;/code&gt;/&lt;code&gt;link&lt;/code&gt;/&lt;code&gt;url&lt;/code&gt;/&lt;code&gt;title&lt;/code&gt;, or a content-hash fallback) and a content hash. New id → &lt;code&gt;added&lt;/code&gt;; same id, different hash → &lt;code&gt;modified&lt;/code&gt; with full before/after; id missing → &lt;code&gt;removed&lt;/code&gt; (only with &lt;code&gt;includeRemovals: true&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Emit&lt;/strong&gt; — changed items go to the dataset, one per item, plus one batched webhook POST per run that has changes. &lt;strong&gt;Nothing is emitted on a clean poll.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The delivery guarantee
&lt;/h2&gt;

&lt;p&gt;At-least-once is the detail I care most about: state is persisted &lt;strong&gt;only after a successful webhook POST&lt;/strong&gt; (or immediately when no webhook is configured). If the POST fails, the run FAILS and the next run re-emits the same changes — an alert is never silently lost. Within a run, duplicate item ids collapse to the last occurrence, so a feed that repeats entries won't double-notify.&lt;/p&gt;

&lt;h2&gt;
  
  
  The persistence trap
&lt;/h2&gt;

&lt;p&gt;Same lesson as the sibling price monitor: Apify's &lt;strong&gt;default key-value store is per-run for API-started runs&lt;/strong&gt;, so state kept there vanishes between runs and the second run re-emits everything as &lt;code&gt;added&lt;/code&gt;. The fix is a named store — &lt;code&gt;Actor.open_key_value_store(name='change-alert-state')&lt;/code&gt; (keyword-only &lt;code&gt;name=&lt;/code&gt; in SDK 3.4, account-scoped, persists across runs).&lt;/p&gt;

&lt;h2&gt;
  
  
  The smoke test
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BBC News RSS&lt;/strong&gt;: run 1 → &lt;strong&gt;41 added&lt;/strong&gt; (real titles and links); run 2 → &lt;strong&gt;0 items, zero changes&lt;/strong&gt; ✓&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Controlled changed source&lt;/strong&gt; (an Apify dataset with a pre-signed public URL — gist raw CDN served stale content to datacenter IPs, so a dataset is the deterministic route): run A → 2 added; append a modified g2 and a new g3 → run B → &lt;strong&gt;modified g2 (price 20 → 25) + added g3&lt;/strong&gt;; run C → 0 ✓&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The honest bits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The engine compares current fetch vs last persisted fetch — items that leave the feed are only reported with &lt;code&gt;includeRemovals: true&lt;/code&gt; (off by default, because first-page rotation is often noise).&lt;/li&gt;
&lt;li&gt;JSON items without any natural id fall back to a content hash; any edit then looks like an add+remove pair — set &lt;code&gt;idField&lt;/code&gt; for clean &lt;code&gt;modified&lt;/code&gt; detection.&lt;/li&gt;
&lt;li&gt;Feeds with per-item volatile timestamps can look "modified" every poll — list those fields in &lt;code&gt;ignoreFields&lt;/code&gt; to hash only what matters.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://apify.com/darknezz/change-alert-engine" rel="noopener noreferrer"&gt;&lt;strong&gt;Change &amp;amp; Alert Engine on Apify Store&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More from me
&lt;/h2&gt;

&lt;p&gt;While you're here, these might be worth a read:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-stopped-scraping-business-directories-and-built-an-mcp-server-on-official-registry-data-4caf"&gt;I Stopped Scraping Business Directories and Built an MCP Server on Official Registry Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/scraping-romanian-public-contracts-a-native-language-tender-awarded-deals-scraper-4lob"&gt;Scraping Romanian Public Contracts: A Native-Language Tender &amp;amp; Awarded Deals Scraper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-12-city-us-building-permits-scraper-with-python-138b"&gt;Building a 12-City US Building Permits Scraper With Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-an-eu-safety-gate-rapex-product-recall-scraper-with-python-2m26"&gt;Building an EU Safety Gate (RAPEX) Product Recall Scraper With Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-i-built-a-water-utility-risk-intelligence-tool-with-python-and-mcp-1hj0"&gt;How I Built a Water Utility Risk Intelligence Tool With Python and MCP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-an-aviation-hub-api-airports-airlines-live-flights-weather-from-six-keyless-sources-58j9"&gt;Building an Aviation Hub API: Airports, Airlines, Live Flights &amp;amp; Weather From Six Keyless Sources&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-a-canada-product-recalls-safety-alerts-scraper-that-reads-open-government-data-3p59"&gt;I Built a Canada Product Recalls &amp;amp; Safety Alerts Scraper That Reads Open Government Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-a-telegram-members-scraper-that-reads-public-chat-stats-without-login-34mc"&gt;I Built a Telegram Members Scraper That Reads Public Chat Stats Without Login&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-whois-dns-lookup-tool-domain-intelligence-in-one-api-call-2n5b"&gt;Building a WHOIS &amp;amp; DNS Lookup Tool: Domain Intelligence in One...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-an-ai-web-crawler-that-outputs-llm-ready-content-chunks-4ghg"&gt;Building an AI Web Crawler That Outputs LLM-Ready Content Chunks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-real-time-press-release-monitor-with-python-and-rss-aggregation-76l"&gt;Building a Real-Time Press Release Monitor with Python and RSS...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-universal-property-listing-scraper-with-python-and-json-ld-3mdj"&gt;Building a Universal Property Listing Scraper with Python and ...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/tracking-tech-sentiment-in-real-time-with-vader-and-python-3adl"&gt;Tracking Tech Sentiment in Real-Time with VADER and Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-i-built-a-product-hunt-scraper-that-tracks-launches-in-real-time-jkn"&gt;How I Built a Product Hunt Scraper That Tracks Launches in Rea...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/5-apis-every-developer-needs-for-content-processing-rss-extraction-sitemaps-ai-2630"&gt;5 APIs Every Developer Needs for Content Processing (RSS, Extraction, Sitemaps, AI)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-to-extract-clean-content-from-any-website-sitemap-for-seo-audits-ai-training-15a9"&gt;How to Extract Clean Content From Any Website Sitemap (For SEO...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/scraping-187000-romanian-businesses-building-a-b2b-lead-generation-tool-176n"&gt;Scraping 187,000 Romanian Businesses: Building a B2B Lead Gene...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/make-any-website-ai-readable-generating-llmstxt-files-with-python-3jop"&gt;Make Any Website AI-Readable: Generating llms.txt Files with&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-an-rss-aggregator-that-extracts-full-article-content-not-just-summaries-ifl"&gt;I Built an RSS Aggregator That Extracts Full Article Content (...&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>webscraping</category>
      <category>automation</category>
      <category>api</category>
    </item>
    <item>
      <title>Building a Romania Mall Store Directory: Every Store in 29 Malls, One Schema</title>
      <dc:creator>Oaida Adrian</dc:creator>
      <pubDate>Wed, 19 Aug 2026 11:11:43 +0000</pubDate>
      <link>https://dev.to/darksider4all_afa2428f63d0/building-a-romania-mall-store-directory-every-store-in-29-malls-one-schema-2fhb</link>
      <guid>https://dev.to/darksider4all_afa2428f63d0/building-a-romania-mall-store-directory-every-store-in-29-malls-one-schema-2fhb</guid>
      <description>&lt;p&gt;Romania's shopping malls publish their store directories, but they publish them in eight different ways: one operator runs a headless CMS, another a WordPress store list, another a Laravel JSON blob. Nobody publishes the whole country. So I built an actor that crawls every major operator and emits every store as one consistent record.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap
&lt;/h2&gt;

&lt;p&gt;If you want the store mix of every mall in Romania — for retail analytics, site selection, or leasing research — you'd normally stitch together NEPI's site, AFI's site, Iulius's site, and so on, and still end up with per-operator formats that don't join. Worse, nobody flags the anchor tenants: the hypermarkets, cinemas and department stores that actually drive foot traffic. That's the gap: a country-wide directory with one schema and anchor flags built in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The sources
&lt;/h2&gt;

&lt;p&gt;Each operator publishes its own public store data, and each needed a different reverse-engineering pass:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NEPI Rockcastle&lt;/strong&gt; (19 centres, ~2,434 tenants) — their own Strapi CMS JSON API (&lt;code&gt;cms.&amp;lt;site&amp;gt;/api/tenants&lt;/code&gt;), including Mega Mall's 215 stores and Promenada's 182&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AFI Europe&lt;/strong&gt; (Cotroceni, Ploiești) — the &lt;code&gt;evx_retailers&lt;/code&gt; sitemap&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iulius Group&lt;/strong&gt; (Cluj, Iași, Suceava, Iulius Town) — a shopping index mixing category and store links, disambiguated by nesting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sun Plaza, Veranda, Colosseum&lt;/strong&gt; — WordPress store sitemaps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Băneasa&lt;/strong&gt; — a Laravel/Inertia &lt;code&gt;data-page&lt;/code&gt; JSON payload across 11 pages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The robots.txt lesson deserves a callout: fetching it with Python's default &lt;code&gt;RobotFileParser&lt;/code&gt; gets you 403'd by Sun Plaza and Veranda's WAFs — empty rules, everything disallowed. Fetching it with requests and a browser User-Agent works, and the actor then honours the real rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  The smoke test
&lt;/h2&gt;

&lt;p&gt;Input &lt;code&gt;{"country":"ro","malls":["all"],"includeAnchor":true}&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HTTP 201, 3,868 well-formed records in ~86 seconds&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;0 empty store names, 0 malformed key sets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;382 anchor tenants flagged&lt;/strong&gt;, 2,495 records with floor/unit, 1,499 with phone&lt;/li&gt;
&lt;li&gt;29 of 30 registered malls covered&lt;/li&gt;
&lt;li&gt;All 10 identity fields (mall, store, brand, category, url, …) 100% filled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run it again and the &lt;code&gt;first_seen&lt;/code&gt; / &lt;code&gt;last_seen&lt;/code&gt; / &lt;code&gt;is_closed&lt;/code&gt; fields turn the directory into a vacancy and churn time-series — which stores opened, which closed, which anchors moved.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest bits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ParkLake is WAF-gapped from the cloud.&lt;/strong&gt; Sonae Sierra serves an HTTP 202 JavaScript-challenge page to datacenter IPs for its API, sitemap and archive alike; even curl_cffi impersonation hits the wall. From a residential or proxy IP the REST API returns ~223 stores with full detail. The adapter tries hard, then logs the challenge honestly instead of faking data — attach a residential proxy if you need ParkLake from the cloud.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;is_closed&lt;/code&gt; only becomes meaningful from the &lt;strong&gt;second&lt;/strong&gt; run — the first run is your baseline.&lt;/li&gt;
&lt;li&gt;Fields an operator doesn't publish come back as empty strings, so exports stay clean but sometimes thin.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://apify.com/darknezz/ro-mall-store-directory" rel="noopener noreferrer"&gt;&lt;strong&gt;Romania Mall Store Directory on Apify Store&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More from me
&lt;/h2&gt;

&lt;p&gt;While you're here, these might be worth a read:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-stopped-scraping-business-directories-and-built-an-mcp-server-on-official-registry-data-4caf"&gt;I Stopped Scraping Business Directories and Built an MCP Server on Official Registry Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/scraping-romanian-public-contracts-a-native-language-tender-awarded-deals-scraper-4lob"&gt;Scraping Romanian Public Contracts: A Native-Language Tender &amp;amp; Awarded Deals Scraper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-12-city-us-building-permits-scraper-with-python-138b"&gt;Building a 12-City US Building Permits Scraper With Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-an-eu-safety-gate-rapex-product-recall-scraper-with-python-2m26"&gt;Building an EU Safety Gate (RAPEX) Product Recall Scraper With Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-i-built-a-water-utility-risk-intelligence-tool-with-python-and-mcp-1hj0"&gt;How I Built a Water Utility Risk Intelligence Tool With Python and MCP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-an-aviation-hub-api-airports-airlines-live-flights-weather-from-six-keyless-sources-58j9"&gt;Building an Aviation Hub API: Airports, Airlines, Live Flights &amp;amp; Weather From Six Keyless Sources&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-a-canada-product-recalls-safety-alerts-scraper-that-reads-open-government-data-3p59"&gt;I Built a Canada Product Recalls &amp;amp; Safety Alerts Scraper That Reads Open Government Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-a-telegram-members-scraper-that-reads-public-chat-stats-without-login-34mc"&gt;I Built a Telegram Members Scraper That Reads Public Chat Stats Without Login&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-whois-dns-lookup-tool-domain-intelligence-in-one-api-call-2n5b"&gt;Building a WHOIS &amp;amp; DNS Lookup Tool: Domain Intelligence in One...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-an-ai-web-crawler-that-outputs-llm-ready-content-chunks-4ghg"&gt;Building an AI Web Crawler That Outputs LLM-Ready Content Chunks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-real-time-press-release-monitor-with-python-and-rss-aggregation-76l"&gt;Building a Real-Time Press Release Monitor with Python and RSS...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-universal-property-listing-scraper-with-python-and-json-ld-3mdj"&gt;Building a Universal Property Listing Scraper with Python and ...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/tracking-tech-sentiment-in-real-time-with-vader-and-python-3adl"&gt;Tracking Tech Sentiment in Real-Time with VADER and Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-i-built-a-product-hunt-scraper-that-tracks-launches-in-real-time-jkn"&gt;How I Built a Product Hunt Scraper That Tracks Launches in Rea...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/5-apis-every-developer-needs-for-content-processing-rss-extraction-sitemaps-ai-2630"&gt;5 APIs Every Developer Needs for Content Processing (RSS, Extraction, Sitemaps, AI)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-to-extract-clean-content-from-any-website-sitemap-for-seo-audits-ai-training-15a9"&gt;How to Extract Clean Content From Any Website Sitemap (For SEO...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/scraping-187000-romanian-businesses-building-a-b2b-lead-generation-tool-176n"&gt;Scraping 187,000 Romanian Businesses: Building a B2B Lead Gene...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/make-any-website-ai-readable-generating-llmstxt-files-with-python-3jop"&gt;Make Any Website AI-Readable: Generating llms.txt Files with&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-an-rss-aggregator-that-extracts-full-article-content-not-just-summaries-ifl"&gt;I Built an RSS Aggregator That Extracts Full Article Content (...&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>webscraping</category>
      <category>data</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Scraping Romanian Public Contracts: A Native-Language Tender &amp; Awarded Deals Scraper</title>
      <dc:creator>Oaida Adrian</dc:creator>
      <pubDate>Sun, 16 Aug 2026 19:30:10 +0000</pubDate>
      <link>https://dev.to/darksider4all_afa2428f63d0/scraping-romanian-public-contracts-a-native-language-tender-awarded-deals-scraper-4lob</link>
      <guid>https://dev.to/darksider4all_afa2428f63d0/scraping-romanian-public-contracts-a-native-language-tender-awarded-deals-scraper-4lob</guid>
      <description>&lt;p&gt;Romania publishes every public tender and awarded contract through e-licitatie.ro — the successor to the old SEAP system. The data is public, the opportunities are real (millions of RON in awards every month), and almost nobody outside Romania is building on it. Being native-language gives us an edge no English-first competitor has.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap
&lt;/h2&gt;

&lt;p&gt;The niche's existing actors are thin: the top reference has single-digit runs. Tenders come with deadlines and award values, but most scrapers stop at the listing page. Ours goes deeper — tenders &lt;strong&gt;and&lt;/strong&gt; awarded contracts, with detail enrichment, CPV classification, and winner + CUI (company ID) extraction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The source
&lt;/h2&gt;

&lt;p&gt;The Angular SPA at e-licitatie.ro calls its own JSON API — but it 403s without the right headers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST api-pub/NoticeCommon/GetCNoticeListFiltered/   # tenders
POST api-pub/NoticeCommon/GetCANoticeList/          # awarded contracts
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trick: the API requires a &lt;code&gt;Referer&lt;/code&gt; of &lt;code&gt;https://e-licitatie.ro/pub&lt;/code&gt; plus &lt;code&gt;Origin&lt;/code&gt; and &lt;code&gt;X-Requested-With&lt;/code&gt;. Missing referer, instant 403 "Access Denied: Referrer cannot be null". Detail enrichment needs &lt;code&gt;GetSection21View&lt;/code&gt; (description, CPV, estimated value — keyed off the item's own &lt;code&gt;sysNoticeTypeId&lt;/code&gt;, not the noticeId) and &lt;code&gt;GetSection1View&lt;/code&gt; for the contracting authority. Winners come from &lt;code&gt;GetCANoticeContracts&lt;/code&gt; — and its filter payload is strict: a bare payload 400s.&lt;/p&gt;

&lt;h2&gt;
  
  
  The native-language advantage
&lt;/h2&gt;

&lt;p&gt;Romanian titles, Romanian descriptions, CPV codes joined to EU TED. The reference actors are English-first and miss the nuance — e.g., distinguishing &lt;em&gt;licitație deschisă&lt;/em&gt; (open tender) from &lt;em&gt;achiziție directă&lt;/em&gt; (direct award). Our output carries the original Romanian fields untouched, so downstream users can do their own analysis without a translation layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The smoke test
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tenders&lt;/strong&gt;: keyword &lt;code&gt;infrastructura&lt;/code&gt;, dateFrom 2026-01-01 → &lt;strong&gt;5 items, 5/5 complete&lt;/strong&gt; — real titles, authorities, CPV names, estimated RON values, deadlines, status&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Awarded&lt;/strong&gt;: keyword &lt;code&gt;drumuri&lt;/code&gt;, dateFrom 2026-06-01 → &lt;strong&gt;4 items&lt;/strong&gt;, including winner &lt;strong&gt;ROUTE CENTER CONSTRUCT (CUI 29170569)&lt;/strong&gt; with a contract value of &lt;strong&gt;4,822,401.88 RON&lt;/strong&gt; and an award date&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The honest bits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Authority filtering is client-side containment — the API ignores authority text server-side&lt;/li&gt;
&lt;li&gt;Some CAN (award) records show zero value and no winner — that's data-side, the tender was annulled&lt;/li&gt;
&lt;li&gt;Publication dates are what the portal publishes; actual contract signatures can lag&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://apify.com/darknezz/ro-public-contracts-scraper" rel="noopener noreferrer"&gt;&lt;strong&gt;Romanian Public Contracts Scraper on Apify Store&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More from me
&lt;/h2&gt;

&lt;p&gt;While you're here, these might be worth a read:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-a-canada-product-recalls-safety-alerts-scraper-that-reads-open-government-data-3p59"&gt;I Built a Canada Product Recalls &amp;amp; Safety Alerts Scraper That Reads Open Government Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-a-telegram-members-scraper-that-reads-public-chat-stats-without-login-34mc"&gt;I Built a Telegram Members Scraper That Reads Public Chat Stats Without Login&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-whois-dns-lookup-tool-domain-intelligence-in-one-api-call-2n5b"&gt;Building a WHOIS &amp;amp; DNS Lookup Tool: Domain Intelligence in One API Call&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-an-ai-web-crawler-that-outputs-llm-ready-content-chunks-4ghg"&gt;Building an AI Web Crawler That Outputs LLM-Ready Content Chunks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-real-time-press-release-monitor-with-python-and-rss-aggregation-76l"&gt;Building a Real-Time Press Release Monitor with Python and RSS Aggregation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-universal-property-listing-scraper-with-python-and-json-ld-3mdj"&gt;Building a Universal Property Listing Scraper with Python and JSON-LD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/tracking-tech-sentiment-in-real-time-with-vader-and-python-3adl"&gt;Tracking Tech Sentiment in Real-Time with VADER and Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-i-built-a-product-hunt-scraper-that-tracks-launches-in-real-time-jkn"&gt;How I Built a Product Hunt Scraper That Tracks Launches in Real-Time&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/5-apis-every-developer-needs-for-content-processing-rss-extraction-sitemaps-ai-2630"&gt;5 APIs Every Developer Needs for Content Processing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-to-extract-clean-content-from-any-website-sitemap-for-seo-audits-ai-training-15a9"&gt;How to Extract Clean Content From Any Website Sitemap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/scraping-187000-romanian-businesses-building-a-b2b-lead-generation-tool-176n"&gt;Scraping 187,000 Romanian Businesses: Building a B2B Lead Generation Tool&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/make-any-website-ai-readable-generating-llmstxt-files-with-python-3jop"&gt;Make Any Website AI-Readable: Generating llms.txt Files with Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-an-rss-aggregator-that-extracts-full-article-content-not-just-summaries-ifl"&gt;I Built an RSS Aggregator That Extracts Full Article Content&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>webscraping</category>
      <category>opensource</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a 12-City US Building Permits Scraper With Python</title>
      <dc:creator>Oaida Adrian</dc:creator>
      <pubDate>Sun, 16 Aug 2026 19:30:09 +0000</pubDate>
      <link>https://dev.to/darksider4all_afa2428f63d0/building-a-12-city-us-building-permits-scraper-with-python-138b</link>
      <guid>https://dev.to/darksider4all_afa2428f63d0/building-a-12-city-us-building-permits-scraper-with-python-138b</guid>
      <description>&lt;p&gt;Building permits are the earliest public signal of construction activity — new housing, commercial builds, renovation waves. Every major US city publishes them, but each through a different portal with a different schema. I built one scraper that covers twelve of them with a single output shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap
&lt;/h2&gt;

&lt;p&gt;The reference actor in this niche covers the big cities but has blind spots: in its own all-cities run it scanned &lt;strong&gt;zero&lt;/strong&gt; rows for Washington DC and never even attempted West Sacramento. Ours covers all twelve: ten Socrata portals (Chicago, NYC, LA, San Francisco, Austin, Seattle, Cincinnati, Mesa, Montgomery County, Baton Rouge) plus two ArcGIS servers (DC, West Sacramento).&lt;/p&gt;

&lt;h2&gt;
  
  
  The reverse-engineering
&lt;/h2&gt;

&lt;p&gt;Two very different API families to reconcile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Socrata&lt;/strong&gt; — the well-documented &lt;code&gt;resource&lt;/code&gt; endpoint family, clean JSON&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ArcGIS&lt;/strong&gt; — the old FeatureServer contract, where field names vary per server and some servers reject modern ordering parameters outright&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The resilience ladder for the old ArcGIS servers: try &lt;code&gt;TIMESTAMP&lt;/code&gt; + &lt;code&gt;orderBy&lt;/code&gt; first, fall back to &lt;code&gt;where&lt;/code&gt;-only queries, then to unfiltered pulls with client-side date filtering. West Sacramento's server rejects &lt;code&gt;TIMESTAMP&lt;/code&gt; entirely — the ladder is what makes it work.&lt;/p&gt;

&lt;h2&gt;
  
  
  One schema to rule them all
&lt;/h2&gt;

&lt;p&gt;Eighteen canonical keys: city, permit_number, permit_type, status, issue_date, application_date, address, work_description, valuation, contractor_name, applicant, latitude, longitude, source, source_type, source_url, scraped_at, cityKey. NYC's "Permit is not yet issued" placeholder rows get filtered. Adding a city is one entry in the config table.&lt;/p&gt;

&lt;h2&gt;
  
  
  The smoke test
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;24/24 non-zero&lt;/strong&gt; — two runs per city across all twelve, full 18-key schema on every item, plus local unit tests covering ten Socrata normalisations, both ArcGIS servers, and live fetches.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest bits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Coverage is US-only and city-scoped — counties and rural areas aren't in this pass&lt;/li&gt;
&lt;li&gt;Valuation can be null on older records; some cities simply don't publish it&lt;/li&gt;
&lt;li&gt;ArcGIS servers rate-limit; the ladder paces requests per city&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://apify.com/darknezz/building-permits-scraper" rel="noopener noreferrer"&gt;&lt;strong&gt;Building Permits Scraper on Apify Store&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More from me
&lt;/h2&gt;

&lt;p&gt;While you're here, these might be worth a read:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-a-canada-product-recalls-safety-alerts-scraper-that-reads-open-government-data-3p59"&gt;I Built a Canada Product Recalls &amp;amp; Safety Alerts Scraper That Reads Open Government Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-a-telegram-members-scraper-that-reads-public-chat-stats-without-login-34mc"&gt;I Built a Telegram Members Scraper That Reads Public Chat Stats Without Login&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-whois-dns-lookup-tool-domain-intelligence-in-one-api-call-2n5b"&gt;Building a WHOIS &amp;amp; DNS Lookup Tool: Domain Intelligence in One API Call&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-an-ai-web-crawler-that-outputs-llm-ready-content-chunks-4ghg"&gt;Building an AI Web Crawler That Outputs LLM-Ready Content Chunks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-real-time-press-release-monitor-with-python-and-rss-aggregation-76l"&gt;Building a Real-Time Press Release Monitor with Python and RSS Aggregation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-universal-property-listing-scraper-with-python-and-json-ld-3mdj"&gt;Building a Universal Property Listing Scraper with Python and JSON-LD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/tracking-tech-sentiment-in-real-time-with-vader-and-python-3adl"&gt;Tracking Tech Sentiment in Real-Time with VADER and Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-i-built-a-product-hunt-scraper-that-tracks-launches-in-real-time-jkn"&gt;How I Built a Product Hunt Scraper That Tracks Launches in Real-Time&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/5-apis-every-developer-needs-for-content-processing-rss-extraction-sitemaps-ai-2630"&gt;5 APIs Every Developer Needs for Content Processing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-to-extract-clean-content-from-any-website-sitemap-for-seo-audits-ai-training-15a9"&gt;How to Extract Clean Content From Any Website Sitemap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/scraping-187000-romanian-businesses-building-a-b2b-lead-generation-tool-176n"&gt;Scraping 187,000 Romanian Businesses: Building a B2B Lead Generation Tool&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/make-any-website-ai-readable-generating-llmstxt-files-with-python-3jop"&gt;Make Any Website AI-Readable: Generating llms.txt Files with Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-an-rss-aggregator-that-extracts-full-article-content-not-just-summaries-ifl"&gt;I Built an RSS Aggregator That Extracts Full Article Content&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>webscraping</category>
      <category>opensource</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building an EU Safety Gate (RAPEX) Product Recall Scraper With Python</title>
      <dc:creator>Oaida Adrian</dc:creator>
      <pubDate>Sun, 16 Aug 2026 19:30:08 +0000</pubDate>
      <link>https://dev.to/darksider4all_afa2428f63d0/building-an-eu-safety-gate-rapex-product-recall-scraper-with-python-2m26</link>
      <guid>https://dev.to/darksider4all_afa2428f63d0/building-an-eu-safety-gate-rapex-product-recall-scraper-with-python-2m26</guid>
      <description>&lt;p&gt;Every week the EU publishes the product recalls that keep dangerous goods off the shelves — the Safety Gate (RAPEX) portal. It's open data, but it's buried behind a JavaScript SPA, so most teams never build on it. I did.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap
&lt;/h2&gt;

&lt;p&gt;The reference actor in this niche couldn't even be trialled: it carries a $5 minimum charge and our account had $4.69 of budget left. One dollar short, locked out. So instead of paying to watch someone else's implementation, I reverse-engineered the upstream directly — a strictly better outcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  The source
&lt;/h2&gt;

&lt;p&gt;The EU Safety Gate portal exposes a &lt;strong&gt;pure JSON API&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;POST /public/api/search&lt;/code&gt; — the search contract&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /public/api/notification/{id}?language=en&lt;/code&gt; — per-alert detail&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No auth, no cookies, no browser. I captured the exact endpoints and payloads from the live SPA's network traffic, then ground-truthed the date semantics against real notifications.&lt;/p&gt;

&lt;h2&gt;
  
  
  The smoke test
&lt;/h2&gt;

&lt;p&gt;Input &lt;code&gt;{category: "Electrical appliances and equipment", country: "DE", dateFrom: "2026-07-01"}&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SUCCEEDED, 17 items, 17/17 complete&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Every record has reference, productName, riskTypes, riskDescription&lt;/li&gt;
&lt;li&gt;Unique reference numbers, date window exactly &lt;code&gt;2026-07-01..2026-08-11&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run cost: &lt;strong&gt;$0.007&lt;/strong&gt; — seven-tenths of a cent for 17 recalls&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The honest bits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The API covers consumer products, vehicles, and some professional equipment — not medical devices or food&lt;/li&gt;
&lt;li&gt;Search is a POST contract, so you need the right payload shape; a bare GET returns nothing&lt;/li&gt;
&lt;li&gt;Risk descriptions are free text in the EU's own taxonomy — normalise carefully&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://apify.com/darknezz/safety-gate-scraper" rel="noopener noreferrer"&gt;&lt;strong&gt;EU Safety Gate (RAPEX) Product Recall Scraper on Apify Store&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More from me
&lt;/h2&gt;

&lt;p&gt;While you're here, these might be worth a read:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-a-canada-product-recalls-safety-alerts-scraper-that-reads-open-government-data-3p59"&gt;I Built a Canada Product Recalls &amp;amp; Safety Alerts Scraper That Reads Open Government Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-a-telegram-members-scraper-that-reads-public-chat-stats-without-login-34mc"&gt;I Built a Telegram Members Scraper That Reads Public Chat Stats Without Login&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-whois-dns-lookup-tool-domain-intelligence-in-one-api-call-2n5b"&gt;Building a WHOIS &amp;amp; DNS Lookup Tool: Domain Intelligence in One API Call&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-an-ai-web-crawler-that-outputs-llm-ready-content-chunks-4ghg"&gt;Building an AI Web Crawler That Outputs LLM-Ready Content Chunks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-real-time-press-release-monitor-with-python-and-rss-aggregation-76l"&gt;Building a Real-Time Press Release Monitor with Python and RSS Aggregation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-universal-property-listing-scraper-with-python-and-json-ld-3mdj"&gt;Building a Universal Property Listing Scraper with Python and JSON-LD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/tracking-tech-sentiment-in-real-time-with-vader-and-python-3adl"&gt;Tracking Tech Sentiment in Real-Time with VADER and Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-i-built-a-product-hunt-scraper-that-tracks-launches-in-real-time-jkn"&gt;How I Built a Product Hunt Scraper That Tracks Launches in Real-Time&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/5-apis-every-developer-needs-for-content-processing-rss-extraction-sitemaps-ai-2630"&gt;5 APIs Every Developer Needs for Content Processing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-to-extract-clean-content-from-any-website-sitemap-for-seo-audits-ai-training-15a9"&gt;How to Extract Clean Content From Any Website Sitemap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/scraping-187000-romanian-businesses-building-a-b2b-lead-generation-tool-176n"&gt;Scraping 187,000 Romanian Businesses: Building a B2B Lead Generation Tool&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/make-any-website-ai-readable-generating-llmstxt-files-with-python-3jop"&gt;Make Any Website AI-Readable: Generating llms.txt Files with Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-an-rss-aggregator-that-extracts-full-article-content-not-just-summaries-ifl"&gt;I Built an RSS Aggregator That Extracts Full Article Content&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>webscraping</category>
      <category>opensource</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How I Built a Water Utility Risk Intelligence Tool With Python and MCP</title>
      <dc:creator>Oaida Adrian</dc:creator>
      <pubDate>Sun, 16 Aug 2026 19:30:07 +0000</pubDate>
      <link>https://dev.to/darksider4all_afa2428f63d0/how-i-built-a-water-utility-risk-intelligence-tool-with-python-and-mcp-1hj0</link>
      <guid>https://dev.to/darksider4all_afa2428f63d0/how-i-built-a-water-utility-risk-intelligence-tool-with-python-and-mcp-1hj0</guid>
      <description>&lt;p&gt;Water utilities fail slowly: aging pipes, drought pressure, unaffordable rates, crumbling infrastructure. The data to assess all of it is public — you just need to pull nine different government APIs and score what comes back. That's the tool I built.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap
&lt;/h2&gt;

&lt;p&gt;The niche's reference actor is MCP-standby-only: its batch runs emit &lt;strong&gt;zero dataset items&lt;/strong&gt;. It's an MCP server you call by hand — fine for a demo, useless for monitoring a portfolio of utilities. Our build fixes that: &lt;strong&gt;batch mode returns a full assessment item per utility&lt;/strong&gt;, and the engine is also exposed as eight fastmcp MCP tools for interactive use.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scoring engine
&lt;/h2&gt;

&lt;p&gt;Four transparent models, one composite score, five verdicts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Water Vulnerability&lt;/strong&gt; — contamination and source risk&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure&lt;/strong&gt; — pipe age and condition signals&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drought/Climate&lt;/strong&gt; — precipitation and heat trends&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Affordability&lt;/strong&gt; — rate and income pressure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Composite weights: vulnerability 0.30, infrastructure 0.25, drought 0.25, affordability 0.20. Verdicts: CRITICAL ≥80, HIGH_RISK ≥60, ELEVATED ≥40, MANAGEABLE ≥20, LOW_RISK &amp;lt;20. Every signal that moved a score is returned in the output — no black-box scoring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nine keyless upstreams
&lt;/h2&gt;

&lt;p&gt;USGS earthquakes, FEMA disaster declarations (v2 — the v1 endpoint 404s), NOAA weather alerts (custom UA required), Federal Register water-scoped notices, CFPB complaints (default UA only — a custom UA gets 403, a lovely asymmetry), BLS unemployment + CPI, World Bank precipitation/GINI indicators, and Open-Meteo geocoding. OpenAQ v3 needs a key, so it's an optional input that degrades gracefully.&lt;/p&gt;

&lt;h2&gt;
  
  
  The smoke test
&lt;/h2&gt;

&lt;p&gt;Local engine: NYC → &lt;strong&gt;26 MANAGEABLE&lt;/strong&gt;, Flint → &lt;strong&gt;21 MANAGEABLE&lt;/strong&gt;, Sacramento → &lt;strong&gt;26 MANAGEABLE&lt;/strong&gt; with an aging-infrastructure signal. Cloud: NYC full assessment → composite 26 with all four dimensions populated; Phoenix drought → &lt;strong&gt;LOW_RISK 8&lt;/strong&gt; with a real NOAA heat signal. The MCP server boots via fastmcp, lists all eight tools, and executes end-to-end.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest bits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It's a screening tool, not an engineering audit — a composite score won't tell you which valve is rusting&lt;/li&gt;
&lt;li&gt;OpenAQ is keyed and optional; the other eight sources are keyless by design&lt;/li&gt;
&lt;li&gt;Some upstreams rate-limit aggressively; the batch mode paces requests&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://apify.com/darknezz/water-utility-risk-intelligence" rel="noopener noreferrer"&gt;&lt;strong&gt;Water Utility Risk Intelligence on Apify Store&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More from me
&lt;/h2&gt;

&lt;p&gt;While you're here, these might be worth a read:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-a-canada-product-recalls-safety-alerts-scraper-that-reads-open-government-data-3p59"&gt;I Built a Canada Product Recalls &amp;amp; Safety Alerts Scraper That Reads Open Government Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-a-telegram-members-scraper-that-reads-public-chat-stats-without-login-34mc"&gt;I Built a Telegram Members Scraper That Reads Public Chat Stats Without Login&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-whois-dns-lookup-tool-domain-intelligence-in-one-api-call-2n5b"&gt;Building a WHOIS &amp;amp; DNS Lookup Tool: Domain Intelligence in One API Call&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-an-ai-web-crawler-that-outputs-llm-ready-content-chunks-4ghg"&gt;Building an AI Web Crawler That Outputs LLM-Ready Content Chunks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-real-time-press-release-monitor-with-python-and-rss-aggregation-76l"&gt;Building a Real-Time Press Release Monitor with Python and RSS Aggregation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-universal-property-listing-scraper-with-python-and-json-ld-3mdj"&gt;Building a Universal Property Listing Scraper with Python and JSON-LD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/tracking-tech-sentiment-in-real-time-with-vader-and-python-3adl"&gt;Tracking Tech Sentiment in Real-Time with VADER and Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-i-built-a-product-hunt-scraper-that-tracks-launches-in-real-time-jkn"&gt;How I Built a Product Hunt Scraper That Tracks Launches in Real-Time&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/5-apis-every-developer-needs-for-content-processing-rss-extraction-sitemaps-ai-2630"&gt;5 APIs Every Developer Needs for Content Processing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-to-extract-clean-content-from-any-website-sitemap-for-seo-audits-ai-training-15a9"&gt;How to Extract Clean Content From Any Website Sitemap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/scraping-187000-romanian-businesses-building-a-b2b-lead-generation-tool-176n"&gt;Scraping 187,000 Romanian Businesses: Building a B2B Lead Generation Tool&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/make-any-website-ai-readable-generating-llmstxt-files-with-python-3jop"&gt;Make Any Website AI-Readable: Generating llms.txt Files with Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-an-rss-aggregator-that-extracts-full-article-content-not-just-summaries-ifl"&gt;I Built an RSS Aggregator That Extracts Full Article Content&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>mcp</category>
      <category>api</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building an Aviation Hub API: Airports, Airlines, Live Flights &amp; Weather From Six Keyless Sources</title>
      <dc:creator>Oaida Adrian</dc:creator>
      <pubDate>Sun, 16 Aug 2026 19:29:56 +0000</pubDate>
      <link>https://dev.to/darksider4all_afa2428f63d0/building-an-aviation-hub-api-airports-airlines-live-flights-weather-from-six-keyless-sources-58j9</link>
      <guid>https://dev.to/darksider4all_afa2428f63d0/building-an-aviation-hub-api-airports-airlines-live-flights-weather-from-six-keyless-sources-58j9</guid>
      <description>&lt;p&gt;The aviation niche looks impenetrable at first: proprietary flight feeds, licensing fees, and a wall of "contact sales" forms. But most of the data people actually pay for is already public — it's just scattered across six or seven government and community sources that nobody bothered to stitch together.&lt;/p&gt;

&lt;p&gt;So I built an aviation hub actor that does exactly that: one input, six keyless upstreams, six output modes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap
&lt;/h2&gt;

&lt;p&gt;The reference actor in this niche did the same job, but its &lt;code&gt;live&lt;/code&gt; mode returned zero rows in our trial — its single ADS-B feed was empty at the time. One feed, no fallback, no resilience. Ours ships &lt;strong&gt;six modes&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Airports&lt;/strong&gt; — 85K+ entries from OurAirports (public domain), including runways&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Airlines&lt;/strong&gt; — the OpenFlights registry with country and callsign data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Routes&lt;/strong&gt; — geocoded endpoints plus great-circle distance in kilometres&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live&lt;/strong&gt; — adsb.lol community ADS-B &lt;strong&gt;with an OpenSky anonymous fallback&lt;/strong&gt;, so live never returns empty when either feed has coverage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weather&lt;/strong&gt; — NOAA METAR JSON from aviationweather.gov&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Digest&lt;/strong&gt; — a market summary: busiest airports, biggest airlines, most common aircraft types&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reverse-engineering
&lt;/h2&gt;

&lt;p&gt;The trickiest part was the route mode: OpenFlights gives you source/destination pairs, but not coordinates. I geocode both ends and compute great-circle distance — one formula, no API key, works for every pair in the registry.&lt;/p&gt;

&lt;p&gt;The live mode needed the fallback architecture: try adsb.lol first, and if the feed is thin or empty, flip to OpenSky's anonymous endpoint. The reference's single-feed design was its weakness; the fallback is the whole point.&lt;/p&gt;

&lt;h2&gt;
  
  
  The smoke test
&lt;/h2&gt;

&lt;p&gt;Six modes, six cloud runs, all non-zero:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;airports&lt;/strong&gt; — Romanian airports (Bacău, Belfast shapes correct)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;airlines&lt;/strong&gt; — Tarom returned with country + callsign&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;routes&lt;/strong&gt; — OTP→LHR, American Airlines at 5,539.8 km great-circle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;live&lt;/strong&gt; — BAW416 (G-DBCA, A319) tracked near EGLL via adsb.lol&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;weather&lt;/strong&gt; — LROP METAR, VFR conditions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;digest&lt;/strong&gt; — 85,892 airports, matching the reference digest total exactly&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The honest bits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Live coverage depends on community ADS-B volunteers; the OpenSky fallback helps but isn't a paid-quality feed&lt;/li&gt;
&lt;li&gt;METAR is aviation weather only — no TAF long-range forecasts in this mode&lt;/li&gt;
&lt;li&gt;Route distances are great-circle, not flight-path&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://apify.com/darknezz/aviation-hub" rel="noopener noreferrer"&gt;&lt;strong&gt;Aviation Hub on Apify Store&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More from me
&lt;/h2&gt;

&lt;p&gt;While you're here, these might be worth a read:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-a-canada-product-recalls-safety-alerts-scraper-that-reads-open-government-data-3p59"&gt;I Built a Canada Product Recalls &amp;amp; Safety Alerts Scraper That Reads Open Government Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-a-telegram-members-scraper-that-reads-public-chat-stats-without-login-34mc"&gt;I Built a Telegram Members Scraper That Reads Public Chat Stats Without Login&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-whois-dns-lookup-tool-domain-intelligence-in-one-api-call-2n5b"&gt;Building a WHOIS &amp;amp; DNS Lookup Tool: Domain Intelligence in One API Call&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-an-ai-web-crawler-that-outputs-llm-ready-content-chunks-4ghg"&gt;Building an AI Web Crawler That Outputs LLM-Ready Content Chunks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-real-time-press-release-monitor-with-python-and-rss-aggregation-76l"&gt;Building a Real-Time Press Release Monitor with Python and RSS Aggregation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-universal-property-listing-scraper-with-python-and-json-ld-3mdj"&gt;Building a Universal Property Listing Scraper with Python and JSON-LD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/tracking-tech-sentiment-in-real-time-with-vader-and-python-3adl"&gt;Tracking Tech Sentiment in Real-Time with VADER and Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-i-built-a-product-hunt-scraper-that-tracks-launches-in-real-time-jkn"&gt;How I Built a Product Hunt Scraper That Tracks Launches in Real-Time&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/5-apis-every-developer-needs-for-content-processing-rss-extraction-sitemaps-ai-2630"&gt;5 APIs Every Developer Needs for Content Processing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-to-extract-clean-content-from-any-website-sitemap-for-seo-audits-ai-training-15a9"&gt;How to Extract Clean Content From Any Website Sitemap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/scraping-187000-romanian-businesses-building-a-b2b-lead-generation-tool-176n"&gt;Scraping 187,000 Romanian Businesses: Building a B2B Lead Generation Tool&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/make-any-website-ai-readable-generating-llmstxt-files-with-python-3jop"&gt;Make Any Website AI-Readable: Generating llms.txt Files with Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-an-rss-aggregator-that-extracts-full-article-content-not-just-summaries-ifl"&gt;I Built an RSS Aggregator That Extracts Full Article Content&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>api</category>
      <category>aviation</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I Stopped Scraping Business Directories and Built an MCP Server on Official Registry Data</title>
      <dc:creator>Oaida Adrian</dc:creator>
      <pubDate>Thu, 13 Aug 2026 17:05:25 +0000</pubDate>
      <link>https://dev.to/darksider4all_afa2428f63d0/i-stopped-scraping-business-directories-and-built-an-mcp-server-on-official-registry-data-4caf</link>
      <guid>https://dev.to/darksider4all_afa2428f63d0/i-stopped-scraping-business-directories-and-built-an-mcp-server-on-official-registry-data-4caf</guid>
      <description>&lt;h1&gt;
  
  
  I Stopped Scraping Business Directories and Built an MCP Server on Official Registry Data
&lt;/h1&gt;

&lt;p&gt;For a long time I built B2B lead-generation tools the way everyone does: scrape a directory, fight the anti-bot, re-scrape when the layout changes, pray the data is current. Then I found out the Romanian government publishes the &lt;em&gt;entire&lt;/em&gt; company registry as open data — 4.2 million firms, refreshed monthly, with legal status, activity codes and directors. No scraping required.&lt;/p&gt;

&lt;p&gt;This post is the story of that switch: how I turned an official open-data snapshot into a self-hosted MCP server with three tools that actually do work for an AI agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;If you want to build a list of Romanian companies — say, every SRL in Cluj with a CAEN code for software services — you have three options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scrape a directory site.&lt;/strong&gt; Fragile, slow, legally grey, and almost always stale. Directories are themselves scraped from the registry, so you're scraping a copy of a copy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Buy a database.&lt;/strong&gt; Expensive, opaque provenance, and you still have to build the pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use the official source.&lt;/strong&gt; The ONRC (Romania's Trade Register) publishes a full snapshot of every registered company on the national open-data portal, data.gov.ro, which runs CKAN.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Option three is the one nobody seems to talk about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why official registry data beats scraping
&lt;/h2&gt;

&lt;p&gt;The ONRC open-data programme publishes monthly CSV snapshots:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;firme&lt;/strong&gt; — 4.2M companies: name, CUI (tax ID), registration code, legal form, address, website&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reprezentanti legali&lt;/strong&gt; — legal representatives (directors) per company&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;caen autorizat&lt;/strong&gt; — CAEN activity codes per company&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;stare firma&lt;/strong&gt; — company status history&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;nomenclatoare&lt;/strong&gt; — the &lt;em&gt;decode tables&lt;/em&gt;: status codes and CAEN activity names&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the whole registry, structured, and licensed for reuse. No login, no API key, no rate limit, no anti-bot. The fragility is gone: the government keeps the source fresh, and I just re-download the monthly snapshot.&lt;/p&gt;

&lt;p&gt;The catch is that "open data" is not "clean data". Those CSVs are enormous (the firms file alone is ~690 MB), use &lt;code&gt;^&lt;/code&gt; as a delimiter, carry a BOM, encode Romanian diacritics, and store statuses and activities as &lt;strong&gt;codes&lt;/strong&gt; that mean nothing without the nomenclator tables. That's the real engineering work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline: CKAN → SQLite → MCP
&lt;/h2&gt;

&lt;p&gt;The pattern I landed on works for any CKAN-backed government portal:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Find the dataset via the CKAN API.&lt;/strong&gt; &lt;code&gt;GET {portal}/api/3/action/package_search&lt;/code&gt; finds the ONRC organization; &lt;code&gt;package_show&lt;/code&gt; returns the resource file URLs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inspect the schema cheaply.&lt;/strong&gt; A single HTTP range request (&lt;code&gt;curl -r 0-1200&lt;/code&gt;) reads the CSV header before committing to a download.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stream-load into SQLite.&lt;/strong&gt; Never read a multi-hundred-MB CSV into memory. &lt;code&gt;csv.reader&lt;/code&gt; + &lt;code&gt;executemany&lt;/code&gt; in batches of 5,000 rows, with &lt;code&gt;errors="replace"&lt;/code&gt; for diacritics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load the nomenclators&lt;/strong&gt; into their own tables, then &lt;code&gt;LEFT JOIN&lt;/code&gt; at query time to decode status and CAEN codes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expose as MCP tools&lt;/strong&gt; that open the database read-only.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The whole thing lives in a small Python package — a loader script, a nomenclator loader, and a FastMCP server. The MCP server (Streamable HTTP) runs on a homelab box and exposes three tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three tools
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;lookup_business&lt;/code&gt; — the registry search
&lt;/h3&gt;

&lt;p&gt;Search by name or CUI (tax ID). Digits hit the exact CUI index; anything else is a case-insensitive &lt;code&gt;LIKE&lt;/code&gt; on the name. Each result is enriched with decoded CAEN activities, directors, and status — the nomenclator joins are what make it useful. A real call:&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="nf"&gt;lookup_business&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Dedeman&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dedeman"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"results"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"companyName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DEDEMAN SRL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"cui"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2816464"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"registrationCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"J1992002621040"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"registrationDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"05/11/1992"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"legalForm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SRL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"euid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ROONRC.J1992002621040"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Municipiul Bacău, Bacău, Str. ALEXEI TOLSTOI, 8, 600093"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"county"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bacău"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"website"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"www.dedeman.ro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"caenActivities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0125"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"activity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cultivarea altor pomi fructiferi, a arbuștilor fructiferi, căpșunilor și a nuciferelor"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1610"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"activity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tăierea și rindeluirea lemnului"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"directors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"PAVAL I. DRAGOS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BRINZEA S. STEFAN"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1048"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"funcțiune"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"onrc"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything is decoded: &lt;code&gt;1048&lt;/code&gt; is "funcțiune" (active), the CAEN codes come back as readable activities. An agent can ask "what does this company actually do?" and get a straight answer.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;code&gt;extract_contacts&lt;/code&gt; — find the humans
&lt;/h3&gt;

&lt;p&gt;Once you know a company exists, you need the contact points. This tool crawls the company website (bounded to a few pages, prioritising contact/about pages) and extracts emails, phone numbers and social profiles. It filters aggressively — no image files, no &lt;code&gt;example.com&lt;/code&gt; placeholders, no &lt;code&gt;noreply@&lt;/code&gt; — and matches emails against the site's own domain to cut the noise:&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="nf"&gt;extract_contacts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.bitdefender.ro&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.bitdefender.ro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"domain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bitdefender.ro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pagesCrawled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"emails"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"phones"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"facebook"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.facebook.com/bitdefender"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"twitter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://twitter.com/bitdefender"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"instagram"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.instagram.com/bitdefender"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"linkedin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.linkedin.com/company/bitdefender"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"youtube"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.youtube.com/c/Bitdefender"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"socialLinks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://www.facebook.com/bitdefender"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://www.twitter.com/bitdefender"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://www.instagram.com/bitdefender"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://www.linkedin.com/company/bitdefender"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://www.youtube.com/c/Bitdefender"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Honest limitations: corporate homepages often carry no public email (hence &lt;code&gt;emails: null&lt;/code&gt; here), and obfuscated emails (Cloudflare's &lt;code&gt;data-cfemail&lt;/code&gt;, &lt;code&gt;name [at] domain [dot] com&lt;/code&gt;) need decoders. The tool handles both, but you learn to expect gaps on big corporate sites — the SMB sites are where the gold is.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;code&gt;lookup_domain&lt;/code&gt; — verify before you call
&lt;/h3&gt;

&lt;p&gt;Email validation is a lead-gen step most people skip. This tool wraps WHOIS + DNS + SPF/DMARC so an agent can check a domain before adding it to a list — is it registered, who owns it, does it even have mail?&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="nf"&gt;lookup_domain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dedeman.ro&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"domain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dedeman.ro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"whois"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"registrar"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ICI - Registrar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"creationDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2001-04-23"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"nameServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ns1.dedeman.ro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ns2.orange.ro"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"52.16.150.45"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"MX"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"5 mx.dedeman.ro."&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"security"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hasSPF"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"spf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"v=spf1 mx ip4:91.216.225.16/32 ... -all"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hasDMARC"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That single call tells you the domain is 24 years old, points at a real mail server, and has both SPF and DMARC — a company that takes email seriously. For the ones that fail, you've just saved a bounced email.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The decode tables are the real work.&lt;/strong&gt; The registry itself is just codes; the nomenclators turn &lt;code&gt;1048&lt;/code&gt; into "funcțiune". Budget real time for them — they're in a &lt;em&gt;separate&lt;/em&gt; CKAN dataset and it's easy to miss.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The join key isn't the public ID.&lt;/strong&gt; ONRC joins internally on the registration code (&lt;code&gt;COD_INMATRICULARE&lt;/code&gt;) while everyone searches by CUI (tax ID). Get that mapping right or every lookup "silently fails".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSVs from governments are hostile by accident.&lt;/strong&gt; Caret delimiters, BOMs, &lt;code&gt;.CSV&lt;/code&gt;-suffixed resource names, &lt;code&gt;errors="replace"&lt;/code&gt; for diacritics. A range request to read the header first saves hours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monthly snapshots beat real-time scraping.&lt;/strong&gt; The data is a month stale at worst and &lt;em&gt;complete&lt;/em&gt; — no pagination war, no missing pages, no layout break. If a registry publishes open data, use it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agents need tools, not scrapers.&lt;/strong&gt; An LLM can't responsibly scrape 4M companies, but it can absolutely call &lt;code&gt;lookup_business("Dedeman")&lt;/code&gt; and reason about the result. MCP is the right seam between "registry data" and "agent capability".&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;I'm packaging the same pipeline as an Apify actor so it can run on demand (Ro Business Data MCP, coming soon to &lt;a href="https://apify.com/darknezz" rel="noopener noreferrer"&gt;my Apify account&lt;/a&gt;) — but the whole pattern is portable to any CKAN portal. Poland, France, Germany and most EU states publish similar company registries. If your lead-gen data is scraped from a directory, check whether your government already publishes the real thing. It probably does.&lt;/p&gt;

&lt;h2&gt;
  
  
  More from me
&lt;/h2&gt;

&lt;p&gt;While you're here, these might be worth a read:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-a-canada-product-recalls-safety-alerts-scraper-that-reads-open-government-data-3p59"&gt;I Built a Canada Product Recalls &amp;amp; Safety Alerts Scraper That Reads Open Government Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-a-telegram-members-scraper-that-reads-public-chat-stats-without-login-34mc"&gt;I Built a Telegram Members Scraper That Reads Public Chat Stats Without Login&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-whois-dns-lookup-tool-domain-intelligence-in-one-api-call-2n5b"&gt;Building a WHOIS &amp;amp; DNS Lookup Tool: Domain Intelligence in One API Call&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-an-ai-web-crawler-that-outputs-llm-ready-content-chunks-4ghg"&gt;Building an AI Web Crawler That Outputs LLM-Ready Content Chunks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-real-time-press-release-monitor-with-python-and-rss-aggregation-76l"&gt;Building a Real-Time Press Release Monitor with Python and RSS Aggregation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-universal-property-listing-scraper-with-python-and-json-ld-3mdj"&gt;Building a Universal Property Listing Scraper with Python and JSON-LD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/tracking-tech-sentiment-in-real-time-with-vader-and-python-3adl"&gt;Tracking Tech Sentiment in Real-Time with VADER and Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-i-built-a-product-hunt-scraper-that-tracks-launches-in-real-time-jkn"&gt;How I Built a Product Hunt Scraper That Tracks Launches in Real-Time&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/5-apis-every-developer-needs-for-content-processing-rss-extraction-sitemaps-ai-2630"&gt;5 APIs Every Developer Needs for Content Processing (RSS, Extraction, Sitemaps, AI)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/how-to-extract-clean-content-from-any-website-sitemap-for-seo-audits-ai-training-15a9"&gt;How to Extract Clean Content From Any Website Sitemap (For SEO Audits &amp;amp; AI Training)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/scraping-187000-romanian-businesses-building-a-b2b-lead-generation-tool-176n"&gt;Scraping 187,000 Romanian Businesses: Building a B2B Lead Generation Tool&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/make-any-website-ai-readable-generating-llmstxt-files-with-python-3jop"&gt;Make Any Website AI-Readable: Generating llms.txt Files with Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/darksider4all_afa2428f63d0/i-built-an-rss-aggregator-that-extracts-full-article-content-not-just-summaries-ifl"&gt;I Built an RSS Aggregator That Extracts Full Article Content (Not Just Summaries)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>mcp</category>
      <category>scraping</category>
    </item>
  </channel>
</rss>
