<?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: Get Anything</title>
    <description>The latest articles on DEV Community by Get Anything (@get_anything).</description>
    <link>https://dev.to/get_anything</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%2F4024040%2Fe9066f23-9178-40c1-9855-eca27d8a6fd1.png</url>
      <title>DEV Community: Get Anything</title>
      <link>https://dev.to/get_anything</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/get_anything"/>
    <language>en</language>
    <item>
      <title>GCC Tech Salaries in 2026: How to Track Vision 2030 Hiring &amp; Pay with Live Data</title>
      <dc:creator>Get Anything</dc:creator>
      <pubDate>Tue, 28 Jul 2026 04:28:37 +0000</pubDate>
      <link>https://dev.to/get_anything/gcc-tech-salaries-in-2026-how-to-track-vision-2030-hiring-pay-with-live-data-j49</link>
      <guid>https://dev.to/get_anything/gcc-tech-salaries-in-2026-how-to-track-vision-2030-hiring-pay-with-live-data-j49</guid>
      <description>&lt;p&gt;The Gulf is in the middle of the biggest hiring wave in its history. The six GCC states are projected to hire around &lt;strong&gt;2.5 million skilled professionals over three years&lt;/strong&gt;, Saudi Arabia is targeting 50,000 new tech jobs under &lt;strong&gt;Vision 2030&lt;/strong&gt;, and senior-developer pay in the UAE and Saudi Arabia is reportedly inflating &lt;strong&gt;15–25% a year&lt;/strong&gt;. Salaries overall are forecast up ~4.6% in Saudi and ~4.1% in the UAE for 2026.&lt;/p&gt;

&lt;p&gt;If you're recruiting, job-hunting, or building a jobs product in this market, generic salary surveys are already stale by the time they publish. Here's how to track &lt;strong&gt;live GCC salary and hiring data&lt;/strong&gt; yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: most Gulf job boards hide salary
&lt;/h2&gt;

&lt;p&gt;Bayt, GulfTalent and NaukriGulf rarely publish pay. The two sources that &lt;em&gt;do&lt;/em&gt; — &lt;strong&gt;Indeed&lt;/strong&gt; and &lt;strong&gt;Glassdoor&lt;/strong&gt; — sit behind Cloudflare and heavy anti-bot protection, so a normal scraper just hits a "Security Check" page.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: pull salary-tagged jobs across the Gulf
&lt;/h2&gt;

&lt;p&gt;Two ready-made tools get past the anti-bot walls (using a hardened, fingerprint-spoofing browser) and return structured jobs &lt;em&gt;with salary&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://apify.com/get_anything/indeed-gcc-jobs" rel="noopener noreferrer"&gt;Indeed Scraper — GCC Jobs, Salaries &amp;amp; Employers&lt;/a&gt;&lt;/strong&gt; — 70–80% of GCC listings carry a salary range, plus employment type, remote flag and company rating.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://apify.com/get_anything/glassdoor-gcc-jobs" rel="noopener noreferrer"&gt;Glassdoor Scraper — GCC Jobs &amp;amp; Salary Data&lt;/a&gt;&lt;/strong&gt; — employer-provided salary ranges and company data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Give each a &lt;strong&gt;keyword&lt;/strong&gt; (e.g. "software engineer", "data scientist", "AI engineer") and one or more &lt;strong&gt;countries&lt;/strong&gt; (UAE, Saudi Arabia, Qatar, Kuwait, Bahrain, Oman).&lt;/p&gt;

&lt;h3&gt;
  
  
  Example (Indeed) output
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Senior Software Engineer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"company"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Riyadh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Saudi Arabia"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"employmentType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"full-time"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"salaryText"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SAR 25,000 - SAR 35,000 a month"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"salaryMin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"salaryCurrency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SAR"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Track the trends that matter in 2026
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vision 2030 tech demand&lt;/strong&gt; — software, full-stack, data science, AI/ML roles in Riyadh, NEOM, Dubai&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tech salary inflation&lt;/strong&gt; — benchmark senior-dev pay month over month&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Saudization&lt;/strong&gt; — which roles are shifting to Saudi-national-only and how pay moves with it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sector shifts&lt;/strong&gt; — tourism, construction, healthcare, education (5,000+ teaching roles a quarter)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Want coverage first, salary second?
&lt;/h2&gt;

&lt;p&gt;Start with the free flagship: &lt;strong&gt;&lt;a href="https://apify.com/get_anything/gcc-jobs-search" rel="noopener noreferrer"&gt;GCC Jobs Search&lt;/a&gt;&lt;/strong&gt; — searches Bayt, GulfTalent, NaukriGulf, Foundit and Tanqeeb together with filters for field, experience, city, salary and company, then layer Indeed/Glassdoor for salary depth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ask it in plain English
&lt;/h2&gt;

&lt;p&gt;All three plug into ChatGPT/Claude via the &lt;a href="https://mcp.apify.com" rel="noopener noreferrer"&gt;Apify MCP server&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What do senior data engineers earn in Riyadh right now?"&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Tools: &lt;a href="https://apify.com/get_anything/indeed-gcc-jobs" rel="noopener noreferrer"&gt;Indeed GCC&lt;/a&gt; · &lt;a href="https://apify.com/get_anything/glassdoor-gcc-jobs" rel="noopener noreferrer"&gt;Glassdoor GCC&lt;/a&gt; · &lt;a href="https://apify.com/get_anything/gcc-jobs-search" rel="noopener noreferrer"&gt;GCC Jobs Search (free)&lt;/a&gt;. By &lt;a href="https://apify.com/get_anything" rel="noopener noreferrer"&gt;Get Anything&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>jobs</category>
      <category>dubai</category>
      <category>webscraping</category>
      <category>career</category>
    </item>
    <item>
      <title>Build a Dubai Real Estate AI Agent with MCP (No Code) — Asking Price vs Sold Price</title>
      <dc:creator>Get Anything</dc:creator>
      <pubDate>Tue, 28 Jul 2026 04:27:43 +0000</pubDate>
      <link>https://dev.to/get_anything/build-a-dubai-real-estate-ai-agent-with-mcp-no-code-asking-price-vs-sold-price-3bcd</link>
      <guid>https://dev.to/get_anything/build-a-dubai-real-estate-ai-agent-with-mcp-no-code-asking-price-vs-sold-price-3bcd</guid>
      <description>&lt;p&gt;AI agents are only as good as the tools you give them. Ask ChatGPT or Claude "what's a fair price for a 1-bed in JVC?" and you'll get a vague, out-of-date guess — because the model has no live data. The fix in 2026 is &lt;strong&gt;MCP (Model Context Protocol)&lt;/strong&gt;: you connect real data tools to your assistant, and it answers with actual numbers.&lt;/p&gt;

&lt;p&gt;Here's how to give an AI assistant two Dubai property superpowers — &lt;strong&gt;live asking prices&lt;/strong&gt; and &lt;strong&gt;real sold prices&lt;/strong&gt; — with no code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two tools your agent needs
&lt;/h2&gt;

&lt;p&gt;The gap every property decision hinges on is &lt;em&gt;asking price vs sold price&lt;/em&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://apify.com/get_anything/bayut-property-scraper" rel="noopener noreferrer"&gt;Bayut Property Scraper&lt;/a&gt;&lt;/strong&gt; — live listings (what sellers &lt;em&gt;ask&lt;/em&gt;): price, beds, baths, area, agent, for sale or rent, across Dubai and the UAE.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://apify.com/get_anything/dld-transactions-scraper" rel="noopener noreferrer"&gt;Dubai DLD Transactions Scraper&lt;/a&gt;&lt;/strong&gt; — Dubai Land Department registered transactions (what buyers &lt;em&gt;paid&lt;/em&gt;): price per sqm, first-sale vs resale, by community — free, no API key.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both are Apify Actors, and both are MCP-ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect them in ~2 minutes
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;a href="https://mcp.apify.com" rel="noopener noreferrer"&gt;Apify MCP server&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Add the two Actors above as tools.&lt;/li&gt;
&lt;li&gt;Point your MCP client (Claude Desktop, ChatGPT, Cursor, or any MCP-compatible app) at the server.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it — your assistant can now call them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can now ask
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"What's the average sold price per sqm for flats in Dubai Marina this month, first-sale vs resale?"&lt;/p&gt;

&lt;p&gt;"Find 2-bed apartments for sale in JVC under AED 1.2M, then tell me how that compares to what 2-beds actually sold for there."&lt;/p&gt;

