# Tijelabin Multi-Scraper (`hamza325/tijelabin-multi-scraper`) Actor

Scrape Tijelabin.com, an Algerian classifieds marketplace, with lightweight HTTP requests against the site's own web pages.

- **URL**: https://apify.com/hamza325/tijelabin-multi-scraper.md
- **Developed by:** [Hamza Abbad](https://apify.com/hamza325) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Tijelabin Multi-Scraper

Scrape [tijelabin.com](https://tijelabin.com) — an Algerian classifieds marketplace — with lightweight HTTP requests against the site's own web pages. No login and no browser automation required, and the site currently applies no bot protection, so runs are fast and cheap. Every record is a flat, normalized object ready to feed straight into a spreadsheet, a database, or another Actor downstream.

### What you can scrape

The Actor drives six modes from a single **What to scrape** (`startMode`) input. Pick the one that matches what you want.

| Key (`startMode` value) — Console label | What it returns | Best for |
| --- | --- | --- |
| `search` — **Search by keyword** | Up to N listings matching a free-text query, paginated — the site's search covers titles **and** descriptions | Market research, price tracking, lead lists |
| `category` — **Browse a category** | All listings in one category, paginated. The taxonomy is flat: 39 categories, no subcategories | Browsing a niche (phones, cars, clothing…) |
| `directUrls` — **Specific listing URLs** | A specific list of listing pages, fully enriched | Resuming a previous run, following up on alerts, checking one item's real status |
| `location` — **Browse a location (wilaya or commune)** | All listings in one wilaya (`/state/…` pages) or one commune (`/city/…` pages). The pasted URL decides which kind of grid is scraped. The site covers the 48 pre-2019 wilayas | Local inventory checks ("what's for sale in Algiers?") |
| `store` — **Seller store listings** | Every listing a seller has published, one row per listing, paginated | Tracking a shop's inventory, monitoring a seller, building seller histories |
| `similar` — **Similar products** | The products listed in the "Produits similaires" section of each pasted listing's detail page | Discovering alternatives, competitive research |

> All modes share the same flat row shape; each mode populates only the fields it has data for (the others are `null` or empty). Grid modes (`search` / `category` / `location` / `store`) fill the card fields; `directUrls` fills the full projection; **Similar products** (`similar`) fills the card fields plus a `similarToId` marker. Every row is one listing. Sold listings are skipped by default unless you turn the toggle on. Use the key names (`search`, `category`, `directUrls`, `location`, `store`, `similar`) in JSON/API calls — the **What to scrape** (`startMode`) picker shows the Console labels.

### Why this Actor

- **Fast and lightweight.** Plain HTTP requests against the site's own web pages — no browser, no login, no session. Fast on cold start, gentle on memory, cheap to run at scale.
- **Flat, uniform output.** Every row is a flat object with no nested structures — the dataset table shows clean values instead of "N fields" badges.
- **Prices exactly as the seller typed them.** The site labels every price with a literal `$` (its own label, and wrong — the currency is the Algerian Dinar). The Actor strips the `$`, keeps the number as typed in `price`, preserves the raw display string in `priceRaw`, and hardcodes `currency: "DZD"`.
- **Sold listings on request.** Items marked VENDU stay visible on the site; a single toggle includes them in results, tagged with their real status.
- **You only ever paste URLs.** Every reference field takes a full page URL, straight from the browser's address bar. No IDs, no slugs, no values copied out of the address bar — and the Actor tells you exactly what it expected if a URL points at the wrong kind of page.

### Quick start

1. Open this Actor in Apify Console.

2. Click **Try actor** and set **What to scrape** (`startMode`) to **Search by keyword** (`search`), plus a **Search keyword** (`searchQuery`) — the minimum valid JSON is:

   ```json
   { "startMode": "search", "searchQuery": "iphone" }
   ```

3. Click **Start**. Results land in the **Dataset** tab, one JSON object per listing.

The smallest valid input runs a keyword search across the whole site, with sensible defaults for everything else.

### Tutorial: a real workflow

#### 1. Browse a category

Pick the category you care about and paste its page URL straight from the address bar. The taxonomy is flat (39 categories, ids 1–39, no subcategories), so a URL like this is all you need:

```json
{
  "startMode": "category",
  "categoryUrl": "https://tijelabin.com/category/15/cars",
  "maxItems": 500,
  "maxPages": 20
}
```

This walks up to 20 pages of the cars category.

#### 2. Browse a location

Tijelabin grids work per wilaya (`/state/…`) and per commune (`/city/…`). Paste either kind of URL into the same field — the path decides which grid is scraped:

```json
{
  "startMode": "location",
  "locationUrl": "https://tijelabin.com/state/alger"
}
```

Or for a single commune:

```json
{
  "startMode": "location",
  "locationUrl": "https://tijelabin.com/city/alger-centre"
}
```

#### 3. Enrich a hand-picked set

Found a few listings you care about? Queue their URLs and get the full projection for each — description, category, location, seller block, image gallery, view counter, and posted date:

```json
{
  "startMode": "directUrls",
  "startUrls": [
    "https://tijelabin.com/products/8930/tecno-pova-6",
    "https://tijelabin.com/products/9001"
  ]
}
```

Pasted URLs always return with their **real status** — if a listing was sold, the row says so, regardless of the sold-listings toggle.

#### 4. Track a seller's store

Every seller on Tijelabin — individual or business — has a store page. Paste it (or copy it from any listing row's `sellerProfileUrl`) and get the full inventory:

```json
{
  "startMode": "store",
  "sellerUrl": "https://tijelabin.com/store/a693b29e82c2ac"
}
```

You get the seller's full listing grid — every row is one of their listings, in the same flat shape as every other mode.

#### 5. Find similar products

Every detail page on the site carries a "Produits similaires" section. Paste one or more listing URLs to pull those products:

```json
{
  "startMode": "similar",
  "startUrls": [
    "https://tijelabin.com/products/8930/tecno-pova-6",
    "https://tijelabin.com/products/9001"
  ]
}
```

Each returned row carries `similarToId` — the numeric ID of the source listing it came from — so results from several URLs stay easy to group.

### Input

Only **What to scrape** (`startMode`) is required. All other fields have defaults. Every reference field takes a full page URL, pasted straight from the browser's address bar — the Actor rejects bare IDs and slugs with a message telling you exactly what to paste instead.

The Console form groups the secondary inputs into two collapsible sections — **Limits & performance** and **Output & debugging** — under the mode picker and its related fields. JSON/API callers use the `key` names; the Console form shows the **Label** names.

#### What to scrape

| Label (`key`) | Description | Default |
| --- | --- | --- |
| **What to scrape** (`startMode`) | **Search by keyword** (`search`) / **Browse a category** (`category`) / **Specific listing URLs** (`directUrls`) / **Browse a location (wilaya or commune)** (`location`) / **Seller store listings** (`store`) / **Similar products** (`similar`) | (required) |
| **Search keyword** (`searchQuery`) | Search term, when **What to scrape** (`startMode`) = **Search by keyword** (`search`) | — |
| **Category page URL** (`categoryUrl`) | The category to browse, when **What to scrape** (`startMode`) = **Browse a category** (`category`). Paste the category page URL from the address bar (e.g. `https://tijelabin.com/category/15/cars`) | — |
| **Location page URL** (`locationUrl`) | The wilaya or commune to browse, when **What to scrape** (`startMode`) = **Browse a location** (`location`). Paste a `/state/…` or `/city/…` page URL from the address bar | — |
| **Store page URL** (`sellerUrl`) | The seller to scrape, when **What to scrape** (`startMode`) = **Seller store listings** (`store`). Paste the store page URL from the address bar (e.g. `https://tijelabin.com/store/a693b29e82c2ac`) | — |
| **Listing URLs** (`startUrls`) | One or more listing page URLs (e.g. `https://tijelabin.com/products/8930/tecno-pova-6`), used by both modes that start from a listing page: **Specific listing URLs** (`directUrls`, full detail data per URL) and **Similar products** (`similar`, similar items per URL) | — |
| **Language** (`locale`) | Request language. Only affects the site's interface labels — listing titles, descriptions and prices are seller-entered and never translated | `fr` |

#### Limits & performance

| Label (`key`) | Description | Default |
| --- | --- | --- |
| **Maximum results** (`maxItems`) | Max listings to push. Applies in every mode. `0` = no cap | `200` |
| **Maximum pages** (`maxPages`) | Max page requests. Up to 48 listings per page on grid modes (store grids run smaller). `0` = no cap. The Actor stops on its own when a grid runs out — the site's page counter over-counts on search pages because it includes sold items | `20` |
| **Requests per minute** (`maxRequestsPerMinute`) | Rate limit per worker | `40` |

#### Output & debugging

| Label (`key`) | Description | Default |
| --- | --- | --- |
| **Include sold listings** (`includeSold`) | Also emit sold items, tagged `status: "sold"`. Off (default), they are skipped. No effect in **Specific listing URLs** (`directUrls`) — pasted URLs always return with their real status | `false` |
| **Skip duplicate listings** (`dedupe`) | Skip listings already pushed earlier in this run. With **Include sold listings** (`includeSold`) on, an item can appear in both the active grid and the sold section — the active copy wins | `true` |
| **Debug: include raw extracted data** (`debugIncludeRaw`) | Add a `raw` field with the unprocessed data extracted from the page (the raw HTML is never included) | `false` |

### Output

One JSON object per listing, in the **Dataset** tab. Every field is a scalar or a list of scalars — no nested objects — so the dataset table view shows clean values instead of "N fields" badges.

The dataset has a single view:

- **Listings** — the default. Grid projection for `search` / `category` / `location` / `store` runs, full projection for **Specific listing URLs** (`directUrls`) runs, card projection with `similarToId` for **Similar products** (`similar`) runs.

#### Listing row

**Specific listing URLs** (`directUrls`) runs produce the fullest row (grid modes fill only the first block; `null` where a surface has no data):

```json
{
  "id": "8930",
  "url": "https://tijelabin.com/products/8930/tecno-pova-6",
  "title": "tecno pova 6",
  "description": "Le TECNO POVA 6 est livré avec un grand écran AMOLED de 6,78 pouces…",
  "price": 40400,
  "priceRaw": "40400",
  "currency": "DZD",
  "categoryId": "6",
  "categoryLabel": "Accessoires homme",
  "stateSlug": "alger",
  "stateName": "Alger",
  "citySlug": "alger-centre",
  "cityName": "Alger Centre",
  "images": ["https://tijelabin.com/storage/products/hdQuality/2026/08/hash.jpg"],
  "primaryImageUrl": "https://tijelabin.com/storage/products/hdQuality/2026/08/hash.jpg",
  "imageCount": 6,
  "sellerId": "a68e2cdfb2bea2",
  "sellerName": "kacem",
  "sellerMemberSince": "2025-10-01",
  "sellerProfileUrl": "https://tijelabin.com/store/a68e2cdfb2bea2",
  "postedAt": "2026-08-29",
  "views": 27,
  "status": "active",
  "similarToId": null,
  "scrapedAt": "2026-08-31T12:00:00.000Z"
}
```

#### Field-by-field

- **`price`** — numeric price in Algerian Dinar, exactly as the seller typed it with the site's `$` label stripped. `0` marks placeholder prices like `$00`.
- **`priceRaw`** — the displayed price string with the `$` stripped — the audit trail that preserves leading-zero junk (`"00"`) and separators the seller typed.
- **`currency`** — constant `"DZD"`. The site renders prices with a `$` label and emits USD in its structured data; both are wrong, and the value is emitted as typed.
- **`categoryId` / `categoryLabel`** — the listing's category, from the detail page's breadcrumb (ids 1–39). Grid cards don't carry category data.
- **`stateSlug` / `stateName` / `citySlug` / `cityName`** — the listing's wilaya and commune. Site spellings are passed through as-is (not normalized); grid cards carry the commune name, the full set needs a detail page.
- **`images` / `imageCount`** — grid cards carry the single thumbnail; detail pages carry the full gallery at HD quality (with low-quality fallbacks).
- **`primaryImageUrl`** — the first image URL (`images[0]`), surfaced as its own scalar so the dataset table renders an image thumbnail. The `images` array still carries the full list for programmatic use — arrays render as "N items" badges in the table view.
- **`sellerId` / `sellerName` / `sellerMemberSince` / `sellerProfileUrl`** — the seller's store ID, display name, membership date (month precision — the site renders only "Rejoint Oct 2025" → `2025-10-01`) and store page.
- **`postedAt`** — the date the listing was posted, day precision (`YYYY-MM-DD` — the site renders no time component).
- **`views`** — the detail-page view counter. A live counter on the site, so treat it as a point-in-time value, not a stable metric.
- **`status`** — `active` for regular listings, `sold` for items marked VENDU (only when **Include sold listings** (`includeSold`) is on, or when a pasted URL in **Specific listing URLs** (`directUrls`) resolves to a sold item).
- **`similarToId`** — **Similar products** (`similar`) rows only: the numeric ID of the source listing the "Produits similaires" section came from. `null` on every other row.
- **`scrapedAt`** — ISO-8601 timestamp of when the row was captured.

### Notes and limits

- **No seller contact info exists on the web site.** Tijelabin directs all buyer contact to its mobile app; no phone number or WhatsApp link is exposed on any page, logged in or not. The Actor's output carries no contact fields.
- **Dates are day- or month-precision.** The site renders `DD/MM/YYYY` with no time, so `postedAt` is `YYYY-MM-DD`; `sellerMemberSince` is month precision (`YYYY-MM-01`).
- **View counters are live.** `views` reflects the moment of scraping, not a stable metric.
- **Pagination stops on an empty grid.** The site's page counter over-counts on search pages (it includes sold items); the Actor walks until the grid is actually exhausted. **Maximum results** (`maxItems`) and **Maximum pages** (`maxPages`) cap every run.
- **Dead listings** (removed entirely) return HTTP 410 and are skipped with a log line.
- **Titles, descriptions and prices are seller-entered** and never translated — the language option only affects site chrome.

### FAQ

**Can I get a seller's phone number?**
No — the platform gates all contact behind its mobile app. There is nothing to scrape on the web.

**Why is `priceRaw` sometimes `"00"`?**
Sellers type placeholder prices; the site tolerates them. `priceRaw` is the audit trail, `price` the parsed number.

**Can I scrape the whole site?**
Yes, practically: category pages 1–39 with a high **Maximum pages** (`maxPages`) cover the active inventory, and the run stays idempotent thanks to ID dedupe.

**How fast should I run it?**
The site applies no bot protection, but keep `Requests per minute` at its default (40) or lower to stay a good citizen.

**How do I find the URLs to paste?**
Open the category, location, store or listing page on tijelabin.com and copy the address from the browser's address bar — e.g. `https://tijelabin.com/category/15/cars`, `https://tijelabin.com/state/alger`, `https://tijelabin.com/store/a693b29e82c2ac`, `https://tijelabin.com/products/8930/tecno-pova-6`. There is no need to read IDs or slugs out of the URL — paste the whole thing.

**My run hit `maxItems` early. Why?**
Either the dataset already contains those listing IDs (dedupe is on by default) or the site returned fewer unique results than expected. Try raising **Maximum results** (`maxItems`) and **Maximum pages** (`maxPages`), or turning **Skip duplicate listings** (`dedupe`) off to see raw counts.

### Contact

Bug reports, feature requests, or a custom scraper for a marketplace not yet covered? Open an issue on the Actor's Apify Console page or message the maintainer directly there.

# Actor input Schema

## `startMode` (type: `string`):

How to drive the scraper. **Search by keyword** (`search`), **Browse a category** (`category`) and **Browse a location** (`location`) return listing grids matched by keyword, category or location; **Seller store listings** (`store`) returns everything a specific store has listed; **Similar products** (`similar`) fetches products similar to a given listing; **Specific listing URLs** (`directUrls`) enriches a hand-picked list of listing URLs with the full detail data.

## `searchQuery` (type: `string`):

Used when **What to scrape** (`startMode`) = **Search by keyword** (`search`). Free-text query, exactly as the site's own search box accepts it (matches titles and descriptions).

## `categoryUrl` (type: `string`):

Used when **What to scrape** (`startMode`) = **Browse a category** (`category`). The site's taxonomy is flat (39 categories, ids 1–39, no subcategories). Paste the category page URL straight from the browser's address bar (e.g. `https://tijelabin.com/category/15/cars`).

## `locationUrl` (type: `string`):

Used when **What to scrape** (`startMode`) = **Browse a location** (`location`). Paste the location page URL straight from the browser's address bar — a wilaya page (`https://tijelabin.com/state/alger`) or a commune page (`https://tijelabin.com/city/alger-centre`). The URL decides which kind of grid is scraped. The site covers the 48 pre-2019 wilayas.

## `sellerUrl` (type: `string`):

Used when **What to scrape** (`startMode`) = **Seller store listings** (`store`). Paste the store page URL straight from the browser's address bar (e.g. `https://tijelabin.com/store/a693b29e82c2ac`). Every seller on Tijelabin — individual or business — has a store page. The run returns every listing the seller has published, one row per listing.

## `startUrls` (type: `array`):

The listing page URLs, pasted straight from the browser's address bar (e.g. `https://tijelabin.com/products/8930/tecno-pova-6`). The single input for the two modes that start from a listing page: **Specific listing URLs** (`directUrls`) scrapes each URL with the full data the site offers (description, category, wilaya/commune, seller block, image gallery, view counter, posted date), and **Similar products** (`similar`) fetches the products from each listing's "Produits similaires" section (each row records its source listing in `similarToId`).

## `maxItems` (type: `integer`):

Stop after pushing this many listings. Applies in every mode. 0 = no cap.

## `maxPages` (type: `integer`):

Stop after this many page requests. One page = one request (up to 48 listings per page on grids; seller store grids run smaller). Use 0 for no cap. The scraper stops on its own when a grid runs out — the site's page counter over-counts on search pages because it includes sold items.

## `maxRequestsPerMinute` (type: `integer`):

Rate limit per worker. The site currently applies no bot protection, but stay polite so the Actor stays viable long-term.

## `locale` (type: `string`):

Request language. Only affects the site's interface labels — listing titles, descriptions and prices are seller-entered and never translated. French (the site's default) is fine for almost every run.

## `includeSold` (type: `boolean`):

If true, sold items are also emitted, tagged with `status: "sold"` (search pages append a sold-listings section; sold items stay visible on the site with a VENDU badge). If false (default), sold items are skipped entirely. In **Specific listing URLs** (`directUrls`) this toggle has no effect — pasted URLs are always returned with their real status.

## `dedupe` (type: `boolean`):

Skip listings whose ID has already been pushed in this run. On search pages with **Include sold listings** (`includeSold`) enabled, an item can appear in both the active grid and the sold section — the active copy wins. Duplicates are dropped at the request-queue level too, so re-running the Actor with the same settings is idempotent.

## `debugIncludeRaw` (type: `boolean`):

If true, each pushed row includes a `raw` field with the unprocessed data extracted from the page (the raw HTML is never included).

## Actor input object example

```json
{
  "startMode": "search",
  "searchQuery": "iphone",
  "categoryUrl": "https://tijelabin.com/category/15/cars",
  "locationUrl": "https://tijelabin.com/state/alger",
  "sellerUrl": "https://tijelabin.com/store/a693b29e82c2ac",
  "startUrls": [],
  "maxItems": 200,
  "maxPages": 20,
  "maxRequestsPerMinute": 40,
  "locale": "fr",
  "includeSold": false,
  "dedupe": true,
  "debugIncludeRaw": false
}
```

# Actor output Schema

## `listings` (type: `string`):

Default dataset containing every listing the Actor pushed during the run — one flat row per listing, in the Listings view.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchQuery": "iphone",
    "startUrls": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("hamza325/tijelabin-multi-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searchQuery": "iphone",
    "startUrls": [],
}

# Run the Actor and wait for it to finish
run = client.actor("hamza325/tijelabin-multi-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchQuery": "iphone",
  "startUrls": []
}' |
apify call hamza325/tijelabin-multi-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,hamza325/tijelabin-multi-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/jsFK686DMzClcUWT2/builds/UZdeucb25PhJgEXFf/openapi.json
