Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
161567 stories
·
33 followers

Airbnb has no public API - how to get listing and price data for any city (Python)

1 Share

People search "Airbnb API" thousands of times a month, but Airbnb has never had a public API for listing
data. Its partner APIs are for hosts and software vendors managing their own listings. If you're sizing a
short-term-rental market, pricing your own place against the neighbourhood, or doing a data-science project, you
end up doing one of these:

  1. Download a static dataset (Inside Airbnb, Kaggle): free and great for research, but a snapshot, with no prices for your dates.
  2. Pay for a market-analytics subscription: polished dashboards, priced for professionals.
  3. Collect the public search results yourself.

This post is about option 3, done without writing or maintaining a scraper.

The 270-result wall

An Airbnb search tops out at 15 pages of 18 results: about 270 listings per query, however many exist. A city
like Lisbon has thousands. Most tools stop at that wall, and the popular Airbnb scraper on the Apify Store states
a 240-result limit.

The way around it is to split the query until each piece fits under the wall: first by nightly-price band,
then, if a band is still full, into map quadrants. Then de-duplicate by listing id. I built that into an
Apify Actor called "exhaustive mode". In a cloud test on
Porto for four nights it returned 330 unique listings from 28 sub-queries (41 HTTP requests) in 50 seconds,
past the single-query ceiling. A Lisbon test hit the requested 400-listing cap with zero duplicate ids.

Script: a city's listings for your dates

import csv, os, requests

resp = requests.post(
    "https://api.apify.com/v2/acts/feedsmith~airbnb-listings-scraper/run-sync-get-dataset-items",
    params={"timeout": 600},
    headers={"Authorization": f"Bearer {os.environ['APIFY_TOKEN']}"},
    json={
        "locations": ["Porto, Portugal"],
        "checkIn": "2026-11-06", "checkOut": "2026-11-09",
        "exhaustive": True, "maxItems": 300, "currency": "USD",
    },
    timeout=630,
)
resp.raise_for_status()
listings = resp.json()

fields = ["id", "name", "roomType", "priceNightly", "priceTotal", "nights",
          "rating", "reviewsCount", "latitude", "longitude", "url"]
with open("airbnb_listings.csv", "w", newline="") as f:
    w = csv.DictWriter(f, fieldnames=fields, extrasaction="ignore")
    w.writeheader()
    w.writerows(listings)
print(len(listings), "listings")

Real rows from that run (2026-09-18):

id,name,roomType,priceNightly,priceTotal,nights,rating,reviewsCount,latitude,longitude,url
38350016,São Bento Dream Loft - City's  Historic Center,entire_home,87.67,263,3,4.92,241,41.1444,-8.6113,https://www.airbnb.com/rooms/38350016
19666593,Casas de SantAna - Old town amazing views,entire_home,130.33,391,3,4.98,632,41.1426,-8.6133,https://www.airbnb.com/rooms/19666593

priceTotal is what Airbnb shows for your whole stay. priceNightly is that total divided by the nights, so it
includes cleaning and service fees spread over the stay. That's usually what you want when comparing listings, but
it can sit a bit above the "per night" figure Airbnb's own price filter uses.

A trap when computing market stats

My first version of this script printed median prices from those 300 listings: $158.67 per night for entire homes,
and a p75 of $536. That p75 is wrong as a market figure. Exhaustive mode walks the city price band by price
band
, so a run that stops at maxItems has only covered some of the bands. The sample is skewed, not random.

Two ways to get honest numbers:

  • Raise maxItems until the run finishes below it. Then you have every listing Airbnb returns for those dates and filters.
  • Or narrow the search (a neighbourhood's map bounds, a price range, roomType) so the full set is small.

The example repo's script now prints a warning when a run was cut off by maxItems.

Details per listing

Set "includeDetails": true to also fetch each listing page: description, the amenities list, house rules, bedrooms,
beds, bathrooms, guest capacity, the host's first name and superhost flag, and six sub-ratings (cleanliness, accuracy,
check-in, communication, location, value). In a four-listing test all four came back complete.

Cost

What Price
Listing (search data) $1.20 per 1,000
Listing details (optional) $2.00 per 1,000

The Porto run above (300 listings) cost the user about $0.36. Duplicates and failed pages are free.

