# InfoJobs Spain Jobs Scraper (`devilscrapes/infojobs-spain-jobs-scraper`) Actor

Scrape job listings from InfoJobs.net, Spain's dominant job board, by keyword and optional province. Get title, company, city, salary if published, contract type, teleworking flag, posting date, and URL — deduplicated and ready to use.

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

## Pricing

Pay per event

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

<div align="center">
  <img src=".actor/icon.svg" width="160" alt="Devil Scrapes mark" />
</div>

## InfoJobs Spain Jobs Scraper

**💰 $2.70 / 1 000 results**  ·  pay only for results  ·  no credit card to try

*Captchas are our cardio.* 😈

Scrape job listings from InfoJobs.net, Spain's dominant job board, keyed
by keyword and an optional province. Get clean, deduplicated rows —
title, company, city, salary if published, contract type, teleworking
flag, posting date, and URL — ready for a recruiter pipeline or a
hiring-intent feed.

### 🎯 What this scrapes

InfoJobs.net lists thousands of Spanish job postings across every
industry, roughly 20-22 to a page, with no bulk export. This Actor
searches by keyword and an optional province, walks the result pages,
and returns one row per job: title, full description, city, absolute
URL, contract type, salary (when published), workday, teleworking
policy, posting timestamp, and the hiring company's name, logo, and
InfoJobs profile URL.

### 🔥 Features

- 🛡️ **We rotate browser fingerprints** (curl-cffi impersonation —
  Chrome and Firefox) so InfoJobs sees a real browser's TLS handshake,
  not a Python script.
- 🔁 **We retry with exponential backoff** on rate limits and server
  errors, honouring `Retry-After`.
- 🌐 **We route every request through Apify Proxy**, pinned to Spain
  (`ES`) by default to match InfoJobs' own market.
- 🧊 **Run-wide deduplication.** The same job id never appears twice in
  your dataset, even across pages.
- 🧾 **Province resolution built in.** Pass a plain name ("Madrid") or a
  numeric province id ("33") — both resolve against InfoJobs' own
  province table.
- 💰 **You pay only for results that land.** No data → no charge (only
  the small `actor-start` warm-up fee).

### 💡 Use cases

- **Recruiters and staffing agencies** — pull every open role matching
  a skill or title across Spanish employers, filtered to a province.
- **Job boards and aggregators** — backfill Spain-region postings your
  existing sources miss.
- **Sales / BD teams** — new job openings are a public hiring-intent
  signal; a company opening five engineering roles just funded a team.
- **Market research** — track which titles, cities, and contract types
  a sector is hiring for over time.

### ⚙️ How to use it

1. Set `keyword` — the search term to run, e.g. `"developer"`.
2. Optionally set `province` — a Spanish province name or numeric id.
3. Set `maxResults` and `maxPages` to bound how much you pull per run.
4. Run it, then export to JSON, CSV, or Excel — or pull the dataset
   straight from the Apify API.

### 📥 Input

| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
| `keyword` | `string` | yes | — | Search term, e.g. `"developer"`. |
| `province` | `string \| null` | no | `null` | Province name (e.g. `"Madrid"`) or numeric id (e.g. `"33"`). |
| `maxResults` | `integer` | no | `100` | Hard cap on total rows emitted (1-2000). |
| `maxPages` | `integer` | no | `10` | Safety cap on pages fetched, ~20-22 rows/page (1-100), independent of `maxResults`. |
| `proxyConfiguration` | `object` | no | `{"useApifyProxy": true, "apifyProxyCountry": "ES"}` | Apify Proxy configuration, ES-pinned by default. |

#### Example input

```json
{
  "keyword": "developer",
  "province": null,
  "maxResults": 5,
  "maxPages": 1
}
```

### 📤 Output

One row per deduplicated job matching your search.

| Field | Type | Notes |
|---|---|---|
| `job_id` | `string` | Stable unique id, dedupe key. |
| `title` | `string` | Job title. |
| `description` | `string` | Full job description text. |
| `city` | `string \| null` | City, if published. |
| `url` | `string` | Absolute `https://` job URL. |
| `contract_type` | `string \| null` | e.g. `"Contrato indefinido"`. |
| `salary_min` | `number \| null` | Salary range minimum — `null` when salary is not published. |
| `salary_max` | `number \| null` | Salary range maximum — `null` when salary is not published. |
| `salary_period` | `string \| null` | e.g. `"YEAR"`. |
| `salary_currency` | `string \| null` | e.g. `"EUR"`. |
| `workday` | `string \| null` | e.g. `"Jornada completa"`. |
| `teleworking` | `string \| null` | e.g. `"Híbrido"`. |
| `published_at` | `string` | ISO-8601 UTC posting timestamp. |
| `company_name` | `string \| null` | Hiring company's name. |
| `company_logo_url` | `string \| null` | Hiring company's logo URL. |
| `company_url` | `string \| null` | Hiring company's InfoJobs profile URL. |
| `states` | `array<string>` | Offer state flags, e.g. `["published"]`. May be empty. |
| `is_executive` | `boolean` | Executive-level listing flag. |

#### Example output

```json
{
  "job_id": "857fba60ca48faa2976d0436cd55d4",
  "title": "Developer Mainframe COBOL (Z/OS, inglés)",
  "description": "Buscamos un desarrollador con experiencia en COBOL...",
  "city": "Madrid",
  "url": "https://www.infojobs.net/madrid/developer-mainframe-cobol.-ibm-z-.-ingles/of-i857fba60ca48faa2976d0436cd55d4",
  "contract_type": "Indefinido",
  "salary_min": null,
  "salary_max": null,
  "salary_period": null,
  "salary_currency": null,
  "workday": "Completa",
  "teleworking": "Híbrido",
  "published_at": "2026-09-24T10:15:00Z",
  "company_name": "Grupo IBM Partner",
  "company_logo_url": "https://static.infojobs.net/...",
  "company_url": "https://www.infojobs.net/empresa/...",
  "states": ["published"],
  "is_executive": false
}
```

### 💰 Pricing

Pay-Per-Event — you pay only when these events fire:

| Event | USD | What it is |
|---|---:|---|
| `actor-start` | $0.20 | One-off warm-up charge per run |
| `result-emitted` | $0.0025 | Per deduplicated job posting written to the dataset |

Example: 1 000 job postings = **$2.70 per run** ($0.20 start + 1000 x
$0.0025) — **$2.70 / 1,000 results** once you're pulling more
than a handful of rows per run. No subscription, no minimum, no card
required to try it.

### 🚧 Limitations

- **Single search per run.** One `keyword` plus an optional `province`
  per run — no multi-keyword batching in this version.
- **`maxResults` vs `maxPages` are independent caps.** Whichever fires
  first stops the run: hit your result budget, hit your page-fetch
  budget, or run out of pages InfoJobs actually has. The status message
  tells you which one applied.
- **List-page fields only.** This returns the fields InfoJobs' search
  result page publishes — it doesn't follow through to any further
  detail page.
- **A narrow search can legitimately return zero rows.** That's a
  successful run, not a failure — widen your keyword or drop the
  province filter.
- **Spain only.** InfoJobs operates exclusively in the Spanish market.

### ❓ FAQ

**Do I need an InfoJobs account or API key?**
No. This reads InfoJobs' public search result pages.

**Why did my run return fewer rows than `maxResults`?**
Either the search genuinely has fewer matching jobs than your cap, or
`maxPages` stopped the run first — the status message tells you which.

**What happens if a job posting is malformed or missing a field?**
It's skipped and logged, and the rest of the page still comes through —
one bad row never drops your whole run.

**Can I filter by province?**
Yes — pass a plain Spanish province name (`"Madrid"`, `"Barcelona"`) or
its numeric InfoJobs id (`"33"`); both resolve the same way.

**Can I run this on a schedule?**
Yes — use Apify Schedules to re-run your keyword/province combination on
a cadence and catch newly posted jobs.

### Your feedback

Found a bug, or need a field we don't return yet? Contact us at
<https://apify.com/DevilScrapes> — we
read every message.

# Changelog

This Actor's version history is a separate document: https://apify.com/devilscrapes/infojobs-spain-jobs-scraper/changelog.md

# Actor input Schema

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

Search term, e.g. "developer". URL-encoded into the InfoJobs search query.

## `province` (type: `string`):

Optional province filter — a Spanish province name (e.g. "Madrid") or a numeric province id (e.g. "33"). Leave empty for no province filter.

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

Hard cap on total rows emitted for this search.

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

Safety cap on pages fetched (~20-22 rows/page), independent of maxResults.

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

Apify Proxy configuration. Defaults to the RESIDENTIAL group pinned to Spain (ES) to match InfoJobs' target market. The group must be named explicitly — a bare useApifyProxy resolves to datacenter, which has no ES exit.

## Actor input object example

```json
{
  "keyword": "developer",
  "maxResults": 40,
  "maxPages": 2,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "ES"
  }
}
```

# Actor output Schema

## `datasetItems` (type: `string`):

All dataset items as JSON.

## `datasetItemsCsv` (type: `string`):

Same data exported to CSV.

## `datasetView` (type: `string`):

Open the run dataset in the Console.

# 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": "developer",
    "maxResults": 40,
    "maxPages": 2,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "ES"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("devilscrapes/infojobs-spain-jobs-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": "developer",
    "maxResults": 40,
    "maxPages": 2,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "ES",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("devilscrapes/infojobs-spain-jobs-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": "developer",
  "maxResults": 40,
  "maxPages": 2,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "ES"
  }
}' |
apify call devilscrapes/infojobs-spain-jobs-scraper --silent --output-dataset

```

## MCP server setup

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