# OCC.com.mx Jobs Scraper (`bovi/occ-com-mx-scraper`) Actor

Scrapes job listings from OCC.com.mx, Mexico's leading job board. Extracts job titles, companies, locations, salaries, and descriptions for lead generation and market research.

- **URL**: https://apify.com/bovi/occ-com-mx-scraper.md
- **Developed by:** [Vitalii Bondarev](https://apify.com/bovi) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.85 / 1,000 listings

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/platform/actors/running/actors-in-store#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

## OCC.com.mx Jobs Scraper

Apify Actor that scrapes job listings from [OCC.com.mx](https://www.occ.com.mx) and emits normalized job records.

### What it extracts

The actor collects job postings from OCC.com.mx search results and, when detail fetching is enabled, enriches each item from the job detail page. Data is read from **server-rendered HTML** and **JobPosting JSON-LD**. **No headless browser** is used.

Typical extracted information includes job identity and URL, titles and descriptions, employer information, location, contract and employment types, salary when present, posting age/dates, and source metadata. Field availability depends on what the site actually returns for each listing.

### Proxy behavior (mandatory)

This actor is configured for **residential Mexico** proxy usage. Requests are made through the Apify proxy with a residential Mexico geo-preference so that search and detail pages are served in the expected regional context. Provide a valid `proxyConfiguration` (see Inputs). Running without a suitable residential Mexico proxy is not supported for reliable operation.

### Input example

```json
{
  "searchQuery": "desarrollador",
  "maxResults": 50,
  "fetchDetails": true,
  "contractType": "",
  "employmentType": "",
  "city": "Ciudad de México",
  "state": "CDMX",
  "minSalary": 0,
  "daysOld": 30,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["RESIDENTIAL"],
    "apifyProxyCountry": "MX"
  }
}
```

### Inputs

| Field | Type | Required | Description |
|--------|------|----------|-------------|
| `searchQuery` | string | Yes | Keyword or phrase used in the OCC.com.mx job search. |
| `maxResults` | integer | No | Maximum number of job records to emit. Pagination stops when this limit is reached or when no further result pages are available (see also the pagination safety ceiling below). |
| `fetchDetails` | boolean | No | When `true`, the actor requests each job detail page and merges detail-level fields into the output. When `false`, **no detail requests** are made; the actor emits only **honest minimal** records (ID, URL, source, and timestamp). Filters that depend on detail-only fields **cannot match** those minimal records. |
| `contractType` | string | No | Client-side filter on contract type (e.g. values consistent with OCC labels). Empty or omitted means no filter on this field. |
| `employmentType` | string | No | Client-side filter on employment type. Empty or omitted means no filter on this field. |
| `city` | string | No | Client-side filter on city / locality. Empty or omitted means no filter on this field. |
| `state` | string | No | Client-side filter on state / region. Empty or omitted means no filter on this field. |
| `minSalary` | number | No | Client-side minimum salary threshold. Listings without a visible numeric salary do not satisfy a positive `minSalary` filter. Use `0` or omit to disable. |
| `daysOld` | integer | No | Client-side recency filter: only emit jobs considered at most this many days old when a usable date/age signal exists. Omit or use a non-restrictive value to disable. |
| `proxyConfiguration` | object | Yes | Apify proxy configuration. **Residential Mexico** proxying is required (`useApifyProxy: true`, residential group, country `MX` as in the example). |

#### `fetchDetails=false` and filters

With `fetchDetails=false`, each emitted dataset record is limited to minimal identity and provenance fields (job id, URL, source, timestamps). Optional detail fields are not invented. Any input filter that requires detail-level data (for example salary, full location breakdown, contract/employment type from the detail page, or rich description text) **will not match** those minimal records, so matching results may be empty even when search hits exist. Use `fetchDetails=true` when you need detail fields or detail-dependent filters.

### Output

Each dataset item is one normalized job record. Fields below are emitted when present in the parsed HTML/JSON-LD. **Optional fields may be absent** when the source page does not provide them.

| Field | Description |
|--------|-------------|
| `id` | Job identifier as exposed by OCC. |
| `url` | Canonical or listing URL for the job. |
| `title` | Job title. |
| `description` | Job description text when available (typically from detail/JSON-LD). |
| `company` | Employer / company name. |
| `companyId` | Employer identifier when present. |
| `city` | City or locality. |
| `state` | State or region. |
| `location` | Combined or raw location string when provided. |
| `country` | Country when present (commonly Mexico for this source). |
| `contractType` | Contract type label when present. |
| `employmentType` | Employment type label when present. |
| `salaryMin` | Numeric minimum salary when parseable. |
| `salaryMax` | Numeric maximum salary when parseable. |
| `salary` | Salary value or range summary when available. |
| `salaryCurrency` | Currency code or symbol when available. |
| `salaryVisible` | Boolean: `true` when a real salary is shown; `false` when pay is hidden or not disclosed. |
| `datePosted` | Posted date when available. |
| `validThrough` | Expiry / valid-through date when available. |
| `daysOld` | Age in days when it can be derived from source signals. |
| `source` | Source marker for the actor / site. |
| `scrapedAt` | ISO timestamp when the record was processed. |
| `jsonLd` | Parsed JobPosting JSON-LD object when present on the page. |

#### Salary and location notes

- **Hidden or undisclosed salaries** remain **numeric zero** (`salaryMin` / `salaryMax` / equivalent numeric salary fields as applicable) with **`salaryVisible: false`**. The actor does not fabricate pay bands.
- **No coordinates are invented.** Latitude/longitude are only emitted if explicitly present in the source data; otherwise they are omitted.
- Optional fields are omitted rather than filled with guesswork when fixtures/pages do not support them.

### Client-side filter behavior

Search results are retrieved from OCC; **refinement filters** supplied in input (`contractType`, `employmentType`, `city`, `state`, `minSalary`, `daysOld`, and similar) are applied **client-side** on the normalized records after parse (and after detail merge when `fetchDetails` is true). Listings that lack the fields required to evaluate a filter are treated as non-matches for that filter. This keeps filtering consistent with honest extraction rather than over-claiming server-side filter parity.

### Retries and response quality

Transient HTTP and network failures are handled with **bounded retries** and conservative backoff. Responses are checked at a high level for **usable HTML/JSON-LD** before parsing. Empty, clearly blocked, or non-job pages are not turned into fake listings. The actor does **not** claim Cloudflare bypass or anti-bot evasion beyond normal proxy-backed HTTP access to server-rendered pages.

### Pagination safety

Result pages are followed while results remain available and `maxResults` has not been reached. Pagination includes a **hard safety ceiling** on the maximum number of pages/requests so runaway crawls cannot continue indefinitely if the site returns ambiguous “next page” signals.

### Pricing (PPE)

Pay-per-event billing uses **exactly one job-record event per emitted dataset record**. You are charged for jobs successfully written to the dataset, not for intermediate search hits discarded by filters or failed detail downloads that produce no record.

### Limitations

- Relies on **server-rendered HTML** and **JobPosting JSON-LD** as returned for the session; layout or schema changes on OCC.com.mx can reduce field coverage.
- **No browser**: content that appears only after client-side rendering and is absent from HTML/JSON-LD will not appear in output.
- **Residential Mexico proxy** use is mandatory for expected behavior; other proxy setups may yield incomplete or region-mismatched pages.
- With **`fetchDetails=false`**, output is **minimal** (id/URL/source/timestamp); detail-dependent filters will not match those records.
- **Salaries** may be hidden; hidden pay is represented as numeric zero with `salaryVisible: false`, not as estimated market rates.
- **Geocoding is not performed**; coordinates are never invented.
- **Completeness is not guaranteed**: caps (`maxResults`, pagination safety ceiling), filters, and site availability all limit what is emitted.
- This README does **not** claim live verification against production OCC at any particular time; behavior is defined by the actor implementation and its fixtures/tests.
- Does not support fields that are not present in the actor’s fixtures and parsers; unsupported attributes are not promised.

### Ethics and compliance

Use this actor responsibly and in accordance with OCC.com.mx terms of use, applicable law, and Apify platform policies. Respect rate limits and personal data obligations when processing employer or listing content.

# Actor input Schema

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

Job search keywords or phrase to query on OCC.com.mx (e.g. developer, contador, marketing).

## `maxResults` (type: `integer`):

Maximum number of job listings to collect.

## `fetchDetails` (type: `boolean`):

When true, open each job page to collect full detail fields.

## `contractType` (type: `string`):

Optional client-side filter applied after normalization (e.g. indefinido, temporal).

## `employmentType` (type: `string`):

Optional client-side filter applied after normalization (e.g. tiempo completo, medio tiempo).

## `city` (type: `string`):

Optional client-side filter applied after normalization to match job city.

## `state` (type: `string`):

Optional client-side filter applied after normalization to match job state.

## `minSalary` (type: `integer`):

Optional client-side filter applied after normalization; keep jobs with salary at or above this value.

## `daysOld` (type: `integer`):

Optional client-side filter applied after normalization; keep jobs posted within this many days.

## `proxyConfiguration` (type: `object`):

Apify Proxy settings used for requests. Defaults to residential proxies in Mexico.

## Actor input object example

```json
{
  "maxResults": 50,
  "fetchDetails": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "MX"
  }
}
```

# Actor output Schema

## `results` (type: `string`):

Default dataset with scraped job listing results.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("bovi/occ-com-mx-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("bovi/occ-com-mx-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 '{}' |
apify call bovi/occ-com-mx-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,bovi/occ-com-mx-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/VBsVyaoFoJ3cVtAGE/builds/6GJwr7mMmySCXNq0b/openapi.json