Caveats

  • Prices depend on dates, guests and currency. Always pass dates if you care about prices.
  • About one search page in five comes back as an empty page shell. The Actor retries it up to three times, and if it still fails it logs a warning rather than dropping data silently.
  • It collects public listing data only: no host contact details and no guest reviews' author data.
  • Airbnb's terms restrict automated access. Use the data responsibly and check what's appropriate for your use case and jurisdiction.

Links

Not affiliated with Airbnb. Disclosure: I built this Actor. This article was drafted with AI assistance (Claude); every command, number and output above comes from real runs on 2026-09-18.

Read the whole story
alvinashcraft
1 hour ago
reply
Pennsylvania, USA
Share this story
Delete

Fake LastPass Authenticator Installs a Microsoft-Signed Driver That Kills 145 Security Tools

1 Share

TL;DR

  • what: A fake LastPass Authenticator installer on GitHub side-loads a malicious DLL, escalates to SYSTEM, and installs a Microsoft-signed kernel driver named Alinubx.sys that terminates 145 antivirus and EDR processes before running a credential stealer.

A fake LastPass Authenticator installer distributed through GitHub installs a Microsoft-signed Windows kernel driver that terminates 145 named antivirus and EDR processes, then runs a credential stealer against browsers, cryptocurrency wallets and Windows Credential Manager. LastPass and Delphos Labs published the joint analysis on September 17. When researchers checked the driver in August it scored zero detections on VirusTotal, and it was not on Microsoft's vulnerable driver blocklist. As of the report, it still is not.

LastPass says none of its own systems, services or customer vaults were touched. The attackers borrowed the brand, not the infrastructure. That distinction matters for your incident scoping and for nothing else: a user who ran this file lost every credential stored on that machine, regardless of which password manager they use.

The lure ranks in search results

The entry point is a GitHub page at github.com/LastPass-Authenticator that looks like a real LastPass product page and ranks for search terms like "LastPass Authenticator download". Clicking the download button routes the visitor through several GitHub pages to an attacker-controlled server, which serves a large ZIP. The genuine LastPass Authenticator ships from lastpass.com and the official mobile app stores. It is not distributed on GitHub, and that single fact is the cheapest user-facing control you have here.

The archives observed were 148 MB and 127.9 MB, padded with junk files so that scanners enforcing a maximum file size skip them entirely. Inside sits a renamed copy of vsdbg.exe, a legitimate Microsoft debugging tool, placed next to a malicious file called vsdbg.dll. Running the fake installer makes Windows load the attacker's DLL from the same folder, the classic DLL side-loading pattern. The loader then attempts three separate routes to administrator rights, reaches SYSTEM, and registers the kernel driver as a service.

A signed driver that kills 145 processes

The driver, which the researchers named Alinubx.sys, carries a hardcoded list of 145 antivirus and security process names and terminates every one it finds running. It does this from the kernel, below the layer where user-mode security agents live, so those agents cannot block the kill or record it. This is bring your own vulnerable driver, BYOVD, with one twist: the driver is not exploited, it is simply used as designed.

It is signed through the Microsoft Windows Hardware Compatibility Publisher chain, with a signing date of March 2023, years ahead of this campaign. Windows trusts it on sight.

Attestation is not assurance — The researchers put it plainly: "Microsoft attestation proves a driver passed through a trust pipeline. It does not prove the driver is safe." Treat WHCP signing as provenance metadata, not as a verdict, and do not let driver allowlisting stop at "signed by a Microsoft chain".

The driver can do more than kill processes. Its code also supports hiding files, injecting into other programs, and rerouting web traffic, but those functions require a configuration file the operators did not include, so they stayed dormant. The kill list on its own was sufficient.

The rename that erased detection

Alinubx.sys is a renamed copy of CcProtect.sys, a driver from the Chinese disk encryption product CnCrypt. That original is already catalogued on LOLDrivers as a process killer, with public proof-of-concept code. The two files share the same product name, version and submitter. Only the file name and the description changed. In August the known original was flagged by 7 of roughly 70 antivirus engines. The renamed copy was flagged by none of them.

Microsoft's vulnerable driver blocklist, enabled by default since the Windows 11 2022 Update, is the control most teams assume covers this case. Delphos checked it on August 20 and found neither the renamed driver nor the known original listed. The rename did not defeat the blocklist, because the original was never on it. The blocklist matches known file hashes, so any rename or recompile produces a hash it does not carry.