&lt;p&gt;"Which communities had the most transactions last month, and what's the off-plan vs resale split?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent calls Bayut for the listings, DLD for the registered prices, and reconciles them — the kind of answer that used to need an analyst and a spreadsheet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters in 2026
&lt;/h2&gt;

&lt;p&gt;Dubai just posted its biggest half-year on record (~AED 291.7bn, ~71% off-plan), and prices are up ~9%. In a market moving this fast, static reports are stale on arrival. An MCP agent wired to live listings + registered transactions gives you an always-current answer to "is this priced right?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond real estate
&lt;/h2&gt;

&lt;p&gt;The same pattern works for hiring: connect the &lt;strong&gt;&lt;a href="https://apify.com/get_anything/indeed-gcc-jobs" rel="noopener noreferrer"&gt;Indeed&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://apify.com/get_anything/glassdoor-gcc-jobs" rel="noopener noreferrer"&gt;Glassdoor&lt;/a&gt;&lt;/strong&gt; GCC scrapers and ask your assistant "what do senior developers earn in Riyadh right now?"&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tools: &lt;a href="https://apify.com/get_anything/bayut-property-scraper" rel="noopener noreferrer"&gt;Bayut&lt;/a&gt; · &lt;a href="https://apify.com/get_anything/dld-transactions-scraper" rel="noopener noreferrer"&gt;Dubai DLD Transactions&lt;/a&gt;. More AI-ready Gulf data tools by &lt;a href="https://apify.com/get_anything" rel="noopener noreferrer"&gt;Get Anything&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>webscraping</category>
      <category>chatgpt</category>
    </item>
    <item>
      <title>Dubai Off-Plan vs Resale in 2026: What the DLD Transaction Data Actually Shows</title>
      <dc:creator>Get Anything</dc:creator>
      <pubDate>Tue, 28 Jul 2026 04:27:42 +0000</pubDate>
      <link>https://dev.to/get_anything/dubai-off-plan-vs-resale-in-2026-what-the-dld-transaction-data-actually-shows-26m7</link>
      <guid>https://dev.to/get_anything/dubai-off-plan-vs-resale-in-2026-what-the-dld-transaction-data-actually-shows-26m7</guid>
      <description>&lt;p&gt;Dubai's property market just had its biggest six months on record — &lt;strong&gt;87,800 transactions worth around AED 291.7 billion in H1 2026&lt;/strong&gt;, with average prices up ~9% and off-plan making up roughly &lt;strong&gt;71% of all volume&lt;/strong&gt;. Every headline is about off-plan. But if you're actually deploying capital, the question isn't "is off-plan hot?" — it's &lt;em&gt;"first-sale or resale, in which community, at what price per sqm, right now?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The only data that answers that is &lt;strong&gt;registered transaction data&lt;/strong&gt; from the &lt;strong&gt;Dubai Land Department (DLD)&lt;/strong&gt; — not the asking prices on the portals. Here's how to pull it and read the off-plan vs resale story yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "off-plan vs resale" is the metric that matters
&lt;/h2&gt;

&lt;p&gt;The DLD tags every sale with a &lt;strong&gt;sale sequence&lt;/strong&gt;: &lt;em&gt;first sale&lt;/em&gt; (off-plan / primary, straight from the developer) or &lt;em&gt;resale&lt;/em&gt; (secondary market). That single flag tells you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Where the primary pipeline is concentrated&lt;/strong&gt; — first-sale volume clusters in emerging communities with new launches (Dubai South, JVC, Dubai Islands, Dubai Creek Harbour).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The premium/discount between primary and secondary&lt;/strong&gt; — first-sale price per sqm vs resale price per sqm in the &lt;em&gt;same&lt;/em&gt; community, the same month.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Momentum&lt;/strong&gt; — rising resale volume in an off-plan-heavy area signals an exit market forming.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to get it (free, no API key)
&lt;/h2&gt;

&lt;p&gt;The DLD publishes this on its public transactions page. Instead of wrangling it by hand, the &lt;strong&gt;&lt;a href="https://apify.com/get_anything/dld-transactions-scraper" rel="noopener noreferrer"&gt;Dubai DLD Transactions Scraper&lt;/a&gt;&lt;/strong&gt; turns it into clean JSON for any date range — one record per &lt;strong&gt;community × sub-type × sale sequence&lt;/strong&gt;, with &lt;code&gt;pricePerSqmAed&lt;/code&gt;, &lt;code&gt;avgTransactionValueAed&lt;/code&gt;, &lt;code&gt;transactionCount&lt;/code&gt; and &lt;code&gt;totalValueAed&lt;/code&gt;, in English and Arabic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: reading the split in JVC
&lt;/h3&gt;

&lt;p&gt;For Jumeirah Village Circle — one of the highest-volume communities in 2026 — the data separates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"community"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JUMEIRAH VILLAGE CIRCLE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"propertySubType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Flat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"saleSequence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"First"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"transactionCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;118&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"pricePerSqmAed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16218&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"community"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JUMEIRAH VILLAGE CIRCLE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"propertySubType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Flat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"saleSequence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Second"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"transactionCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"pricePerSqmAed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12106&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First-sale (off-plan) clears higher per sqm than resale in the same community — the "new-build premium" you can now quantify per area, per month.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to build with it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Off-plan heat maps&lt;/strong&gt; — first-sale volume by community over time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Primary vs secondary price gap&lt;/strong&gt; trackers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Absorption / exit signals&lt;/strong&gt; — when resale volume rises in an off-plan area&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Valuation comparables&lt;/strong&gt; grounded in registered prices, not listings&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pair it with live listings
&lt;/h2&gt;

&lt;p&gt;To see &lt;em&gt;asking vs sold&lt;/em&gt; in the same community, add the &lt;strong&gt;&lt;a href="https://apify.com/get_anything/bayut-property-scraper" rel="noopener noreferrer"&gt;Bayut Property Scraper&lt;/a&gt;&lt;/strong&gt; (live listings: price, beds, area, agent). Asking price from Bayut, sold price from DLD — the full gap in one view.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ask it in plain English
&lt;/h2&gt;

&lt;p&gt;Both tools plug into ChatGPT/Claude via the &lt;a href="https://mcp.apify.com" rel="noopener noreferrer"&gt;Apify MCP server&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Compare off-plan vs resale price per sqm in Business Bay this quarter."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Tool: &lt;a href="https://apify.com/get_anything/dld-transactions-scraper" rel="noopener noreferrer"&gt;Dubai DLD Transactions Scraper&lt;/a&gt; · more Gulf data tools by &lt;a href="https://apify.com/get_anything" rel="noopener noreferrer"&gt;Get Anything&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>realestate</category>
      <category>dubai</category>
      <category>webscraping</category>
      <category>data</category>
    </item>
    <item>
      <title>Scraping bilingual data: the Arabic/English traps that quietly corrupt your dataset</title>
      <dc:creator>Get Anything</dc:creator>
      <pubDate>Mon, 27 Jul 2026 11:29:17 +0000</pubDate>
      <link>https://dev.to/get_anything/scraping-bilingual-data-the-arabicenglish-traps-that-quietly-corrupt-your-dataset-48fc</link>
      <guid>https://dev.to/get_anything/scraping-bilingual-data-the-arabicenglish-traps-that-quietly-corrupt-your-dataset-48fc</guid>
      <description>&lt;p&gt;Most scraping advice assumes one language. Build for a bilingual market and you meet a category of bug that never throws an exception: the request succeeds, the HTML parses, and every field you wanted comes back empty.&lt;/p&gt;

&lt;p&gt;Here is what I learned building job datasets that have to work in both Arabic and English.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug that returns null instead of failing
&lt;/h2&gt;

&lt;p&gt;I was fetching job detail pages and extracting the stated seniority and employment type. The parser looked for the labels the page uses, matched them, and pulled the adjacent value. It worked in testing.&lt;/p&gt;

&lt;p&gt;In production, &lt;code&gt;seniorityStated&lt;/code&gt; and &lt;code&gt;employmentType&lt;/code&gt; came back &lt;code&gt;null&lt;/code&gt; for every record. No error. Status 200. Valid HTML.&lt;/p&gt;

&lt;p&gt;The links I was following pointed at a regional subdomain. That subdomain served the &lt;em&gt;Arabic&lt;/em&gt; version of the page. My selectors were matching on the English labels — "Seniority level", "Employment type" — and the page said &lt;code&gt;المستوى الوظيفي&lt;/code&gt; and &lt;code&gt;نوع التوظيف&lt;/code&gt;. Nothing matched. Nothing raised. The fields were simply absent.&lt;/p&gt;

