Turning Mubawab Listings Into a Dataset for Moroccan Housing Models
Mubawab-Housing-Scraper is an Apify Actor that turns mubawab.ma property listings into structured CSV or JSON records, with fields suited to price modeling and market analysis.
A structured-data path for Moroccan property listings
Housing analysis starts with a mundane but difficult prerequisite: a dataset whose rows mean the same thing. A property portal can show prices, surfaces, room counts, locations, and listing descriptions, but those details live across search pages and individual listing pages. Before a builder can train a model or create a market dashboard, they need to collect, normalize, and export those attributes in a consistent form.
MuLIAICHI/Mubawab-Housing-Scraper is designed for that step. It is an open-source Apify Actor, a packaged program that runs on the Apify platform, for gathering real estate listings from mubawab.ma. Its README frames the output as a Moroccan counterpart to the housing dataset used in a classic machine learning example: a flat dataset intended to be usable for modeling rather than a collection of raw pages.
That focus makes the project relevant to builders working on price prediction, geographic analysis, property-market dashboards, or property assistants that need structured listing inputs. It also provides a local route for developers who want to run the scraper outside the hosted Apify workflow.
One listing, one normalized record
The actor maps each scraped listing to a row with both raw and derived values. The documented schema includes priceDh, living area in surfaceM2, room and bathroom counts, floor, property type, condition-related fields, city, neighborhood, transaction type, listing URL, title, and an ISO-8601 scrape timestamp.
A particularly useful derived field is pricePerM2, calculated as price divided by surface. Keeping it alongside the underlying price and area is practical: analysis can use the ratio directly, while a modeling workflow can decide whether to recompute or exclude it to avoid leaking a target-derived value into a price prediction task.
The schema also allows missing values for many fields. That is an important reality of listing data, where a publisher may omit surface, floor, or condition. Rather than pretending every page is complete, the documented output represents many values as nullable. The project’s Apify output includes run statistics with null rates per field, giving users a way to inspect completeness before treating an export as training-ready.
The README documents two dataset views in the Apify Console. An overview exposes the scraped records for inspection, while an ML-ready view limits the output to 12 feature columns for CSV export. That separation reflects a useful operational distinction: the full record is helpful for auditing and linking back to a listing, while a smaller feature set is more convenient for an initial model pipeline.
A two-stage crawl, from search results to detail pages
The documented crawl is deliberately split into two phases. First, the actor builds or accepts seed URLs and sends them to a LISTING_PAGE handler. That handler parses search-result pages, extracts individual listing URLs, and follows the next-page link through pagination. Those detail URLs are then queued for a DETAIL_PAGE handler.
On the second phase, the detail-page parser extracts the listing fields and normalization utilities clean values before the actor writes the record through Actor.pushData() to a named dataset, mubawab-housing. This division is straightforward, but it matters. Search pages are useful for discovery, while detail pages are where the project expects to find the attributes that make a row analytically useful.
The repository documents CSS selector fallbacks for different page structures, including individual listings and project or ensemble listings. That is a practical response to a common scraping problem: nominally similar pages can place equivalent information in different parts of the HTML. It also saves the parser from relying on one fragile layout assumption.
When a page cannot be parsed, the advertised output includes debug HTML snapshots. Combined with run statistics, this creates a feedback loop for maintenance. A user can see whether a site change is creating missing fields or failures, then inspect captured page markup instead of trying to reconstruct the problem from a final CSV alone.
Configurable scope, with clear limits
The actor accepts inputs for transaction type, cities, property types, a maximum listing count, concurrency, custom start URLs, and proxy configuration. By default, the README lists nine cities: Casablanca, Marrakech, Rabat, Agadir, Tanger, Fès, Meknès, Oujda, and Tétouan. It also documents property categories including apartments, villas, houses, riads, land, offices, and commercial properties.
For a bounded exploratory run, maxListings can cap the number of detail pages, and the README gives an example with 10 listings. For more focused collection, a user can select cities and property types, or supply start URLs instead of relying on generated seeds. The default documented cap is 5,000 listings, while a value of zero is described as unlimited.
Concurrency is also exposed, with a documented default of five parallel browser tabs and a maximum of 20. More concurrency can make a crawl move faster, but it is not automatically better. The README pairs this control with polite delays of 500 to 800 milliseconds between requests and recommends residential proxies because the target site can block datacenter IPs, issue CAPTCHAs, or return 403 errors. In practice, reliable collection is constrained by site behavior as much as by local throughput.
Why a browser-based scraper is part of the design
The confirmed stack is JavaScript on Node.js 20 or later, using Apify, Crawlee, and Playwright. The package manifest lists apify 3.2.6, crawlee 3.10.3, and playwright 1.44.0 as dependencies.
Playwright provides Chromium browser automation. The README says mubawab.ma is JavaScript-rendered, which is why the project uses a headless browser rather than Cheerio, a library commonly used to parse static HTML. In other words, the scraper can wait for the page as a browser would render it before running its selectors.
Crawlee supplies the crawl-oriented structure around routing and queues, reflected in the listing and detail labels described by the project. Apify provides the Actor runtime and storage interface used to persist records and make them exportable as CSV or JSON. Together, those pieces support both local execution through the Apify CLI and deployment to an Apify account.
The repository includes a short Python example for the next stage of the workflow: load a CSV with pandas, drop rows missing price or surface, one-hot encode categorical fields, create a rooms-per-square-meter feature, then train and evaluate a random forest regressor. It is an example rather than a claim of model quality, but it clarifies the intended handoff from web collection to analysis.
For builders who need a repeatable starting point for Moroccan property data, the implementation is worth inspecting. The repository documents the schema, crawl controls, output views, and the operational caveats that come with maintaining a browser-based scraper.
Turn your GitHub repository into a story worth sharing.
Connect a public repository, generate a reviewable draft, and publish it with Reather.