Delphos reported the driver to Microsoft on August 19. Microsoft replied that the behavior does not meet its definition of a security vulnerability, because the driver is not a Microsoft component, and directed the researchers to the separate channel that evaluates drivers for the blocklist. Delphos resubmitted there the same day. At publication on September 17, the driver was still not blocked.

What the stealer collected

With security software terminated, the stealer pulled saved passwords from more than two dozen browsers, cryptocurrency wallet files, and active login sessions for Discord, Steam and Telegram. It also took the contents of Windows Credential Manager and any file with a name containing "password", "seed" or "recovery". For Chrome and Edge, which use Google's app-bound encryption specifically to stop this, the stealer injects code into the browser process and asks the browser's own service to perform the decryption. Everything is packed into a ZIP and shipped to an attacker server.

⚠️ It survives the reboot and re-arms itself — The driver stays loaded. On every restart it re-kills security tooling and re-runs the stealer, which means the tools you would normally use to clean the machine are dead before they start. A host that ran this payload is a kernel-level compromise: give it a kernel-level forensic examination or rebuild it.

If a machine ran the installer

  • Treat every password saved in a browser on that host as stolen, along with wallet files, Discord, Steam and Telegram sessions, and everything in Windows Credential Manager.
  • Rotate those credentials from a separate clean device, never from the affected machine, since the stealer is still running there.
  • Review account activity on the affected services for logins, transfers or session grants you did not perform.
  • Rebuild the host, or at minimum image it and examine it offline. Kernel persistence means in-place cleanup by a user-mode agent is not reliable.

What to hunt for

The researchers stress hunting on lineage and behavior rather than a single file name, since the operators already demonstrated that a rename costs them nothing. Known artifacts from this campaign:

  • Service: a service created with the name NvFsFilter
  • File: a driver written to C:\Windows\System32\drivers\nvfsflt64.sys
  • Signer: driver signing details naming Henan Dafeng Software or containing "CnCrypt"
  • Device: the device path \.\Alinubx
  • Behavior: any kernel driver load followed closely by security processes terminating

Get-WinEvent -FilterHashtable @{LogName='System'; Id=7045} | Where-Object { $.Message -match 'NvFsFilter|nvfsflt64' }
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-Sysmon/Operational'; Id=6} | Where-Object { $
.Message -match 'CnCrypt|Henan Dafeng' }

LOLDrivers publishes a community detection for this exact driver. It matches by hash, so it inherits the same weakness the blocklist has: it stops working the moment the operators change the file again. Build the behavioral rule as well, and treat any newly registered kernel service followed by EDR agent silence as an incident until proven otherwise.

Originally published on RedEye Threat Intelligence.

Read the whole story
alvinashcraft
1 hour ago
reply
Pennsylvania, USA
Share this story
Delete

A Simple Alpine.js Template with Vite and TypeScript

1 Share

This will be a quick one - but as I think more about TypeScript and how I'd like to learn (and play, and build silly demos), I naturally thought it may make sense to look at how I'd use Alpine.js with that stack. Alpine's been my go to library for web apps that reach the level of complexity where I'd like some help with DOM manipulation and such. I don't always use it, because (imho) the default stack should be as vanilla as possible (obviously I'm going a bit off ranch with these explorations into TypeScript and Vite) but Alpine is lightweight and simple and just a great little library in general. What follows isn't necessarily a "template" as it's got a bit of template code with it, but I thought it would be helpful to share. As always, let me know what you think!

Step One - the Scaffold

In my first post a few days ago, I mentioned that the Vite scaffold support lets you create a vanilla application with TypeScript. I used that for my code and removed as much of the demo code as I could. I do wish Vite's template was a bit more minimal.

Step Two - Adding Alpine

Usually I make use of the Alpine CDN in my demos, for this one, I instead installed it as a dependency. I also installed the Alpine types dependency. Here's my package.json:

{
  "name": "alpine-test-1",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview"
  },
  "devDependencies": {
    "@types/alpinejs": "^3.13.11",
    "typescript": "~6.0.2",
    "vite": "^8.3.0"
  },
  "dependencies": {
    "alpinejs": "^3.17.4"
  }
}

Step Three - the HTML

Ok, so this part is really simple. Alpine "ties" to your DOM usually via an x-data attribute. That means this is the bare minimum:

<div x-data="app">
</div>