&lt;p&gt;The fix was one line: normalize the host before fetching.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="c1"&gt;# Regional hosts serve localized pages whose labels don't match
# English selectors. Normalize to the canonical host.
&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://[a-z]{2}\.example\.com/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.example.com/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lesson generalizes: &lt;strong&gt;a scraper that finds nothing and a scraper that finds the wrong language look identical from the outside.&lt;/strong&gt; If a field is null across every single record, suspect locale before you suspect your selector.&lt;/p&gt;

&lt;h2&gt;
  
  
  Assert on what you expect to find
&lt;/h2&gt;

&lt;p&gt;The defence is cheap. After a parse, check that the fields you rely on actually populated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;critical&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seniority&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;employmentType&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;critical&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No record has &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; — wrong locale or stale selector?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A scraper that crashes loudly on an empty column is worth ten that return tidy nulls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Numerals are not the digits your regex expects
&lt;/h2&gt;

&lt;p&gt;Arabic pages frequently use Eastern Arabic numerals. &lt;code&gt;٢٠٠&lt;/code&gt; is two hundred.&lt;/p&gt;

&lt;p&gt;Python is more helpful here than people assume, which is exactly why this bites. &lt;code&gt;int("٢٠٠")&lt;/code&gt; returns &lt;code&gt;200&lt;/code&gt;. &lt;code&gt;"٢٠٠".isdigit()&lt;/code&gt; is &lt;code&gt;True&lt;/code&gt;. &lt;code&gt;re.findall(r"\d+", ...)&lt;/code&gt; finds them, because &lt;code&gt;\d&lt;/code&gt; is Unicode-aware by default.&lt;/p&gt;

&lt;p&gt;The thing that silently fails is the character class almost everyone actually writes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;أكثر من ٢٠٠ متقدم&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;          &lt;span class="c1"&gt;# "more than 200 applicants"
&lt;/span&gt;
&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[0-9]+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# []          &amp;lt;- silently empty
&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\d+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# []          &amp;lt;- same trap
&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\d+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;             &lt;span class="c1"&gt;# ['٢٠٠']     &amp;lt;- works
&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;٢٠٠&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                           &lt;span class="c1"&gt;# 200         &amp;lt;- works
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So a scraper that extracts applicant counts with &lt;code&gt;[0-9]+&lt;/code&gt; doesn't crash on an Arabic page. It reports that the page contains no numbers, forever, and you conclude the field is missing rather than that your regex is ASCII-only.&lt;/p&gt;

&lt;p&gt;If you want ASCII digits in your output regardless of the source, translate rather than strip:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ARABIC_DIGITS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;maketrans&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;٠١٢٣٤٥٦٧٨٩&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0123456789&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\D&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ARABIC_DIGITS&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I nearly published the claim that &lt;code&gt;int()&lt;/code&gt; raises on Arabic numerals. It doesn't. I only found out because I ran the snippet before shipping the article, which is the entire moral of this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't let a substring match decide a language
&lt;/h2&gt;

&lt;p&gt;Keyword matching across languages goes wrong in ways that are hard to see. Two real examples from classifying job titles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Matching the seniority abbreviation &lt;code&gt;coo&lt;/code&gt; as a substring also matches &lt;strong&gt;"Coordinator"&lt;/strong&gt;. A programme coordinator became a chief operating officer.&lt;/li&gt;
&lt;li&gt;Matching &lt;code&gt;partner&lt;/code&gt; as a substring promoted &lt;strong&gt;"Partner Onboarding Specialist"&lt;/strong&gt; to firm partner.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Word boundaries fix both. But the general point is sharper in a bilingual dataset, where titles mix scripts, transliterations and English loanwords in the same string. &lt;code&gt;re.search(r"\bcoo\b", title)&lt;/code&gt;, never &lt;code&gt;"coo" in title&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Print the rows behind the number
&lt;/h2&gt;

&lt;p&gt;I was about to publish a statistic saying 18% of roles in a market were executive-level. It felt too high, so I printed the titles in that bucket. Half of them were coordinators and onboarding specialists. The real figure was 16%, and the composition was completely different.&lt;/p&gt;

&lt;p&gt;Every heuristic you write will be wrong in a way that flatters your numbers. Print the rows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Machine translation is for labels, not for meaning
&lt;/h2&gt;

&lt;p&gt;If you add Arabic labels to an English dataset, keep the original. Store &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;titleArabic&lt;/code&gt; side by side; never overwrite. Translation is lossy, brand names transliterate inconsistently, and someone downstream will want to match on the original string.&lt;/p&gt;

&lt;p&gt;For job titles specifically, a translated label is a display convenience. Every join, dedupe and filter should still run on the source-language field.&lt;/p&gt;

&lt;h2&gt;
  
  
  Encoding, briefly
&lt;/h2&gt;

&lt;p&gt;Set your encoding explicitly at every boundary: reading files, writing files, and printing. On Windows the default console encoding will happily raise &lt;code&gt;UnicodeEncodeError&lt;/code&gt; on a perfectly good Arabic string, which sends you hunting for a scraping bug that doesn't exist.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reconfigure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also: a UTF-8 byte order mark at the start of a JSON file will make some parsers reject it outright. If a tool insists your valid JSON is invalid, check for a BOM before you check your syntax.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this came from
&lt;/h2&gt;

&lt;p&gt;I maintain scrapers for regional job boards that output company and role labels in both Arabic and English. All of the above is scar tissue from building them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apify.com/get_anything" rel="noopener noreferrer"&gt;They're on my Apify page&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>webscraping</category>
      <category>data</category>
      <category>i18n</category>
    </item>
    <item>
      <title>How to build a hiring intent score that doesn't lie to you</title>
      <dc:creator>Get Anything</dc:creator>
      <pubDate>Mon, 27 Jul 2026 11:29:09 +0000</pubDate>
      <link>https://dev.to/get_anything/how-to-build-a-hiring-intent-score-that-doesnt-lie-to-you-e67</link>
      <guid>https://dev.to/get_anything/how-to-build-a-hiring-intent-score-that-doesnt-lie-to-you-e67</guid>
      <description>&lt;p&gt;You have a list of companies and their open roles. You want one number per company: how hard is this company hiring, right now?&lt;/p&gt;

&lt;p&gt;That number is easy to compute and easy to get wrong in ways that look plausible. Here's a design that survives contact with real data, and the specific mistakes that inflate every naive version.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Volume.&lt;/strong&gt; How many roles are open. The most obvious input and the least interesting on its own, because it scales with company size. Ten open roles at a fifty-person startup means something very different from ten at a ten-thousand-person bank.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Seniority mix.&lt;/strong&gt; What kind of roles. Weight senior and executive postings higher. A company creating a director-level position it never had is starting something; a company posting three interns is running a graduate scheme. Seniority is the closest proxy for "new initiative" that a job title gives you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Momentum.&lt;/strong&gt; How the count changed since last time. This is the input everyone skips, because it requires storing state across runs, and it's the one that carries the most information. A company that went from two roles to eight is in a different state from one that has had eight open for a year.&lt;/p&gt;

&lt;h2&gt;
  
  
  A workable formula
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;SENIORITY_WEIGHTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Internship&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Entry level&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mid level&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mid-Senior level&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Director&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Executive&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hiring_intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seniority_mix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Volume: log-ish, so 40 roles doesn't drown out everything else
&lt;/span&gt;    &lt;span class="n"&gt;volume&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_count&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Seniority: weighted average of the mix
&lt;/span&gt;    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seniority_mix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;seniority&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SENIORITY_WEIGHTS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
                    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seniority_mix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;

    &lt;span class="c1"&gt;# Momentum: unknown on the first run, and that's not the same as zero
&lt;/span&gt;    &lt;span class="n"&gt;momentum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;volume&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;seniority&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;momentum&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cap volume rather than letting it run linearly, or the largest company in your list wins every time and the score becomes a headcount proxy.&lt;/p&gt;

&lt;p&gt;Note &lt;code&gt;delta is None&lt;/code&gt;. On the first run you have no previous state. A first run is not a company with zero momentum — it's a company whose momentum you don't know. Scoring those identically means every company looks stagnant on day one, and your first Monday's call list is garbage. Give unknown momentum a neutral value and label the run as a baseline.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap that will inflate your score
&lt;/h2&gt;

&lt;p&gt;Seniority weighting is where this breaks, because it depends on a classifier that reads job titles, and job titles are adversarial.&lt;/p&gt;

