<?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>Romania's company registry has no free API — so I built one (4.2M firms, MCP + REST)</title>
      <dc:creator>Oaida Adrian</dc:creator>
      <pubDate>Mon, 07 Sep 2026 09:33:43 +0000</pubDate>
      <link>https://dev.to/darksider4all_afa2428f63d0/romanias-company-registry-has-no-free-api-so-i-built-one-42m-firms-mcp-rest-234l</link>
      <guid>https://dev.to/darksider4all_afa2428f63d0/romanias-company-registry-has-no-free-api-so-i-built-one-42m-firms-mcp-rest-234l</guid>
      <description>&lt;h1&gt;
  
  
  Romania's company registry has no free API — so I built one (4.2M firms, MCP + REST)
&lt;/h1&gt;

&lt;p&gt;I run a side hustle building data tools for AI agents. A few weeks ago I needed&lt;br&gt;
Romanian company data — the kind of thing every lead-gen tool wants: who's&lt;br&gt;
registered, their CAEN activity codes, legal representatives, status, address,&lt;br&gt;
website.&lt;/p&gt;

&lt;p&gt;Turns out that data has no free API. Romania's official business registry&lt;br&gt;
(&lt;strong&gt;ONRC&lt;/strong&gt;) publishes everything as open data — a monthly CSV dump on&lt;br&gt;
&lt;code&gt;data.gov.ro&lt;/code&gt; — but there's no programmatic way to query it. The only access is&lt;br&gt;
commercial scrapers, or scraping the registry website yourself.&lt;/p&gt;

&lt;p&gt;So I built the missing API. Here's the whole thing, from a 1.5 GB government&lt;br&gt;
CSV dump to a live MCP server.&lt;/p&gt;

&lt;h2&gt;
  
  
  The data is already free — it's just not queryable
&lt;/h2&gt;

&lt;p&gt;Romania publishes its entire registry as open data. Every month, &lt;code&gt;data.gov.ro&lt;/code&gt;&lt;br&gt;
drops a snapshot: &lt;strong&gt;4,196,860 companies&lt;/strong&gt;, 19.3M CAEN activity records, 3.68M&lt;br&gt;
legal representatives, 4.64M status entries. For 2025, it also publishes full&lt;br&gt;
&lt;strong&gt;financial statements&lt;/strong&gt; — balance sheets, revenue, headcount, debt.&lt;/p&gt;

&lt;p&gt;The problem is ergonomics, not availability. The files are pipe-delimited CSVs,&lt;br&gt;
a gigabyte and a half each, with no index and no API. You can download them,&lt;br&gt;
but then you have a pile of text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning a CSV dump into a queryable registry
&lt;/h2&gt;

&lt;p&gt;The whole pipeline is three loaders:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;load_onrc.py&lt;/code&gt;&lt;/strong&gt; — streams the CKAN CSVs straight into SQLite (no pandas
loading 1.5 GB into RAM).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;load_nomenclatoare.py&lt;/code&gt;&lt;/strong&gt; — pulls the CAEN activity + status code
decode tables, so &lt;code&gt;0125&lt;/code&gt; renders as "cultivation of fruit trees" instead of
a number.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;load_financiare.py&lt;/code&gt;&lt;/strong&gt; — loads the 2025 financial statements, keyed by CUI
(tax code).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then the important bit: &lt;strong&gt;search&lt;/strong&gt;. Romanian names are full of diacritics —&lt;br&gt;
&lt;code&gt;PAVĂL&lt;/code&gt;, &lt;code&gt;ȘTEFAN&lt;/code&gt;, &lt;code&gt;ţ&lt;/code&gt;. If you search &lt;code&gt;paval&lt;/code&gt;, a naive &lt;code&gt;LIKE&lt;/code&gt; misses &lt;code&gt;PAVĂL&lt;/code&gt;.&lt;br&gt;
So I built an &lt;strong&gt;FTS5 full-text index&lt;/strong&gt; with diacritic-insensitive folding:&lt;br&gt;
&lt;code&gt;ă â î ș ț&lt;/code&gt; fold to &lt;code&gt;a a i s t&lt;/code&gt; on &lt;em&gt;both&lt;/em&gt; the index and the query. Search&lt;br&gt;
&lt;code&gt;popescu&lt;/code&gt; and you match &lt;code&gt;Popéscu&lt;/code&gt;; search &lt;code&gt;paval&lt;/code&gt; and you match &lt;code&gt;PAVĂL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The result is a 3.5 GB SQLite database that answers "who is this company?" in&lt;br&gt;
milliseconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two surfaces, same data
&lt;/h2&gt;