But of course you'll have more Alpine directives and more HTML. Also, "app" is not required, but is the name I pretty much use all the time. For my template/demo, I output a couple of variables and included a few click directives just to test stuff out. Oh, I also added Simple.css to just to make it look prettier. That's absolutely not necessary. The Vite demo actually imports CSS in their main TypeScript file (you can see that on Stackblitz here) and I'm not sure how I feel about that. I know it minimizes the code, but it feels really weird to me. Anyway, here's my HTML:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">
    <title>alpine-test-1</title>
  </head>
  <body>
    <div x-data="app">
      <p>
      <span x-text="message"></span>
      </p>
      <p>
      <button @click="meow()">Meow (default)</button>
      <button @click="meow('Purr...')">Meow (custom)</button>
      </p>
      <ul>
        <template x-for="(cat, index) in cats" :key="index">
          <li x-text="cat.name"></li>
        </template>
      </ul>
    </div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

Step Four - Enter the TypeScript

Ok, for the final bit - my TypeScript code:

import Alpine from 'alpinejs';

type Cat = {
  name: string;
  breed: string;
  gender: 'male' | 'female';
};

Alpine.data('app', () => ({
  message:'Hello from Alpine!',
  cats: [] as Cat[],
  init() {
    this.cats.push({ name: 'Whiskers', breed: 'Siamese', gender: 'male' });
    this.cats.push({ name: 'Fluffy', breed: 'Persian', gender: 'female' });
  },
  meow(message:string = 'Meow!') {
    alert(message);
  }
}));

Alpine.start();

Let me point out the important bits. Unlike my usual Alpine demos, I import Alpine here from the local install. I also need to fire Alpine.start() manually. But outside of that, it's pretty vanilla Alpine - define the app and include relevant variables and methods, which in this case is pretty small.

Now obviously I'm trying to learn TypeScript as well, but I kept it pretty short here. I've defined a type for Cat and when I worked with the data, my editor (Visual Studio Code) provided support as I'd expect - it knew the right parts of a cat and correctly flagged an error if I tried to include something that wasn't defined in the type.

As a reminder, this is all done in the editor - it wouldn't be a "real" error in production - but the idea here - and the benefit of TypeScript - is that I'd (hopefully!) catch it much earlier.

Outside of that, you can also see where I define cats as an array of Cat and specify that the message argument to meow is string.

Again - this is pretty minimal TypeScript usage, but I dig it, and I can really see how in some of my larger Alpine demos in the past, the additional safety/checking/etc would have been real helpful I think.

There Is No Step Five

If you want the code to try it yourself, you can copy it from here: https://github.com/cfjedimaster/typescript-stuff/tree/main/alpine-test-1. I know this was pretty short, but I'd still love any feedback or advice, so hit up the comments below!

Read the whole story
alvinashcraft
1 hour ago
reply
Pennsylvania, USA
Share this story
Delete

Microsoft is laying off 268 Xbox staffers and Ninja Theory may close

1 Share
Vector collage of the Xbox logo.

Microsoft is laying off around 260 Xbox employees today, as part of an ongoing "reset" of its gaming business. 1,600 Xbox employees were impacted by layoffs in July, with Xbox CEO Asha Sharma describing the cuts as "the most significant restructure in Xbox history." Today's layoffs are part of the 3,200 roles that were previously confirmed to be impacted over the course of Microsoft's 2027 financial year (which ends in June 2027).

"We are eliminating 268 roles across Halo Studios, other first-party studios, and the XGS management and central functions layer," says Xbox chief content officer Matt Booty in a memo. "The actions completed since …

Read the full story at The Verge.

Read the whole story
alvinashcraft
1 hour ago
reply
Pennsylvania, USA
Share this story
Delete

Activision is taking over Halo as part of a massive Xbox shake-up

1 Share
Promotional art from Halo: Campaign Evolved.

Microsoft is announcing a massive restructuring of its Xbox studios today, months after laying off 1,600 Xbox employees and selling off studios. The latest changes will see Activision take on the Halo and Sea of Thieves franchises, as well as Age of Empires. Microsoft is also laying off around 268 Xbox employees as part of this restructuring, and likely closing Ninja Theory.

Halo Studios will be significantly impacted by today's changes and layoffs. The studio will continue to support existing titles like Halo Infinite and Halo: Campaign Evolved, but the next flagship installment in the Halo franchise will come from Activision instead.

"Ac …

Read the full story at The Verge.

Read the whole story
alvinashcraft
1 hour ago
reply
Pennsylvania, USA
Share this story
Delete