&lt;p&gt;Three bugs I shipped, all of which made companies look more senior than they were:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;"coo" in title.lower()&lt;/code&gt;&lt;/strong&gt; matches &lt;strong&gt;"Coordinator"&lt;/strong&gt;. Every programme coordinator became a chief operating officer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;"partner" in title.lower()&lt;/code&gt;&lt;/strong&gt; matches &lt;strong&gt;"Partner Onboarding Specialist"&lt;/strong&gt; and &lt;strong&gt;"Partnerships Lead"&lt;/strong&gt;. Support staff became firm partners.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Assistant Vice President" is not an executive.&lt;/strong&gt; In banking and real estate it's an upper-mid individual contributor title. A single company's AVP postings pushed my executive count up by seven.&lt;/p&gt;

&lt;p&gt;The first two are fixed with word boundaries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b(ceo|cfo|cto|cmo|coo)\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# not `"coo" in title`
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The third is fixed by ordering your rules so the specific case is caught before the general one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assistant vice president&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;avp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mid-Senior level&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;          &lt;span class="c1"&gt;# must come first
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vice president&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chief&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Executive&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Together these took my "executive-level roles" figure from 18% of a market down to 16%, and changed which companies ranked highest. I nearly published the wrong number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate by reading, not by eyeballing percentages
&lt;/h2&gt;

&lt;p&gt;The check that catches all three of the above takes one minute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seniority&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;most_common&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;=== &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seniority&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;   &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Print the titles inside each bucket. Every classification bug I have written was invisible as a percentage and obvious as a list. If "Executive" is 18% of your dataset, look at who's in it before you tell anyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the score legible
&lt;/h2&gt;

&lt;p&gt;A score of 73 is useless on its own. Ship the components alongside it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"company"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Example Co"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hiringIntentScore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;73.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"openRoles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"trendDelta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"seniorityMix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"Mid-Senior level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Director"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Entry level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sampleTitles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Director of Engineering"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Senior Data Engineer"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anyone using the score to decide where to spend their week needs to see &lt;em&gt;why&lt;/em&gt; a company ranks where it does. A high score driven by twenty interns is not the same as one driven by three director hires, and the person making the call should be able to tell those apart at a glance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Weekly, or it means nothing
&lt;/h2&gt;

&lt;p&gt;Momentum requires a previous run. Score once and you have a snapshot with the most valuable input missing. Run it on a fixed weekly schedule, store each run's per-company counts, and compare consecutive runs.&lt;/p&gt;

&lt;p&gt;One caution: never persist state from a run where a source failed. A partial fetch writes a truncated baseline, and next week every missing role looks like it closed. Suppress the closure signal on any incomplete run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ready-made
&lt;/h2&gt;

&lt;p&gt;I maintain a tracker that does exactly this: aggregates open roles per company, infers seniority and function from titles, compares against the previous scheduled run, and returns a 0–100 hiring intent score with all its components attached.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apify.com/get_anything" rel="noopener noreferrer"&gt;It's on my Apify page&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>data</category>
      <category>python</category>
      <category>analytics</category>
      <category>career</category>
    </item>
    <item>
      <title>The same job is posted on three boards. Here's how to deduplicate job listings properly.</title>
      <dc:creator>Get Anything</dc:creator>
      <pubDate>Mon, 27 Jul 2026 11:29:00 +0000</pubDate>
      <link>https://dev.to/get_anything/the-same-job-is-posted-on-three-boards-heres-how-to-deduplicate-job-listings-properly-2862</link>
      <guid>https://dev.to/get_anything/the-same-job-is-posted-on-three-boards-heres-how-to-deduplicate-job-listings-properly-2862</guid>
      <description>&lt;p&gt;Search any regional job board for a role and you will meet the same position three times, under three slightly different titles, from three sources.&lt;/p&gt;

&lt;p&gt;The company entered it once. The boards each reformatted it. If you are aggregating listings, you now have a deduplication problem, and it's harder than it looks: the naive fixes either merge two genuinely different jobs or fail to merge two copies of the same one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why exact matching fails
&lt;/h2&gt;

&lt;p&gt;The same posting, as three boards render it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;E-Commerce Executive              | Example Trading Co | Dubai, United Arab Emirates
E-commerce Executive              | Example Trading Co | Dubai - UAE
E-Commerce Executive (FMCG)       | EXAMPLE TRADING CO | Dubai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing matches exactly. Case differs, the location string differs, one board appended a category, one uppercased the company. An exact-match key finds zero duplicates here and you publish the same job three times.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why fuzzy matching also fails
&lt;/h2&gt;

&lt;p&gt;The obvious next step is fuzzy string similarity on the title. Set a threshold, merge anything above it. This works until it meets real data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Senior Accountant     vs  Senior Accountant     — same job? Maybe. Two different teams?
Sales Manager (Dubai) vs  Sales Manager (Abu Dhabi) — 92% similar, definitely different jobs
Data Analyst I        vs  Data Analyst II       — 95% similar, different seniority, different pay
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Titles are short. Short strings are similar to each other by accident. Any threshold loose enough to catch "E-Commerce Executive" and "E-commerce Executive (FMCG)" is also loose enough to merge two roles in different cities.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually works: a normalized composite key
&lt;/h2&gt;

&lt;p&gt;Match on the pieces that identify a job, normalize each aggressively, and require all of them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Lowercase, strip everything that isn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t alphanumeric.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[^a-z0-9]+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;job_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
        &lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;company&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;location&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;E-Commerce Executive&lt;/code&gt; and &lt;code&gt;E-commerce Executive&lt;/code&gt; both collapse to &lt;code&gt;ecommerceexecutive&lt;/code&gt;. Company does the same. &lt;code&gt;Sales Manager (Dubai)&lt;/code&gt; and &lt;code&gt;Sales Manager (Abu Dhabi)&lt;/code&gt; stay apart because the parenthetical survives normalization.&lt;/p&gt;

&lt;p&gt;Falling back to location when the company is missing matters more than it sounds: some boards omit the employer for confidential postings, and keying on title alone would merge every "Sales Manager" in the country into one row.&lt;/p&gt;

&lt;h2&gt;
  
  
  Merge, don't discard
&lt;/h2&gt;

&lt;p&gt;When two records collapse to the same key, don't pick one and throw away the other. Different boards carry different fields. Take the union:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;merged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;job_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;rec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sources&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
            &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;allLinks&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
            &lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="n"&gt;rec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sources&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sources&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]))&lt;/span&gt;
        &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;allLinks&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="c1"&gt;# Fill gaps from whichever source has the field
&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;company&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;location&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postedDate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;salaryText&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One board gives you a salary range, another an accurate posted date, a third the full description. The merged record is better than any single source, and &lt;code&gt;sources&lt;/code&gt; tells the reader where the job appears — genuinely useful, because applying through the board with the fewest applicants is a real strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the merge rate honest
&lt;/h2&gt;

&lt;p&gt;The number worth watching is how many records collapsed. Publish it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;58 raw postings → 51 records (7 merged)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that ratio ever hits something implausible — 200 postings collapsing to 12 — your key is too loose and you are merging distinct jobs. If it never merges anything, your normalization isn't doing its job. In my own runs a cross-board merge rate around 10 to 15 percent looks right for a single keyword; much higher and I go read the rows.&lt;/p&gt;

&lt;p&gt;Which is the general lesson. Print the rows behind any aggregate before you trust it. Every deduplication bug I've shipped looked perfectly reasonable as a percentage and obviously wrong as a list of titles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't dedupe before you filter
&lt;/h2&gt;

&lt;p&gt;Order matters. If you cap results per source before merging, you throw away the copy that had the salary field. If you filter by location before normalizing, you drop &lt;code&gt;Dubai - UAE&lt;/code&gt; while keeping &lt;code&gt;Dubai&lt;/code&gt;. Fetch fully, normalize, merge, then filter and cap.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ready-made version
&lt;/h2&gt;

&lt;p&gt;I maintain a scraper that searches the three largest regional job boards in one run and merges duplicates exactly this way, returning one record per role with every source it appears on. Pricing is per merged record, so cross-posted jobs are billed once rather than three times.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apify.com/get_anything" rel="noopener noreferrer"&gt;It's on my Apify page&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>data</category>
      <category>webscraping</category>
      <category>career</category>
    </item>
    <item>
      <title>Track new job postings the week they appear, with a scheduled scraper and a diff</title>
      <dc:creator>Get Anything</dc:creator>
      <pubDate>Mon, 27 Jul 2026 11:28:52 +0000</pubDate>
      <link>https://dev.to/get_anything/track-new-job-postings-the-week-they-appear-with-a-scheduled-scraper-and-a-diff-4lnk</link>
      <guid>https://dev.to/get_anything/track-new-job-postings-the-week-they-appear-with-a-scheduled-scraper-and-a-diff-4lnk</guid>
      <description>&lt;p&gt;One scrape of a careers page tells you what's open. Two scrapes, a week apart, tell you what &lt;em&gt;changed&lt;/em&gt; — and the change is where the value is.&lt;/p&gt;

