# InfoJobs Scraper: Spain Job Listings and Salaries (`reventadata/infojobs-scraper`) Actor

Scrape InfoJobs.net job offers in Spain by keyword, province, category, contract type, remote mode and posted date. Title, company, city, salary band, contract, workday, publication date and offer text. Optional applicants, skills and requirements. Unofficial.

- **URL**: https://apify.com/reventadata/infojobs-scraper.md
- **Developed by:** [reventadata](https://apify.com/reventadata) (community)
- **Categories:** Jobs, Automation
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 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.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## InfoJobs Scraper

> **Unofficial.** This Actor is not affiliated with, authorised by, endorsed by, or in any
> way officially connected to InfoJobs. "InfoJobs" is a trademark of its respective owner and
> is used here only to describe what this tool reads. It reads the same public search pages a
> browser does.

InfoJobs Scraper turns an InfoJobs.net (Spain) job search into a clean table: title, company, city, contract, working day, remote mode, published salary band, publication date and the offer text. Optionally it opens each offer to add applicants, required skills, languages, studies and experience.

### What does it do?

Give it a keyword (for example `camarero`) and, if you want, a province, category, contract type, on-site or remote mode, a posted-within window and a sort order. It reads the result pages and returns one row per offer.

### Why use it?

- **Filters applied by the site:** province, category, contract, remote mode, posted-since and sort are sent to InfoJobs itself. If InfoJobs does not apply one of them, the run stops there with a warning instead of returning unfiltered rows; rows already returned are kept, and a run with no rows fails.
- **Salary as numbers:** minimum, maximum, period (year or month) and currency when the offer publishes a salary; null otherwise.
- **Optional detail enrichment:** applicants so far, required skills and languages, minimum studies and experience.
- **Reaches a site that blocks plain scrapers:** pages are fetched through an unblocking service run by the Actor itself; you do not need a proxy or a key.
- **Public data only:** company names and the offer text the site shows are in the rows; nothing from behind a login.

### How to use it

1. Enter a `keyword`, or leave it empty to list every offer.
2. Optionally set the filters. Province and category take the InfoJobs numeric id (for example 33 for Madrid, 160 for Turismo y restauración); the input form lists the ids seen on the site.
3. Set `maxItems` and `maxPages`, run the Actor, and download the dataset as JSON, CSV or Excel.

**Limit of the site:** InfoJobs serves at most 2,999 pages (59,980 offers) for one search, even when it reports more. When a search is bigger, the Actor logs a warning; split it with the filters (one run per province, for example). Each page takes about 30 seconds.

**Detail pages are slow:** `fetchDetails` opens each offer's own page, one request per row, about 26 to 52 seconds each (measured), one at a time. Leave it off unless you need those fields. A detail page that cannot be read leaves the five detail fields null; the row is still returned.

#### Input

| Field | Type | Description |
|---|---|---|
| `keyword` | string | Job title or words. Empty lists every offer. |
| `provinceId` | string | InfoJobs province id (digits), for example `33`. |
| `categoryId` | string | InfoJobs category id (digits), for example `160`. |
| `contractTypeId` | string | Contract type (Indefinido, De duración determinada, Fijo discontinuo, ...). |
| `teleworkingId` | string | Presencial, Solo teletrabajo or Sin especificar. |
| `sinceDate` | string | `ANY`, `_24_HOURS`, `_7_DAYS` or `_15_DAYS`. |
| `sortBy` | string | `PUBLICATION_DATE` or `RELEVANCE`. |
| `fetchDetails` | boolean | Add applicants, skills, languages, studies, experience (one extra request per row). Each detail page used in a returned row is charged as a separate `detail-page` event. |
| `maxItems` | integer | Maximum rows to return. |
| `maxPages` | integer | Maximum result pages (20 offers each), up to 2,999. |

#### Sample Input

```json
{
    "keyword": "camarero",
    "provinceId": "33",
    "contractTypeId": "1",
    "maxItems": 20,
    "maxPages": 1
}
```

#### Output

One row per offer: `id`, `url`, `title`, `description`, `city`, `contractType`, `workday`, `teleworking`, `salaryMin`, `salaryMax`, `salaryPeriod`, `salaryCurrency`, `salaryType`, `publishedAt`, `companyName`, `companyUrl`, `upsellings`, `executive`, `searchUrl`. With `fetchDetails`: `applicants`, `requiredSkills`, `requiredLanguages`, `minimumStudies`, `minimumExperience`. Fields the offer does not give are null. The result list marks some offers as promoted (`upsellings`), and promoted offers can appear out of date order.

#### Sample Output

A real row (keyword `camarero`, with `fetchDetails`), description shortened and `searchUrl` left out:

```json
{
    "id": "98ce6b67ca4d60a459d2cc5c08e981",
    "url": "https://www.infojobs.net/zaragoza/camarero/of-i98ce6b67ca4d60a459d2cc5c08e981",
    "title": "Camarero/a",
    "description": "Desde InfoJobs Assistance un servicio de Adevinta, estamos buscando un/a Camarer...",
    "city": "Zaragoza",
    "contractType": "Contrato indefinido",
    "workday": "Jornada completa",
    "teleworking": "Presencial",
    "salaryMin": 1800,
    "salaryMax": 1900,
    "salaryPeriod": "MONTH",
    "salaryCurrency": "EUR",
    "salaryType": "GROSS",
    "publishedAt": "2026-11-06T07:37:08Z",
    "companyName": "InfoJobs Assistance",
    "companyUrl": "https://ijassistance.ofertas-trabajo.infojobs.net",
    "upsellings": ["PROMOTED"],
    "executive": false,
    "applicants": 111,
    "requiredSkills": ["camarero/a", "comandas", "barra", "sala", "terraza", "TPV", "Carnet manipulador/a alimentos"],
    "requiredLanguages": [],
    "minimumStudies": "Educación Secundaria Obligatoria",
    "minimumExperience": "Al menos 3 años"
}
```

### Pricing

Detail pages (`fetchDetails`) are charged as a separate event, one per detail page used in a returned row; a detail page that cannot be read is not charged. For current pricing and details, see the Store page.

### Privacy & Compliance

This Actor reads only publicly accessible search and offer pages and returns the public data they show. It sends no login and returns nothing from behind a login.

# Actor input Schema

## `keyword` (type: `string`):

Job title or words to search for, for example camarero or desarrollador. Leave empty to list every offer (use filters to stay under the site's 59,980-offer ceiling per search).

## `provinceId` (type: `string`):

Optional. One InfoJobs province id (digits). Seen on the site: 33 Madrid, 9 Barcelona, 49 Valencia/València, 43 Sevilla, 34 Málaga, 4 Alicante/Alacant, 53 Zaragoza, 36 Murcia, 51 Vizcaya/Bizkaia, 26 Islas Baleares, 20 Las Palmas, 46 Santa Cruz de Tenerife. Unlisted ids are passed through unvalidated; the run stops (and fails if it has no rows yet) if the site does not apply the filter.

## `categoryId` (type: `string`):

Optional. One InfoJobs category id (digits). Seen on the site: 160 Turismo y restauración, 170 Atención a clientes, 180 Otros, 120 Profesiones artes y oficios, 190 Comercial y ventas, 200 Ventas al detalle, 10 Administración de empresas, 40 Compras logística y almacén, 30 Calidad producción e I+D. Other ids are passed through unvalidated.

## `contractTypeId` (type: `string`):

Optional. Contract type filter.

## `teleworkingId` (type: `string`):

Optional. Work location mode filter.

## `sinceDate` (type: `string`):

Optional. Only offers published within this window. ANY sends no filter.

## `sortBy` (type: `string`):

Optional. Newest first or the site's relevance order. Empty uses the site's default (relevance with a keyword, newest without).

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

Off by default. When on, the Actor opens each offer's own page to add applicants, required skills, required languages, minimum studies and minimum experience. This is one extra slow request per row (about 30 seconds each, one at a time). Each detail page used in a returned row is charged as a separate pay-per-event "detail-page" event (see the Store page for pricing); a detail page that cannot be read is not charged.

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

Hard cap on rows pushed.

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

Cap on result pages fetched (20 offers each). The site serves at most 2,999 pages per search. Each page is one paid unblocking request on the Actor's side and takes about 30 seconds.

## Actor input object example

```json
{
  "keyword": "camarero",
  "sinceDate": "ANY",
  "fetchDetails": false,
  "maxItems": 20,
  "maxPages": 1
}
```

# Actor output Schema

## `dataset` (type: `string`):

Items scraped from the target website.

# 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 = {
    "keyword": "camarero",
    "maxItems": 20,
    "maxPages": 1
};

// Run the Actor and wait for it to finish
const run = await client.actor("reventadata/infojobs-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 = {
    "keyword": "camarero",
    "maxItems": 20,
    "maxPages": 1,
}

# Run the Actor and wait for it to finish
run = client.actor("reventadata/infojobs-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 '{
  "keyword": "camarero",
  "maxItems": 20,
  "maxPages": 1
}' |
apify call reventadata/infojobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,reventadata/infojobs-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/noGprz2cSAgH3uX8B/builds/WvvGFu9xV86MDHXmr/openapi.json