&lt;p&gt;I exposed it two ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A REST API&lt;/strong&gt; (&lt;a href="https://onrc-api.adrianhomelab.com/" rel="noopener noreferrer"&gt;onrc-api.adrianhomelab.com&lt;/a&gt;) — plain JSON over HTTP, with
Swagger docs. &lt;code&gt;GET /lookup_business?query=DEDEMAN&lt;/code&gt;, done.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An MCP server&lt;/strong&gt; (&lt;a href="https://leadgen-mcp.adrianhomelab.com/" rel="noopener noreferrer"&gt;&lt;code&gt;leadgen-mcp.adrianhomelab.com&lt;/code&gt;&lt;/a&gt; — endpoint &lt;code&gt;/mcp&lt;/code&gt;) — Streamable HTTP, so any
MCP-capable agent (Claude Desktop, Cursor, etc.) can query it natively with
five tools: &lt;code&gt;lookup_business&lt;/code&gt;, &lt;code&gt;lookup_director&lt;/code&gt;, &lt;code&gt;lookup_financials&lt;/code&gt;,
&lt;code&gt;extract_contacts&lt;/code&gt;, &lt;code&gt;lookup_domain&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both are free: 5 lookups a day with no key at all, or 25 a day with a free API&lt;br&gt;
key from &lt;a href="https://onrc-api.adrianhomelab.com/start" rel="noopener noreferrer"&gt;onrc-api.adrianhomelab.com/start&lt;/a&gt;&lt;br&gt;
— one email address, no payment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The freemium shape
&lt;/h2&gt;

&lt;p&gt;Registry identity (name, CUI, address, CAEN, directors, status) is free. The&lt;br&gt;
&lt;strong&gt;financial statements&lt;/strong&gt; — revenue, profit, employees, assets, debt — are the&lt;br&gt;
paid tier. The scaffolding is already in the repo (&lt;code&gt;monetization.py&lt;/code&gt;), held&lt;br&gt;
behind a single &lt;code&gt;MONETIZATION_ENABLED&lt;/code&gt; flag. Flip it and paid tools require a&lt;br&gt;
bearer key; free tools get rate-limited.&lt;/p&gt;

&lt;p&gt;The point of the flag: prove agents actually &lt;em&gt;want&lt;/em&gt; this before I wire billing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Open data ≠ accessible data.&lt;/strong&gt; The value isn't the data — it's the
diacritic-insensitive search, the join across five tables, and making it
one HTTP call away.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP is a distribution channel, not just a protocol.&lt;/strong&gt; Wrapping the same
SQLite DB as an MCP server instantly made it usable by any agent. That's the
wedge: the registry is one country, but the pattern generalises.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ship the flag before the billing.&lt;/strong&gt; Monetization scaffolding costs nothing
to build and keeps the door open.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Try it at &lt;a href="https://onrc-api.adrianhomelab.com/" rel="noopener noreferrer"&gt;onrc-api.adrianhomelab.com&lt;/a&gt; — a free key takes one email.&lt;/p&gt;

&lt;h2&gt;
  
  
  The data is Romania's — the queryability is the product.
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Companion posts in the same build series:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/darksider4all_afa2428f63d0/building-a-romanian-business-registry-api-42m-companies-as-queryable-json-1dh3"&gt;Building a Romanian Business Registry API: 4.2M Companies as Queryable JSON&lt;/a&gt; — the REST API deep-dive and the diacritics/FTS5 traps&lt;/li&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; — why official open data beats directory scraping&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mcp</category>
      <category>romania</category>
      <category>api</category>
      <category>python</category>
    </item>
    <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 /start&lt;/code&gt; — free API key: 25 lookups/day for an email address&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;a href="https://leadgen-mcp.adrianhomelab.com/" rel="noopener noreferrer"&gt;leadgen-mcp.adrianhomelab.com&lt;/a&gt; — Streamable HTTP at the &lt;code&gt;/mcp&lt;/code&gt; path. 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. Try it at &lt;a href="https://onrc-api.adrianhomelab.com/" rel="noopener noreferrer"&gt;onrc-api.adrianhomelab.com&lt;/a&gt; — a free key takes one email.&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;&lt;strong&gt;Newest in this series:&lt;/strong&gt; &lt;a href="https://dev.to/darksider4all_afa2428f63d0/romanias-company-registry-has-no-free-api-so-i-built-one-42m-firms-mcp-rest-234l"&gt;Romania's company registry has no free API — so I built one (4.2M firms, MCP + REST)&lt;/a&gt;&lt;/p&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;&lt;strong&gt;Newest in this series:&lt;/strong&gt; &lt;a href="https://dev.to/darksider4all_afa2428f63d0/romanias-company-registry-has-no-free-api-so-i-built-one-42m-firms-mcp-rest-234l"&gt;Romania's company registry has no free API — so I built one (4.2M firms, MCP + REST)&lt;/a&gt;&lt;/p&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;&lt;strong&gt;Newest in this series:&lt;/strong&gt; &lt;a href="https://dev.to/darksider4all_afa2428f63d0/romanias-company-registry-has-no-free-api-so-i-built-one-42m-firms-mcp-rest-234l"&gt;Romania's company registry has no free API — so I built one (4.2M firms, MCP + REST)&lt;/a&gt;&lt;/p&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;&lt;strong&gt;Newest in this series:&lt;/strong&gt; &lt;a href="https://dev.to/darksider4all_afa2428f63d0/romanias-company-registry-has-no-free-api-so-i-built-one-42m-firms-mcp-rest-234l"&gt;Romania's company registry has no free API — so I built one (4.2M firms, MCP + REST)&lt;/a&gt;&lt;/p&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;&lt;strong&gt;Newest in this series:&lt;/strong&gt; &lt;a href="https://dev.to/darksider4all_afa2428f63d0/romanias-company-registry-has-no-free-api-so-i-built-one-42m-firms-mcp-rest-234l"&gt;Romania's company registry has no free API — so I built one (4.2M firms, MCP + REST)&lt;/a&gt;&lt;/p&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>
  </channel>
</rss>