&lt;p&gt;A role that appeared three days ago has a handful of applicants. The same role a month later has hundreds. If you are job hunting, that gap is most of the advantage you can actually control. If you are selling to companies, a role that just opened is a signal that a team is growing and has budget.&lt;/p&gt;

&lt;p&gt;Here's how to build it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;Run the same scrape on a schedule. Store each run's job IDs. Compare consecutive runs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IDs in the new run but not the old one → &lt;strong&gt;newly posted&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;IDs in the old run but not the new one → &lt;strong&gt;closed or filled&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Count per company over time → &lt;strong&gt;hiring momentum&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the whole trick. The engineering is small; the discipline is in keeping a stable identifier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use a stable ID, not the title
&lt;/h2&gt;

&lt;p&gt;The most common mistake is diffing on job title. Titles get edited. "Senior Engineer" becomes "Senior Software Engineer" and suddenly your diff reports one role closed and one opened, on the same day, for the same job.&lt;/p&gt;

&lt;p&gt;Every applicant tracking system returns a stable ID per posting. Use it, and keep the URL as a tiebreaker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;job_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jobId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Titles and locations belong in the output, never in the key.&lt;/p&gt;

&lt;h2&gt;
  
  
  The diff
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="n"&gt;STATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;last_run.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_jobs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;job_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;current_jobs&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;STATE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;STATE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="n"&gt;opened&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
    &lt;span class="n"&gt;closed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;

    &lt;span class="n"&gt;STATE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;opened&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;closed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the very first run, &lt;code&gt;previous&lt;/code&gt; is empty and everything looks new. Handle that explicitly rather than emailing yourself 500 "new" jobs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;first_run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;STATE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;opened&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;closed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;first_run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Baseline stored: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opened&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; jobs. No alerts on the first run.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opened&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; new, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;closed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; closed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Two failure modes that will lie to you
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A failed fetch looks like mass closure.&lt;/strong&gt; If one company's board times out and you write the partial result as your new state, next week's diff reports every one of that company's jobs as "closed," then as "newly opened" the week after. Never persist state from a run where any source errored:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Skipping state update — incomplete run&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;opened&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;          &lt;span class="c1"&gt;# report opens, suppress the false closures
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one bit me. A source silently returned an empty list, and the diff cheerfully announced that a company had closed every role it had.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pagination that stops early looks the same.&lt;/strong&gt; If your scraper quietly collects 40 of 2,000 jobs because a paginated endpoint throttled you, the diff will report 1,960 closures. Assert that the count you fetched matches the total the API reports, and fail loudly when it doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading momentum
&lt;/h2&gt;

&lt;p&gt;Once you have a few weeks of runs, the count per company is more interesting than any single posting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;momentum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;runs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;                       &lt;span class="c1"&gt;# runs: list of {company: count}
&lt;/span&gt;    &lt;span class="n"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prior&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;runs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;runs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;prior&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prior&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A company that added six roles this week and four the week before is expanding. One that closed eight without opening any either finished a hiring round or stopped hiring. Both are worth knowing, and neither shows up in a single snapshot.&lt;/p&gt;

&lt;p&gt;Weight by seniority if you want a sharper signal. A new VP posting says something different from a new intern posting: senior hires usually mean a new function is being built, not a backfill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduling it
&lt;/h2&gt;

&lt;p&gt;Any scheduler works: cron, GitHub Actions, a hosted platform. Two rules regardless:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Run at the same time each week.&lt;/strong&gt; Diffs across uneven intervals aren't comparable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the raw output of every run&lt;/strong&gt;, not just the diff. When a number looks wrong — and eventually one will — you need the underlying rows to check it against. Every aggregate I've published that surprised me turned out to be a bug, and the raw data is what proved it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Doing it without writing any of this
&lt;/h2&gt;

&lt;p&gt;My ATS scraper reads five applicant tracking systems and returns a normalized record per job, with a stable ID. Point it at a list of companies, schedule it weekly, and diff the datasets. It's free to run.&lt;/p&gt;

&lt;p&gt;The hiring-signal tracker takes it further: it does the diff for you, aggregates per company, and scores hiring momentum by role volume, seniority and week-over-week change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apify.com/get_anything" rel="noopener noreferrer"&gt;Both are on my Apify page&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What this is not
&lt;/h2&gt;

&lt;p&gt;A diff tells you a role appeared. It doesn't tell you the team is good, the salary is fair, or the posting isn't a pipeline-filling exercise that's been reposted quarterly for two years. Being early is a real edge, and it is the only part of this a scraper can give you.&lt;/p&gt;

</description>
      <category>python</category>
      <category>automation</category>
      <category>career</category>
      <category>webscraping</category>
    </item>
    <item>
      <title>How to find Dubai jobs before they appear on job boards</title>
      <dc:creator>Get Anything</dc:creator>
      <pubDate>Mon, 27 Jul 2026 11:28:41 +0000</pubDate>
      <link>https://dev.to/get_anything/how-to-find-dubai-jobs-before-they-appear-on-job-boards-371j</link>
      <guid>https://dev.to/get_anything/how-to-find-dubai-jobs-before-they-appear-on-job-boards-371j</guid>
      <description>&lt;p&gt;There is a gap between the moment a recruiter publishes a job and the moment you see it on LinkedIn. The role goes live on the company's own career page first. Everything else, LinkedIn, Bayt, Indeed, GulfTalent, is downstream of that.&lt;/p&gt;

&lt;p&gt;By the time a Dubai role trends on LinkedIn, it can already have 200 applicants. I scraped one recently that said exactly that: "Over 200 applicants," on a role posted two days earlier.&lt;/p&gt;

&lt;p&gt;You can read the source instead. It's public, and it's free.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the career page is the source
&lt;/h2&gt;

&lt;p&gt;Companies don't type jobs into LinkedIn. They enter them once into an applicant tracking system, Greenhouse, Lever, Ashby, Workday, and that system pushes copies outward to job boards on its own schedule. The company's own careers page reads from that database directly.&lt;/p&gt;

&lt;p&gt;So the career page is always first, always complete, and never reordered by an algorithm that thinks you'd like a role in a different country.&lt;/p&gt;

&lt;h2&gt;
  
  
  The catch: most Gulf giants aren't readable
&lt;/h2&gt;

&lt;p&gt;I checked 42 of the region's most sought-after employers. Only 7 publish their jobs through a system with an open, readable feed.&lt;/p&gt;

&lt;p&gt;Emirates Group, ADNOC, Emaar, Majid Al Futtaim, Qatar Airways, DP World, Emirates NBD, Aramco, talabat and noon all run on older enterprise systems (Taleo, Oracle, Phenom) with no public feed. For those, you genuinely do have to check their careers page by hand, or use an aggregator.&lt;/p&gt;

&lt;p&gt;But the seven that &lt;em&gt;are&lt;/em&gt; readable happen to be among the most interesting employers in the region.&lt;/p&gt;

&lt;h2&gt;
  
  
  The companies you can watch in real time
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Company&lt;/th&gt;
&lt;th&gt;Hiring in&lt;/th&gt;
&lt;th&gt;Currently open&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Aldar Properties&lt;/td&gt;
&lt;td&gt;Abu Dhabi&lt;/td&gt;
&lt;td&gt;48 roles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tamara&lt;/td&gt;
&lt;td&gt;Dubai, Riyadh&lt;/td&gt;
&lt;td&gt;31 roles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deliveroo&lt;/td&gt;
&lt;td&gt;Dubai&lt;/td&gt;
&lt;td&gt;26 roles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Masdar&lt;/td&gt;
&lt;td&gt;Abu Dhabi&lt;/td&gt;
&lt;td&gt;23 roles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Careem&lt;/td&gt;
&lt;td&gt;Dubai&lt;/td&gt;
&lt;td&gt;11 roles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fresha&lt;/td&gt;
&lt;td&gt;Riyadh&lt;/td&gt;
&lt;td&gt;9 roles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lean Technologies&lt;/td&gt;
&lt;td&gt;Riyadh&lt;/td&gt;
&lt;td&gt;4 roles&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Those numbers were live on 10 July 2026. Run the tool below and you'll get today's.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things the data will tell you that surprise most job seekers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Abu Dhabi has more open roles than Dubai.&lt;/strong&gt; In this dataset, 70 versus 50. Aldar and Masdar are hiring heavily, and most job seekers filter on "Dubai" out of habit and never see them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Only 16% of roles are entry level or internships.&lt;/strong&gt; Forty-five percent are mid-senior. If you're early in your career, the Gulf market at these companies is tighter than the headlines suggest, and the honest advice is to target the mid-level roles that ask for three years rather than the graduate schemes that barely exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to watch them, free
&lt;/h2&gt;