System.Text.Json vs Newtonsoft.Json on a Crestron MC4-R (Mono/net472): measured on the processor

1 Share

Most published STJ-vs-Newtonsoft benchmarks run on desktop or server .NET 8–10. I needed numbers for the runtime my Crestron Home drivers actually run on, so I measured on the processor itself.

Setup

  • Hardware: Crestron MC4-R (hardware version 2), firmware 2.8000.00057 (built 17 Sep 2025), Crestron Home 4.011.0322
  • Runtime: Mono 6.12.0.107, CLR version 4.0.30319.42000, OS reported as Unix 4.19.35.5149
  • Serialisers: System.Text.Json 10.0.12 and Newtonsoft.Json 13.0.5-beta1. The Newtonsoft version was the driver's existing dependency. It's a prerelease that has since been unlisted on NuGet, and the latest stable is 13.0.4. I haven't rerun against 13.0.4. Both serialisers were merged into the test package as private copies.
  • Payload: a synthetic 409-byte response shaped like a WeatherLink API response, deserialised into the same nullable DTOs, which carry attributes for both serialisers. STJ runs with AllowReadingFromString, and Newtonsoft uses its defaults. Results are checked for correctness.
  • Method: Debug harness build (the serialisers are the precompiled NuGet release binaries). 200 warm-up parses per serialiser, then 4 alternating rounds of 3,000 synchronous deserialisations, with a GC before each round. Time is measured with Stopwatch and allocations with GC.GetAllocatedBytesForCurrentThread.
  • Harness: NUnit fixtures run remotely on the processor through my own test adapter (CrestronHomeNUnit).

Results (MC4-R, 3,000 parses per round)

Round System.Text.Json Newtonsoft 13.0.5-beta1 STJ time saving
0 324.553 ms 440.976 ms 26.40%
1 324.821 ms 442.914 ms 26.66%
2 323.404 ms 440.126 ms 26.52%
3 324.255 ms 441.608 ms 26.57%
Per parse 0.108 ms 0.147 ms 26.5%
Allocated per parse 1,024 B 3,816 B 73.2% fewer

Each serialiser's round times stayed within 0.63% of its mean. A short check on desktop .NET 10 also favoured STJ, but its timings varied too much to quote.

Follow-up: STJ vs the processor's resident Newtonsoft (22 Sep)

A second fixture compared bundled STJ with the Newtonsoft.Json already on the processor. That's standard Newtonsoft 13.0.2 at /simpl/app00/, left out of the merge and checked by identity before timing. As in the first run, both packages were Debug harness builds. They ran one after the other in separate host processes.

Round (3,000 parses) Bundled STJ 10.0.12 Resident Newtonsoft 13.0.2
0 324.699 ms 431.950 ms
1 388.026 ms 430.408 ms
2 326.794 ms 430.696 ms
3 326.157 ms 431.120 ms
Per parse 0.114 ms 0.144 ms
Allocated per parse 1,024 B 3,816 B

STJ was about 21% faster overall. Leaving out the slow round 1, it was about 24% faster. The allocation difference is identical to the first run. The raw results include process memory snapshots, but the two runs started from different managed heaps (about 67 MB and 20 MB). Those figures measure host-process state, not serialiser cost, so don't compare them.

What this does and doesn't show

  • On Mono, STJ allocates about a quarter as much as Newtonsoft and parses about 21–27% faster for this payload.
  • These tests used warmed parsing and one small payload shape. They don't cover cold start, serialisation, peak memory or whole-driver CPU.
  • At weather-polling intervals the absolute CPU savings are small. The allocation reduction matters more on a processor shared with other drivers.

Deployment note for Crestron developers

On this firmware, both standard Newtonsoft.Json 13.0.2 and Newtonsoft.Json.Compact 4.0.8.0 are resident in /simpl/app00/, and System.Text.Json isn't. Crestron's driver best-practices page lists Newtonsoft.Json among the DLLs Home loads, but still advises merging your dependencies to avoid DLL conflicts. So STJ has to ship inside your package, and so should Newtonsoft if you use it. None of this was measured against the Compact assembly. That's one firmware on one processor, not a platform guarantee.

Reproduce it

Fixtures, payload, raw results and processor harnesses

AI disclosure: the benchmark fixtures and processor runs were done by GPT-6 Astra, and this write-up was generated by Claude, under my direction.

Read the whole story
alvinashcraft
1 hour ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories