Scrape ouedkniss.com — Algeria's largest classifieds marketplace. Paste a search, category, store or listing URL: get title, price in DZD, condition, wilaya, category, seller contacts and photos.
The Category menu view renders each category's icon as an image instead of its raw URL text.
Docs
The README now spells out what this Actor reaches beyond a search feed: seller (store and member) pages, similar listings, the category tree, the sponsored banner, and listing detail with contact info — each with the dataset it produces and what it is for.
Every Actor in this repository gained the same kind of section: the surfaces a pasted link can reach that a keyword search cannot.
[3.3] - 2026-10-24
Fixed
Startup no longer stalls when a network exit IP stops responding. Every connectivity check is now time-bounded, and a probe that gets no answer is abandoned instead of being retried for minutes. A blocked Apify Proxy session can no longer consume the whole run before the first request is scraped.
Clear failure when no network path is available. If neither direct egress nor any Apify Proxy session returns data from ouedkniss.com, the run stops immediately with an actionable message instead of finishing with an empty dataset.
Changed
Direct egress is tried first. When the platform's own address can reach the site, the run skips Apify Proxy entirely, which saves proxy cost and startup time.
Residential fallback when datacenter IPs are rejected. The run now walks direct egress → Apify datacenter proxy → Apify residential proxy, each step time-bounded. Residential traffic is billed by data volume on the Apify account running the Actor; a typical run uses a few megabytes. Proxy fallback attempts (proxyMaxAttempts) controls the datacenter step and is capped at 3.
[3.2] - 2026-10-06
Added
Paste a URL, the Actor figures out the task. New URL (pageUrl) input — the one field a run needs. Paste any ouedkniss.com page URL from the browser's address bar and the Actor detects what it points at: a search or category page (https://www.ouedkniss.com/s/1?c=automobiles-voitures&keywords=t-roc, https://www.ouedkniss.com/automobiles_vehicules/1?keywords=t-roc, https://www.ouedkniss.com/t-roc-r/1) runs that search with every filter the link carries — category, keywords, wilaya, commune, price range, photos/price flags, trade-in, delivery, seller type, sorting, and the category's own spec filters such as brand, year or mileage; a seller page (/store/… or /membre/…) scrapes that seller's listings; a listing page (…-d{id}) scrapes that listing with full detail data. A results URL starts the walk at the page stated in the link and paginates forward. Pasting a page that names no task (home page, auth pages) fails at startup with a message pointing at the right kind of link.
Browse a category reads the whole pasted URL too. Keywords and any URL-carried filters on a category page link are applied instead of being ignored.
The sponsored banner follows the search.Sponsored listings (topAnnounList) now picks up the category and the keywords of the URL it is given — categoryUrl, or the URL field when no categoryUrl is set. A search link like https://www.ouedkniss.com/automobiles_vehicules/1?keywords=t-roc returns the promoted listings matching that search (previously the keywords were ignored and the banner always covered the whole category). That is the same scoping the site's own banner applies; the remaining search filters (wilaya, price, …) don't narrow the banner on the site either.
Changed
The per-task controls moved into a collapsible "Legacy inputs (advanced)" section.What to scrape (startMode) defaults to auto (URL detection) and sits at the top of that section, followed by the per-mode URL fields (Search keyword, Category page URL, Seller page URL, Listing URLs) and the per-filter fields. New runs don't need them; all of them keep working for existing integrations and remain the way to reach the tasks a URL can't express: the category-menu walk (categories), the sponsored banner (topAnnounList) and similar products (similar). In explicit modes every surface-specific field falls back to pageUrl when its own field is empty.
When a URL is pasted, it wins.pageUrl (and the filters it carries) takes precedence over the legacy keyword and filter fields in the same run.
Fixed
Trade-in and delivery filters work again. The site's API stopped accepting the boolean form the Actor previously sent for Only listings that accept trade-in (filterExchange) and Only listings with delivery (filterDelivery) — runs using them failed with the server's validation error. The filters are now sent in the same form the site's own frontend uses, and the behavior of setting them remains unchanged.
Migration
Runs that set both a URL (pageUrl) and the legacy keyword/filter fields change behavior: the URL now takes precedence. Remove pageUrl to keep the old field-driven behavior. Integrations that relied on startMode being required can keep sending it — the explicit values work exactly as before.
[3.1] - 2026-09-17
Added
Category nodes now carry their browse url. Rows from Category menu (categories) include a ready-to-use category page link (e.g. https://www.ouedkniss.com/automobiles_vehicules/1), so a node can be fed straight into the Category page URL (categoryUrl) input without rebuilding the link from its slug. Rare root-level nodes that the site returns without a slug keep url: null. The Category menu dataset view shows the new column.
[3.0] - 2026-09-04
Removed
The seller-info row in Seller listings mode. Runs previously emitted one extra row describing the seller itself (identified by attributes.__kind === "seller", carrying the store's public contact block when includeContactInfo: true). Seller listings now returns listing rows only — every row is one of the seller's listings. This also removes the dataset's Seller profile view and the __kind system key.
Changed
Include real phone + email is effective in Specific listing URLs mode only.Seller listings used to attach a store's public contact block to the seller-info row; without that row there is no surface for it, so the toggle now does nothing in seller mode (and, as everywhere else, prints the no-op warning at startup).
Migration
Saved datasets keep their existing rows. New Seller listings runs produce listing rows only — delete the previous attributes.__kind = "seller" row if you already captured one. Re-push the Actor to pick up the new output schema.
[2.0] - 2026-09-04
A ground-up redesign of the input form around one rule: every reference field takes a full page URL, pasted straight from the browser's address bar. No bare IDs, no bare slugs, no mode-specific single-URL fields. The Console form now groups secondary inputs into collapsible sections (Filters / Limits & performance / Output & debugging) instead of one long list.
Removed
Same seller's listings mode (startMode: "sameAuthor") and its Listing ID or URL input. To scrape everything a seller has published, use Seller listings mode and paste the seller's page URL — reachable in one click from any listing row via sellerProfileUrl.
The separate single-listing Listing ID or URL input (similarListingId). Similar products mode now reads the shared Listing URLs input instead.
Added
Listing URLs is now the single input for every mode that starts from a listing page.Specific listing URLs scrapes each pasted URL with full detail data; Similar products fetches the products similar to each pasted URL. Both accept one or more URLs, and both fail with an error naming every invalid entry instead of silently skipping them.
Similar products provenance. With several source listings in one run, each similar row records its source in attributes.similarToId, so results stay easy to group.
Collapsible input sections.Filters, Limits & performance, and Output & debugging group the secondary inputs so the form opens with just the mode picker and its related fields.
Changed
Renamed inputs.Category (categorySlug) → Category page URL (categoryUrl); Seller (sellerId) → Seller page URL (sellerUrl). Both now accept full https://www.ouedkniss.com/… URLs only — category URLs anywhere on the site work (a leading /en/ or /ar/ locale prefix is fine), and seller URLs must be /store/{id}/… or /membre/{id} pages.
URL-only enforcement. Bare values are rejected with a validation error instead of being auto-detected: no bare numeric seller IDs (store and member ID spaces overlap, so only the URL form is unambiguous), no bare category slugs, no bare listing IDs in Listing URLs. Paste the URL from the address bar; that always works.
Empty store grids no longer fall back to a member-profile lookup. A /store/… URL that matches zero listings now returns an empty dataset. The previous fallback probed the numeric ID as a member profile, which could silently scrape an unrelated seller when the store URL was dead.
Seller-info row timing for store runs. The store's seller-info row is emitted when the store grid returns at least one listing (unchanged in practice — a store with listings always produces it).
Migration
Saved tasks that pass sameAuthorListingId, similarListingId, sellerId, or categorySlug will see a schema-validation warning. Re-pick the mode and paste full URLs: seller page URLs into Seller page URL, listing page URLs into Listing URLs, category page URLs into Category page URL. Old bare-ID values cannot be converted automatically — open the corresponding page on ouedkniss.com and copy the URL from the address bar.
[1.7] - 2026-09-02
Added
Similar products mode (startMode: "similar"). Fetches products similar to a given listing, as the site's own similar-products feature defines them. Paste a numeric listing ID (e.g. 57354225) or a full detail URL into the new Listing ID or URL input — the Actor extracts the ID automatically. Results are regular listing rows tagged with attributes.slot = "similar" so you can tell them apart from other modes. Up to 30 results per pasted listing (no pagination).
Same seller's listings mode (startMode: "sameAuthor"). Fetches all listings from a specific seller — paste a listing ID or detail URL into the new Listing ID or URL input and the Actor resolves the seller internally (fetches the listing's detail to extract the user ID, then queries all their listings). This mirrors the website UX where "same seller" listings appear on a product's detail page. Results are paginated (48/page) and tagged with attributes.slot = "sameAuthor". This is the same data as Seller listings mode but accessed through a listing reference rather than a store/profile URL — useful when you found a listing via search and want to browse the seller's other items.
[1.6] - 2026-08-30
Added
Seller listings mode (startMode: "seller"). Scrapes every listing a specific seller has published — the same mode covers business stores (/store/{id}/{slug}/ pages) and personal accounts (/membre/{id} pages), because from a consumer's point of view both are just "a seller". Paste the seller page URL straight from the address bar into the new Seller input and the Actor detects which kind of seller it is from the path; a bare numeric ID (e.g. 290) also works and is tried as a store first, then as a member profile (store and user ID spaces overlap on this site, so the URL form is the unambiguous one). Store pages list 48 listings per page and share the search filters; member pages list 24 per page. Pagination, maxItems, and maxPages work exactly as in search/category mode. Store-grid prices already come in DZD, so priceAmount is always the real price.
sellerMemberSince output field. When the seller account was created, ISO-8601 (assumed UTC — the site returns an unzoned YYYY-MM-DD HH:MM:SS). Populated on seller-mode listing rows for member accounts and on the member seller-info row; null everywhere else. This closes the gap noted when the field was dropped in 0.5 — the value simply wasn't exposed by any operation the Actor used back then; the profile query returns it.
Seller-info row. Every seller run now emits one extra row (identified by attributes.__kind === "seller", same partial-row convention as category-tree rows) describing the seller itself: display name, username/ID, profile URL, and for member accounts the follower and announcement counts. A new Seller profile dataset view shows these rows. When the seller is a store and includeContactInfo: true, the row also carries the store's public contact block — phones (with WhatsApp/Viber/Telegram flags), emails, social links, and address — exactly what the storefront page shows to every visitor. Unlike individual sellers' contact info, this is not gated behind the site's click-to-reveal endpoints; the toggle is honored as the user's "no contact data" preference.
Pagination tail guard for store grids. The site's own page counter sometimes claims more pages than exist (one store said 39 pages where listings ended at page 20). An empty page now ends the walk instead of chasing phantom pages.
Changed
includeContactInfo is now effective in seller mode in addition to directUrls (see the seller-info row above — store contact block only; member profiles expose no contact info on the web). The startup warning no longer fires for seller runs; its text now names the two modes where the toggle does something.
Input schema descriptions for maxItems, maxPages, categorySlug, and the contact-related dataset fields now name the new mode where relevant.
Notes
No dataset-shape migration. Existing rows are unchanged except the new sellerMemberSince field (null everywhere it was previously absent). Seller-mode listing rows use the same organic-row shape as search/category, plus the optional seller-info row. Consumers filtering datasets by populated fields are unaffected.
Member profiles expose no contact info anywhere on the web — the site directs buyers to its mobile app instead. The phone/email arrays for member seller rows and member listing rows are therefore always empty; only stores have a public contact block.
[1.5] - 2026-08-29
Added
Output schema (output_schema.json). The Actor now declares an explicit output schema so the Store/Console Output tab shows the dataset under a "Listings" heading with a description of the per-mode row shape and the three view choices, instead of the default "Output schema is not defined" placeholder. Required for Apify Store publication. The Actor writes only to the default dataset — no key-value store or web-server surfaces — so those schemas are intentionally not defined.
[1.4] - 2026-08-29
Removed
filterConnected input field. The site-side filter this mapped to accepts a Boolean but produces no change in results — runs return identical results whether it is set or not. The input was misleading: it accepted a value and did nothing. It has been removed. Saved tasks that pass filterConnected will see a schema-validation warning; remove the field from the task input.
Changed
filterOrigin is now a 3-option select. The site's seller-type filter accepts only individual sellers or stores, and rejects any other value. The previous free-text field accepted anything and failed at run time for invalid values. The form now offers Any (default), Individual sellers only, or Stores only.
defaultMemoryMbytes dropped from 256 to 128. Observed peak across recent runs is 110 MB. 128 MB leaves ~16% headroom over the observed peak while halving the per-run compute-unit cost (the workload is single-threaded HTTP I/O, so the smaller CPU share is enough). The min: 128 floor and max: 2048 ceiling are unchanged for users who run huge directUrls lists or want extra headroom.
Notes
Both changes are breaking for any saved task or API caller that passes the old field name. A saved task with filterConnected: true fails input validation with an "Unknown property" warning; a caller that passed a free-text filterOrigin value other than INDIVIDUAL / STORE still hits the same VALIDATION_INVALID_TYPE_VARIABLE they always did, but the new UI prevents it. The Actor is not yet published to the public Apify Store (still dev-only), so this is a clean cut.
[1.3] - 2026-08-29
Added
Category field accepts full category-page URLs. Users used to copy-pasting from the address bar can now paste https://www.ouedkniss.com/automobiles_vehicules/1 straight into the Category field; the Actor extracts the category from the first path segment. Bare slugs (automobiles_vehicules) still work unchanged. URLs that point to non-category surfaces (store pages, member profiles, search results, the homepage, auth pages, etc.) are rejected up front with a message saying what went wrong, instead of silently returning an empty banner. Verified against https://www.ouedkniss.com/automobiles_vehicules/1, https://www.ouedkniss.com/immobilier, the bare-domain form www.ouedkniss.com/voitures/2, query strings, fragments, and nested URLs like /automobiles_vehicules/voitures.
Two new dataset views in dataset_schema.json:
Category menu — for rows from Category menu (full tree) mode (the What to scrape value "categories"). Shows the category name, slug, parent breadcrumb, depth, rank, icon, and delivery flag. Previously, categories runs left the dataset's Overview view showing only id and scrapedAt for every row because all the other columns were null — users had to switch to the All fields view to see anything meaningful.
Listing details — for rows from Specific listing URLs mode (the What to scrape value "directUrls"). Adds description, seller info, gallery count, video URL, contact info, and the specs map. The existing Listings view (formerly called Overview) stays the default card projection for organic and sponsored-listing rows.
Changed
Renamed every user-facing label to match the actual OuedKniss site vocabulary. The previous labels were either generic ("Keyword search"), developer jargon ("TopAnnounList banner (paid placements)"), or used internals (the enum keys themselves) that don't map to anything a user browsing ouedkniss.com would recognize. The new labels are what the site itself shows:
startMode enum titles: Keyword search → Search by keyword · Category browse → Browse a category · Direct listing URLs → Specific listing URLs · Category tree (recursive) → Category menu (full tree) · TopAnnounList banner (paid placements) → Sponsored listings (banner). "Sponsorisées" / "Publicité" is the actual term OuedKniss uses for the paid banner, so the new label keeps that connection.
startMode title (was Start mode): What do you want to scrape?
searchQuery title: Search keyword. categorySlug title: Category (the word "slug" is meaningless to a non-developer). startUrls title: Listing URLs. locale title: Language, with enumTitles for the three supported values.
maxItems title: Maximum results. maxPages title: Maximum pages. maxDepth title: How deep to walk the menu. maxRequestsPerMinute title: Requests per minute.
Filter titles: Filter: region (wilaya) IDs → Filter by wilaya (region) · Filter: city (commune) IDs → Filter by commune (city) · Filter: min price (DZD) → Minimum price (DZD) · Filter: max price (DZD) → Maximum price (DZD) · Filter: must have pictures → Only listings with photos · Filter: must have a price set → Only listings with a price · Filter: exchange accepted → Only listings that accept trade-in · Filter: delivery available → Only listings with delivery · Filter: connected (OuedKniss-specific flag) → Only 'Connected' listings (OuedKniss feature) · Filter: order by → Sort by with enumTitles for the two orderings · Filter: page size → Items per page.
Overview dataset view renamed to Listings and its description rewritten to point users at the Category menu view for category-tree runs (which now have a meaningful projection) and at the Listing details view for direct-URL runs that want the full enrichment.
actor.json description mentions "sponsored-listing banner" instead of "paid cross-listing banner" so the storefront copy aligns with the new enum title.
README prose uses the new labels throughout (mode table, input tables, field-by-field notes, Notes-and-limits section, FAQ). The Tutorial has a sentence about switching to the Category menu dataset view after a Category menu run, and a sentence about switching to the Listing details dataset view after a Specific listing URLs run with contact info on.
How deep to walk the menu default dropped from 5 to 2. Recursive category-tree walks grow quickly — at depth 5 over OuedKniss's full menu the Actor pushes hundreds of nodes and many API calls, while most users only need the top 2-3 levels to discover a category slug. New default: 2. Raise it explicitly if you need to walk deeper; the field's maximum: 10 is unchanged. All fallback behavior updated to match.
Notes
No dataset-shape migration path. All five modes produce the same Listing row shape they did before — this is purely a UX change (labels + dataset view configuration). Consumers reading the JSON output are unaffected. The dataset view config changes from "Overview" only to "Listings / Category menu / Listing details"; users who script against a specific view name would need to update, but the previous Overview view's transformation.fields and display.properties were preserved under the new Listings name so the columns are byte-identical.
URL acceptance is opt-in via copy-paste. The Category field's UI is still a plain text input, not a URL picker — the helper just makes pasting a URL into it work. Users who want to keep using bare slugs don't need to change anything.
[1.2] - 2026-08-28
Fixed
includeContactInfo was silently ignored outside directUrls mode. The toggle only takes effect in Specific listing URLs (directUrls) runs — runs that actually open listing detail pages. In search, category, categories, and topAnnounList modes the phone and email arrays were always empty regardless of the toggle — but the old docs only flagged topAnnounList, so users running search/category had no way to know the toggle did nothing. The input field's description, the two seller-contact output fields, the marketing bullet, the input table, the field-by-field notes, and the Notes-and-limits section now all say explicitly: only effective in directUrls mode. The Actor also prints a startup warning line when the toggle is enabled outside directUrls, so a user who copy-pastes a config from a previous directUrls run into a search/category run sees the mismatch immediately instead of debugging empty phone arrays after the run completes.
[1.1] - 2026-08-28
Removed
slot field. Every row from a single run has the same slot value (one run = one mode), so the field was constant noise. The discriminator moved out of the data — there's only one Listing shape now; which fields are populated tells you which mode produced the row (organic rows have priceAmount/categoryPath/sellerId, banner rows have storeId/likeCount, category-tree rows have categoryName/categorySlug/parentTreeNames).
status field. Every row the site returns maps to "active" in practice. The field was internal-only and never surfaced in the dataset.
categories view in the dataset schema. Filterable rows for category-tree data are still in the dataset (under the same shape as organic/banner rows), but the dedicated view was redundant with the Overview — Overview is now a complete view of every row, and a filter on categorySlug !== null (in the All fields view) is what splits out tree rows.
Internal-only code values tied to the removed slot/status markers; nothing outside the Actor used them.
Changed
categoryLeafId renamed to categoryId. The previous name's "Leaf" qualifier was confusing — the field holds the listing's category ID, period. categoryId is what consumers see on organic rows; the same value appears as id on rows produced by startMode: "categories", so the rename makes cross-dataset joins obvious.
Breadcrumb arrays → breadcrumb strings.categoryPath (organic row's full path root-to-leaf) and parentTreeIds / parentTreeNames / parentTreeSlugs (category-tree row's ancestors) are now single strings with the parts joined by › (U+203A) — the most common breadcrumb symbol across the web. Empty strings represent "no path" (topAnnounList rows for categoryPath; root-level nodes and non-category rows for parentTree*). The previous parallel-array form was a workaround for the dataset table view's poor array rendering; the flat-string form is cleaner in JSON and renders the breadcrumb inline in the Overview column.
New primaryImageUrl scalar. First image URL, surfaced as its own scalar so the Apify dataset table renders it as an image thumbnail. The images array still carries the full gallery for programmatic use — but arrays render as "N items" badges in the table view, so the thumbnail needs a scalar. The category-tree categoryIcon field has worked this way since launch; primaryImageUrl brings organic/banner rows to parity.
Notes
No dataset-shape migration path. Consumers reading the JSON output will see slot and status gone, categoryLeafId renamed, breadcrumb arrays replaced by strings, and a new primaryImageUrl field appear. The Actor is not yet published to the public Apify Store (27 dev runs so far), so a clean cut is fine. If you forked this schema for an external pipeline, search-and-replace slot: "organic" filters to categoryPath !== "" (or similar), categoryLeafId → categoryId, and unwrap the breadcrumb arrays (parts = categoryPath.split(" › ")).
[1.0] - 2026-08-28
Removed
rootCategorySlug input field. Merged into categorySlug — see below. Any saved task or API caller passing rootCategorySlug must update the input key to categorySlug. The semantic ("start the category walk at this sub-tree") is preserved; only the field name changed.
Changed
categorySlug is now a single field that covers three modes. In category mode it's the category to browse; in topAnnounList it scopes the banner; in categories it scopes the tree walk. The previous schema exposed categorySlugandrootCategorySlug as separate fields, which made users guess which one to fill for a given mode. They are now one field whose meaning depends on startMode. Field description in the input schema spells out the three modes explicitly. Ignored in search and directUrls.
Pagination framing generalized across search and category. Both tasks paginate identically (48 listings per page, same page counter). The previous wording emphasized pagination as a category-only feature ("Category browse (with pagination)", "browse a category with pagination" in the description). The category-specific qualifier has been dropped everywhere; both rows in the README's mode table now mention pagination explicitly, and the form's category-picker label is now just "Category browse".
[0.9] - 2026-08-28
Changed
Memory defaults dropped from 1 GB to 256 MB. Observed peak across every startMode is ~110 MB and CPU stays under 20%, so the platform's 1024 MB default was paying for four times more capacity than the workload needs. The Actor now declares defaultMemoryMbytes: 256 with min: 128 and max: 2048. Apify rounds the value to the nearest power of 2 either way; this just makes the default match the workload. The CPU share scales linearly (256 MB ≈ 1/16 core), but the workload is single-threaded HTTP I/O so the smaller core is enough. You can still raise memory per run if you push a huge directUrls list.
Description no longer claims "no proxy". The earlier description said "no headless browser, no proxy"; the proxy requirement (OuedKniss's edge blocks the default datacenter egress) was already called out in the README and the user-visible note, but the description was stale. It now matches the README — five modes, proxy-agnostic phrasing — and no longer mentions "headless browser".
Notes
Run timeout is not part of actor.json. Apify's actor.json schema only carries memory defaults; the run timeout lives on the Actor's Console settings and on each run invocation. The platform default for the js-crawlee-cheerio template is 300 s, which is fine for the default caps (maxItems: 200, maxPages: 20) but too tight for large runs (each page is rate-limited to maxRequestsPerMinute requests/min, so maxItems: 1000 ≈ 5 min of network alone, plus startup and proxy probing). For long runs, set a longer timeout when you start the run (UI "Timeout" field, or timeoutSecs in the API call). A good rule of thumb is (maxPages / maxRequestsPerMinute) × 60 + 60 seconds, with a floor of 300 s.
[0.8] - 2026-08-28
Fixed
categories mode still failed every push with Schema validation failed after the 0.7 row-shape fix. Category rows carry a true/false delivery value (categoryDelivery) that the output schema only accepted as text, so rows with false failed validation. categoryDelivery now accepts true/false.
[0.7] - 2026-08-28
Fixed
categories mode failed every push with Schema validation failed. The dataset schema's required list (url, title, scrapedAt) didn't match what the category-tree walker emitted — those rows are tree nodes, not listings, so they don't have a url or a title. Every request retried 4× before failing, so a single categories run produced zero output on Apify.
Added
slot: "category" discriminator. Category-tree nodes from startMode: "categories" now use the same row shape as organic/topAnnounList listings, with slot set to "category". Filter the dataset with slot === "category" to keep only the tree.
Category-mode fields on every row (null/empty outside category mode):categoryName, categorySlug, categoryIcon, categoryDelivery, categoryRank, categoryDepth, and three parallel parentTreeIds/parentTreeNames/parentTreeSlugs arrays for the breadcrumb from root to (excluding) the current node.
A Categories dataset view. Table projection tuned for the tree walk: name, slug, rank, depth, parent path.
Changed
Required fields on the dataset.required is now ["slot", "id", "scrapedAt"] (was ["slot", "id", "url", "title", "scrapedAt"]). Every row has these; everything else is per-slot.
[0.6] - 2026-08-28
Removed
sponsored field from every output row. The field was true for ~100% of rows because the site returns the same value for organic and paid listings. The field added no information; it was just misleading noise.
includeTopAnnounList input. The sponsored/organic split is gone — see "Added" below.
maxPromotedItems input. No longer needed — see "Added" below.
maxOrganicItems input. Replaced by maxItems. Almost everything this Actor pushes is organic, so the per-mode cap is now maxItems. Default raised from 0 to 200 for visibility.
Added
topAnnounList is now its own startMode. The cross-listing paid banner used to be an opt-in add-on (includeTopAnnounList: true) bolted onto search / category runs. Banner entries have a much sparser response than organic listings (only title, price, store metadata, and one image), so they're now a dedicated mode. Run with { "startMode": "topAnnounList" }. Pass categorySlug to scope the banner to a category.
slot discriminator on every output row. Either "organic" (search / category / directUrls / categories results) or "topAnnounList" (banner entries). Filter the dataset into its streams.
storeId, storeName, storeImageUrl, storeProfileUrl, likeCount fields. Populated only for topAnnounList rows; null for organic ones.
Changed
maxItems is now the standard cap.maxOrganicItems (formerly the per-mode cap) is gone.
startMode enum now includes "topAnnounList".
[0.5] - 2026-08-28
Added
proxyMaxAttempts input field. How many proxy IPs to try at the start of a run when the site blocks the actor's exit IP. The first one that returns data is reused for the rest of the run. Default 5. No effect on local runs.
Run log shows proxy outcome.Network: proxy=on is the success signal; the proxy=off variants cover the known failure modes.
Removed
sourceSite field from every output row. Was always the constant "ouedkniss".
price.currency field from every output row. Was always "DZD".
Changed
Flat output schema. Every top-level field is now a scalar or a list of scalars (the only intentional exception is attributes, which holds free-form category-specific specs). The seller.phones array is exposed as five parallel scalar arrays (sellerPhoneNumbers, sellerPhoneIntls, sellerPhoneWhatsapps, sellerPhoneVibers, sellerPhoneTelegrams) — index N pairs across them.
Flat filter input. The previous filters: { regionIds, priceRange, ... } object is replaced with top-level scalar fields prefixed filter* (filterRegionIds, filterPriceMin, filterHasPictures, …).
startUrls is now string[] instead of Array<{ url, method }>. Each item is just a detail-page URL.
Dropped redundant output fields.price.raw (derivable from priceAmount), location.raw (derivable from wilaya/commune/street), media.imageCount (derivable from images.length), media.videoThumbnail (only useful with video), seller.memberSince (always null), seller.messengerLink (derivable from phones + WhatsApp), category.leafLabel (last item of categoryPath).
[0.4] - 2026-08-27
Fixed
Every request to OuedKniss from Apify returned 200 with a 0-byte body. The site blocks the default datacenter egress IP range. All API calls now route through Apify Proxy.
Changed
Local runs skip proxy creation entirely. Previously the Actor tried to create a proxy configuration even when running locally; now it only does so on real Apify containers.
[0.3] - 2026-08-27
Fixed
Search returned zero results when filter booleans defaulted to false. The site rejects the exchange and delivery filters unless they are left unset — sending false broke every run. The Actor now treats false as "leave unset", so the form's defaults no longer break runs.
Changed
Version format dropped the patch segment. Apify's UI displays the first two segments only (M.m), so 0.2.0 was being shown as a malformed version. Bumped to the two-segment form 0.3.
[0.2.0] - 2026-08-26
Changed
Migrated to a shared codebase. The Actor now uses the repo's shared scraping engine. Behavior is unchanged.
Two independent output streams.maxItems and maxPromotedItems are independent caps for the organic and promoted streams. maxItems still works as a global fallback when both are unset.
Added
Per-actor README (actors/ouedkniss/.actor/README.md) with Tutorial, FAQ, Pricing sections — rendered on the Actor's Apify Console and Store pages.
[0.1.0] - 2026-08-25
Added
Initial release. Four run modes: search, category, directUrls, categories.
Optional contact enrichment (includeContactInfo) — no proxy required.