&lt;p&gt;I built a free tool that reads all seven career pages at once and hands you a clean spreadsheet. It needs no account with any of these companies and no login.&lt;/p&gt;

&lt;p&gt;Run it with this input:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"companies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"lever:aldar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"greenhouse:careem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"greenhouse:tamara"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"smartrecruiters:masdar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"ashby:deliveroo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"lever:fresha"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"ashby:leantech"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"locationFilter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dubai"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;a href="https://apify.com/get_anything/ats-jobs-scraper" rel="noopener noreferrer"&gt;Run it free here&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Change &lt;code&gt;"Dubai"&lt;/code&gt; to &lt;code&gt;"Abu Dhabi"&lt;/code&gt;, &lt;code&gt;"Riyadh"&lt;/code&gt;, or delete the line to see everything. Export to CSV or Excel with one click.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set it to run every Monday
&lt;/h3&gt;

&lt;p&gt;The real advantage isn't one run, it's the diff. Schedule it weekly and compare: the roles that appear this week are the ones nobody has applied to yet. That's the window where your application is read by a human rather than counted.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you need the big employers too
&lt;/h2&gt;

&lt;p&gt;For the Gulf companies that don't expose a feed, Emirates, ADNOC, Emaar, Majid Al Futtaim and the rest, I built a second tool that searches Bayt, GulfTalent and NaukriGulf together and merges duplicate postings, so you see each role once with every place it appears.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apify.com/get_anything/gcc-jobs-aggregator" rel="noopener noreferrer"&gt;GCC Jobs Aggregator&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A realistic note
&lt;/h2&gt;

&lt;p&gt;None of this replaces a referral. What it does is buy you time: applying on day one instead of day ten, and seeing roles in Abu Dhabi that your Dubai filter was hiding. In a market where mid-senior roles get hundreds of applicants, being early is most of the advantage you can actually control.&lt;/p&gt;

</description>
      <category>career</category>
      <category>jobsearch</category>
      <category>productivity</category>
      <category>webscraping</category>
    </item>
    <item>
      <title>Job postings are the most underrated buying signal in B2B sales</title>
      <dc:creator>Get Anything</dc:creator>
      <pubDate>Mon, 27 Jul 2026 11:28:20 +0000</pubDate>
      <link>https://dev.to/get_anything/job-postings-are-the-most-underrated-buying-signal-in-b2b-sales-5fhi</link>
      <guid>https://dev.to/get_anything/job-postings-are-the-most-underrated-buying-signal-in-b2b-sales-5fhi</guid>
      <description>&lt;p&gt;Sales teams pay a great deal for intent data: who visited your pricing page, who researched your category, who downloaded a competitor's whitepaper. It's inferred, probabilistic, and expensive.&lt;/p&gt;

&lt;p&gt;Meanwhile, companies publish an explicit, dated, public statement of what they intend to do next. It's called a job posting, and almost nobody in sales is reading it systematically.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a job posting actually tells you
&lt;/h2&gt;

&lt;p&gt;A company posting a role has already done the hard part internally. Someone wrote a business case. Someone approved headcount. Finance allocated budget. That process takes weeks and it does not happen for a team that is standing still.&lt;/p&gt;

&lt;p&gt;So the posting tells you, with far more confidence than a page visit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;There is budget.&lt;/strong&gt; Headcount is the hardest spend to approve.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;There is a gap.&lt;/strong&gt; They are hiring for a capability they don't have enough of.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;There is a timeline.&lt;/strong&gt; Roles get posted when the work is already waiting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;There is an owner.&lt;/strong&gt; The hiring manager is your buyer, or reports to them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Read the role and you learn the shape of the problem. A company hiring its first data engineer has a data problem it has decided to stop tolerating. One hiring three sales development reps is about to need sales tooling, enablement content and probably a CRM cleanup. One hiring a compliance officer just entered a regulated market.&lt;/p&gt;

&lt;h2&gt;
  
  
  Volume and seniority carry the signal
&lt;/h2&gt;

&lt;p&gt;One posting is noise. The pattern is signal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Volume&lt;/strong&gt; tells you the scale of the initiative. A company with one open engineering role is backfilling. One with eleven is building something.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Seniority&lt;/strong&gt; tells you whether it's new or routine. Senior and executive roles usually mean a new function is being created, not a seat being filled. When a company posts a VP of a function it didn't previously have, it has decided that function matters — and the person hired into it will spend budget in their first ninety days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Momentum&lt;/strong&gt; tells you the direction. Roles added since last month matter more than a stale list of everything open. A company that added six roles this week is in a different state from one that has had the same six open since March.&lt;/p&gt;

&lt;p&gt;Combine them and you can rank a target list by how likely each account is to be actively spending. That's the whole idea behind a hiring-intent score: weight role volume, weight the seniority mix, weight the change since the last check, and sort.&lt;/p&gt;

&lt;h2&gt;
  
  
  The timing advantage is real
&lt;/h2&gt;

&lt;p&gt;There is a window between a role appearing on a company's own careers page and that role becoming widely visible. During that window, the initiative is being planned rather than executed. That's when a vendor conversation is welcome, and it's the opposite of arriving after the tool has been chosen.&lt;/p&gt;

&lt;p&gt;Checking weekly and diffing against last week's list turns this into a routine rather than a research project. New roles this week are your call list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three ways to get this wrong
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Treating every posting as new.&lt;/strong&gt; Companies repost roles they cannot fill. A "new" listing that has been reposted quarterly for two years is not a signal of momentum; it's a signal of a hard-to-fill role. Diff against your own previous scrapes rather than trusting a posted date.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Confusing titles with seniority.&lt;/strong&gt; In banking and real estate, "Assistant Vice President" is an upper-mid individual contributor, not an executive. I built a classifier that got this wrong and my "executive hiring" numbers were inflated by a third until I read the underlying titles. If you rank accounts by executive hiring, be sure you know what an executive is in that industry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pitching the recruiter.&lt;/strong&gt; The person named on the posting is in talent acquisition. They are not your buyer, they are measured on time-to-fill, and a sales email is friction in their week. Use the posting as evidence about the team, then find the team's leader.&lt;/p&gt;

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

&lt;p&gt;A job posting tells you a company is spending. It does not tell you they are dissatisfied with an incumbent, that your product fits, or that the timing is right for a purchase rather than a hire. Companies also post roles they never fill, post to benchmark salaries, and post to look like they're growing.&lt;/p&gt;

&lt;p&gt;Treat it as the strongest freely available signal of budget and intent, not as a qualified lead. It gets you a well-timed, well-informed first sentence. The rest of selling is unchanged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Doing it without a research team
&lt;/h2&gt;

&lt;p&gt;I built a tracker that aggregates open roles per company, scores hiring intent from volume, seniority mix and week-over-week change, and flags which companies added roles since the last run. It outputs one row per company with the roles attached, so you can read the shape of what they're building.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apify.com/get_anything" rel="noopener noreferrer"&gt;It's on my Apify page&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Run it on a schedule against your target accounts and read the diff on Monday morning. That is the entire workflow.&lt;/p&gt;

</description>
      <category>sales</category>
      <category>data</category>
      <category>automation</category>
      <category>career</category>
    </item>
    <item>
      <title>I tested 42 large employers to see which ones you can actually scrape. Only 7 worked.</title>
      <dc:creator>Get Anything</dc:creator>
      <pubDate>Mon, 27 Jul 2026 11:27:34 +0000</pubDate>
      <link>https://dev.to/get_anything/i-tested-42-large-employers-to-see-which-ones-you-can-actually-scrape-only-7-worked-535e</link>
      <guid>https://dev.to/get_anything/i-tested-42-large-employers-to-see-which-ones-you-can-actually-scrape-only-7-worked-535e</guid>
      <description>&lt;p&gt;Before you write a scraper for a company's careers page, ask a cheaper question: is the data even reachable?&lt;/p&gt;

&lt;p&gt;I checked 42 of the most sought-after employers in one region. Seven were reachable. The other thirty-five were not, and no amount of clever parsing would have changed that.&lt;/p&gt;

&lt;p&gt;Here is how the split works, and how to test any company in a single request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two kinds of applicant tracking system
&lt;/h2&gt;

&lt;p&gt;Companies don't type jobs into job boards. They enter each role once into an applicant tracking system, and that system pushes copies outward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modern ATS platforms publish an open job board API.&lt;/strong&gt; Greenhouse, Lever, Ashby and SmartRecruiters all serve their job data as JSON to anyone who asks. No key, no login, no browser. They do this deliberately: companies want their roles discovered and syndicated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enterprise ATS platforms do not.&lt;/strong&gt; Taleo, Oracle Cloud Recruiting, Phenom and iCIMS render jobs through session-bound, JavaScript-heavy portals with no public feed. Some of them will happily block you for trying.&lt;/p&gt;

&lt;p&gt;That distinction predicts almost everything about whether scraping a given company is going to be pleasant or miserable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result: 7 of 42
&lt;/h2&gt;

&lt;p&gt;The 35 unreachable companies weren't small. They included the region's largest airline, its national oil company, its biggest property developers, its major banks, and its best-known retail groups. Household names, all of them running enterprise portals with no public feed.&lt;/p&gt;

&lt;p&gt;The seven reachable ones had something in common. They were the newer, faster-moving companies: a fintech, a delivery platform, a real-estate developer that recently modernised, a clean-energy firm, a booking platform. Companies that adopted their ATS in the last several years, when Greenhouse and Lever were the default choice.&lt;/p&gt;

&lt;p&gt;This is not a knock on the larger companies. Enterprise HR systems solve problems that a startup doesn't have. It just means their job data isn't available at the source, which is why aggregators show incomplete or stale listings for exactly the employers people search for most.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check any company in one request
&lt;/h2&gt;

&lt;p&gt;Fetch the careers page and look for the ATS signature in the HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;SIGNATURES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;greenhouse&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;boards\.greenhouse\.io/([\w-]+)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lever&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jobs\.lever\.co/([\w-]+)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ashby&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jobs\.ashbyhq\.com/([\w.-]+)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;smartrecruiters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;careers\.smartrecruiters\.com/([\w-]+)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;workday&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;([\w-]+)\.(wd\d+)\.myworkdayjobs\.com/([\w-]+)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;# Present but not scrapeable — worth naming so you stop early:
&lt;/span&gt;    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;taleo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;taleo\.net&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;oracle&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;oraclecloud\.com|/hcmUI/CandidateExperience&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phenom&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phenompeople&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;icims&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;icims\.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;detect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;careers_url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;careers_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ats&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SIGNATURES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ats&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it comes back &lt;code&gt;taleo&lt;/code&gt;, &lt;code&gt;oracle&lt;/code&gt;, &lt;code&gt;phenom&lt;/code&gt; or &lt;code&gt;icims&lt;/code&gt;, stop. You are looking at days of brittle browser automation for data that will break next quarter. Spend the time on the companies you can read properly, and get the rest from job boards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then read the jobs
&lt;/h2&gt;

&lt;p&gt;Once you know the platform, it's one call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Greenhouse
&lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://boards-api.greenhouse.io/v1/boards/stripe/jobs?content=true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jobs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Lever
&lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.lever.co/v0/postings/spotify?mode=json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# SmartRecruiters (paginated, 100 per page)
&lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.smartrecruiters.com/v1/companies/Visa/postings?limit=100&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ashby is an unauthenticated GraphQL POST. Workday is a POST too, and it has one trap that costs everybody a day: &lt;strong&gt;&lt;code&gt;limit&lt;/code&gt; cannot exceed 20.&lt;/strong&gt; Ask for 100 and it returns an empty array rather than an error, which looks exactly like "no more results." It also throttles fast paging, so if your loop treats a failed page as the end of the list, you'll silently collect a fraction of the jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't guess the slug
&lt;/h2&gt;

&lt;p&gt;The board token in those URLs is not the company name. It's whatever the company typed when they set up the account. Read it off the real careers URL rather than guessing.&lt;/p&gt;

&lt;p&gt;I learned this the tedious way: guessing slugs across those 42 employers had roughly a 10% hit rate, and two of the "hits" were the wrong company entirely. One slug belonged to a New York firm that happened to share a name fragment with the company I wanted. Another belonged to an education company rather than the marketplace with a similar name.&lt;/p&gt;

&lt;p&gt;Always print the locations of the jobs you get back before you trust that you found the right board.&lt;/p&gt;

&lt;h2&gt;
  
  
  The no-code version
&lt;/h2&gt;

&lt;p&gt;I packaged all five platforms into a free scraper: it auto-detects the ATS, normalizes the payloads into one schema, and adds seniority and function detection. Free, because these APIs need no proxies and cost nothing to call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://apify.com/get_anything" rel="noopener noreferrer"&gt;All my scrapers are here&lt;/a&gt;&lt;/strong&gt; — the ATS one is free to run.&lt;/p&gt;

&lt;p&gt;The code is open source under MIT if you'd rather read it than run it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Be reasonable about it
&lt;/h2&gt;

&lt;p&gt;These endpoints are public because companies want their jobs found. Cache your results, pause between requests, and don't hammer a small company's board on a cron. The reason all of this works without keys is that nobody has yet had a reason to lock it down.&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>python</category>
      <category>api</category>
      <category>career</category>
    </item>
    <item>
      <title>How to Scrape GCC Job Salaries from Indeed &amp; Glassdoor (UAE, Saudi &amp; the Gulf)</title>
      <dc:creator>Get Anything</dc:creator>
      <pubDate>Mon, 27 Jul 2026 11:23:12 +0000</pubDate>
      <link>https://dev.to/get_anything/how-to-scrape-gcc-job-salaries-from-indeed-glassdoor-uae-saudi-the-gulf-34n5</link>
      <guid>https://dev.to/get_anything/how-to-scrape-gcc-job-salaries-from-indeed-glassdoor-uae-saudi-the-gulf-34n5</guid>
      <description>&lt;p&gt;Salary data is the hardest part of Gulf job data to get — and the most valuable. Most GCC job boards (Bayt, GulfTalent, NaukriGulf) rarely publish pay. The two sources that &lt;em&gt;do&lt;/em&gt; — &lt;strong&gt;Indeed&lt;/strong&gt; and &lt;strong&gt;Glassdoor&lt;/strong&gt; — sit behind Cloudflare and aggressive anti-bot protection, so a normal scraper just gets a "Security Check" page.&lt;/p&gt;

&lt;p&gt;Here's how to pull &lt;strong&gt;structured Gulf jobs *with salary&lt;/strong&gt;* from both, across the UAE, Saudi Arabia, Qatar, Kuwait, Bahrain and Oman — reliably.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Indeed and Glassdoor are worth the trouble
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Indeed&lt;/strong&gt; exposes a real salary range on a large share of GCC listings (often 70–80%), plus employment type, remote flag and even a company rating.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Glassdoor&lt;/strong&gt; shows employer-provided salary ranges and company data that nothing else in the region matches.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're doing salary benchmarking, recruitment intelligence, or building a jobs product, this is the data that makes it credible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The blocker — and how it's solved
&lt;/h2&gt;

&lt;p&gt;Both sites use Cloudflare's "Just a moment…" challenge. Standard HTTP clients (and even patched headless browsers) only get through intermittently. The reliable fix is a &lt;strong&gt;hardened, fingerprint-spoofing browser&lt;/strong&gt; that passes the challenge the way a real visitor does. That's already built into these two ready-made tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;👉 &lt;strong&gt;&lt;a href="https://apify.com/get_anything/indeed-gcc-jobs" rel="noopener noreferrer"&gt;Indeed Scraper — GCC Jobs, Salaries &amp;amp; Employers&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;👉 &lt;strong&gt;&lt;a href="https://apify.com/get_anything/glassdoor-gcc-jobs" rel="noopener noreferrer"&gt;Glassdoor Scraper — GCC Jobs &amp;amp; Salary Data&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Give each a &lt;strong&gt;keyword&lt;/strong&gt; and one or more &lt;strong&gt;countries&lt;/strong&gt;, and get back structured jobs with salary, location, company and apply links.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example (Indeed) output
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Senior Accountant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"company"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Pure Water Technology"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"companyRating"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Abu Dhabi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"United Arab Emirates"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"employmentType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"full-time"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"salaryText"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AED 5,000 - AED 8,000 a month"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"salaryMin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"salaryCurrency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"link"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://ae.indeed.com/viewjob?jk=..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Use it inside ChatGPT or Claude
&lt;/h2&gt;

&lt;p&gt;Both tools are AI-ready via the &lt;a href="https://mcp.apify.com" rel="noopener noreferrer"&gt;Apify MCP server&lt;/a&gt;. Connect the Actor and ask in plain language:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Find accountant jobs in Dubai paying over AED 10,000 a month."&lt;/p&gt;

&lt;p&gt;"What do finance roles in Riyadh pay?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Want the whole GCC job market in one query?
&lt;/h2&gt;

&lt;p&gt;If you care about &lt;em&gt;coverage&lt;/em&gt; more than salary specifically, start with the free flagship:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://apify.com/get_anything/gcc-jobs-search" rel="noopener noreferrer"&gt;GCC Jobs Search&lt;/a&gt;&lt;/strong&gt; — &lt;strong&gt;free&lt;/strong&gt; — searches Bayt, GulfTalent, NaukriGulf, Foundit and Tanqeeb together, with filters for field, experience, city, salary and company, and full recruitment-agency coverage. Then layer Indeed/Glassdoor on top for salary depth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common use cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Salary benchmarking&lt;/strong&gt; by role, city and country&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recruitment intelligence&lt;/strong&gt; — who's hiring, at what level, for how much&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Job boards &amp;amp; aggregators&lt;/strong&gt; that need salary-enriched listings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Market research&lt;/strong&gt; on Gulf hiring and compensation trends&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Legitimacy
&lt;/h2&gt;

&lt;p&gt;These read &lt;strong&gt;public search pages&lt;/strong&gt; — the same pages any visitor sees — with no login and no paywall bypass.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tools: &lt;a href="https://apify.com/get_anything/indeed-gcc-jobs" rel="noopener noreferrer"&gt;Indeed GCC Scraper&lt;/a&gt; · &lt;a href="https://apify.com/get_anything/glassdoor-gcc-jobs" rel="noopener noreferrer"&gt;Glassdoor GCC Scraper&lt;/a&gt; · &lt;a href="https://apify.com/get_anything/gcc-jobs-search" rel="noopener noreferrer"&gt;GCC Jobs Search (free)&lt;/a&gt;. Part of a suite of Gulf-focused public-data tools by &lt;a href="https://apify.com/get_anything" rel="noopener noreferrer"&gt;Get Anything&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>api</category>
      <category>jobs</category>
      <category>dubai</category>
    </item>
    <item>
      <title>How to Get Dubai Land Department (DLD) Transaction Data — Free, No API Key</title>
      <dc:creator>Get Anything</dc:creator>
      <pubDate>Mon, 27 Jul 2026 11:23:11 +0000</pubDate>
      <link>https://dev.to/get_anything/how-to-get-dubai-land-department-dld-transaction-data-free-no-api-key-4bgl</link>
      <guid>https://dev.to/get_anything/how-to-get-dubai-land-department-dld-transaction-data-free-no-api-key-4bgl</guid>
      <description>&lt;p&gt;If you invest in, sell, or analyse Dubai real estate, you already know the problem: portals like &lt;strong&gt;Bayut&lt;/strong&gt; and &lt;strong&gt;Property Finder&lt;/strong&gt; show you what sellers are &lt;em&gt;asking&lt;/em&gt;. They don't show you what buyers actually &lt;em&gt;paid&lt;/em&gt;. For any real decision — a valuation, a rental-yield model, a market report — you need &lt;strong&gt;registered transaction data&lt;/strong&gt; from the &lt;strong&gt;Dubai Land Department (DLD)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The DLD publishes this data. The catch is that the official formats (an OAuth-protected API and login-gated bulk CSVs on Dubai Pulse) take real engineering effort to use. Below is the simplest way to pull it into clean, structured JSON — with &lt;strong&gt;no personal API key and no login&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What DLD transaction data actually contains
&lt;/h2&gt;

&lt;p&gt;The DLD registers every sale, mortgage and gift in Dubai. Aggregated by area, the public data gives you, for any date range:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Community&lt;/strong&gt; (Dubai Marina, JVC, Business Bay…) and &lt;strong&gt;project&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Property type &amp;amp; sub-type&lt;/strong&gt; (Unit/Flat, Villa, Land…)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sale sequence&lt;/strong&gt; — &lt;em&gt;first sale&lt;/em&gt; (usually off-plan) vs &lt;em&gt;resale&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transaction count&lt;/strong&gt;, &lt;strong&gt;total value (AED)&lt;/strong&gt; and &lt;strong&gt;total size (sqm)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Price per square metre&lt;/strong&gt; — the single most useful benchmarking metric&lt;/li&gt;
&lt;li&gt;Everything &lt;strong&gt;bilingual (English + Arabic)&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the ground truth behind the asking prices. A 1-bed in JVC might be &lt;em&gt;listed&lt;/em&gt; at AED 1.2M, but the DLD data tells you the average &lt;strong&gt;first-sale&lt;/strong&gt; price there was AED ~1.05M at ~AED 16,200/sqm last month, while &lt;strong&gt;resales&lt;/strong&gt; ran cheaper per sqm.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fastest way to pull it: the DLD Transactions Actor
&lt;/h2&gt;

&lt;p&gt;Instead of wrangling OAuth tokens and 300 MB CSVs, you can use a ready-made tool on Apify:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://apify.com/get_anything/dld-transactions-scraper" rel="noopener noreferrer"&gt;Dubai DLD Transactions Scraper&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You give it three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transaction type&lt;/strong&gt; — Sales, Mortgages or Gifts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;From date&lt;/strong&gt; and &lt;strong&gt;To date&lt;/strong&gt; (&lt;code&gt;YYYY-MM-DD&lt;/code&gt;) — history goes back years&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…and it returns one clean record per &lt;strong&gt;community × sub-type × sale sequence&lt;/strong&gt;, with &lt;code&gt;pricePerSqmAed&lt;/code&gt;, &lt;code&gt;avgTransactionValueAed&lt;/code&gt;, &lt;code&gt;avgSizeSqm&lt;/code&gt;, &lt;code&gt;transactionCount&lt;/code&gt; and &lt;code&gt;totalValueAed&lt;/code&gt;. Run it from the Apify Console, via API, on a schedule, or as an &lt;strong&gt;AI tool&lt;/strong&gt; inside ChatGPT/Claude ("&lt;em&gt;what was the average price per sqm in Dubai Marina this month?&lt;/em&gt;").&lt;/p&gt;

&lt;h3&gt;
  
  
  Example output
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"community"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JUMEIRAH VILLAGE CIRCLE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"communityAr"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"جميرا فيليج سيركل"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"propertySubType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Flat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"saleSequence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"First"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transactionCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;118&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"avgTransactionValueAed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1051387.56&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pricePerSqmAed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;16218.46&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fromDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"toDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-31"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What you can build with it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Price benchmarking&lt;/strong&gt; — real AED/sqm by community, this month vs last year&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rental yield / ROI&lt;/strong&gt; models — pair sold prices with asking rents&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Off-plan vs ready&lt;/strong&gt; market share (first-sale vs resale split)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Market reports &amp;amp; dashboards&lt;/strong&gt; that quote registered prices, not seller hopes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Valuation models&lt;/strong&gt; grounded in actual comparables&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pair it with asking-price data
&lt;/h2&gt;

&lt;p&gt;The complete picture is &lt;em&gt;asking vs sold&lt;/em&gt;. Combine the DLD data with live listings from the &lt;strong&gt;&lt;a href="https://apify.com/get_anything/bayut-property-scraper" rel="noopener noreferrer"&gt;Bayut Property Scraper&lt;/a&gt;&lt;/strong&gt; (price, beds, baths, area, agent) to see the gap between what's listed and what's transacting in the same community.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on legitimacy
&lt;/h2&gt;

&lt;p&gt;This uses &lt;strong&gt;public government data&lt;/strong&gt; — the same figures the DLD publishes on its own transactions page for every visitor. No login, no scraping of private records, no paywall bypass.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Get the tool: &lt;a href="https://apify.com/get_anything/dld-transactions-scraper" rel="noopener noreferrer"&gt;Dubai DLD Transactions Scraper on Apify&lt;/a&gt;. Part of a suite of Gulf-focused public-data tools by &lt;a href="https://apify.com/get_anything" rel="noopener noreferrer"&gt;Get Anything&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>api</category>
      <category>dubai</category>
      <category>realestate</category>
    </item>
  </channel>
</rss>
