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

Search InfoJobs Spain and export normalized public vacancies with employers, locations, salaries, work modes, requirements, and application URLs.

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

## Pricing

from $0.96 / 1,000 item extracteds

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

## InfoJobs Spain Jobs Scraper

Search InfoJobs Spain and export normalized public vacancies for recruiting research, hiring-market analysis, and recurring vacancy monitoring. The Actor collects stable offer IDs, titles, employers, locations, salary data, contract and work-mode details, publication dates, descriptions, requirements, skills, and application URLs.

It supports keyword searches, Spanish location filters, InfoJobs category IDs, remote/hybrid/on-site filters, public search URLs, and individual vacancy URLs. Results are ready for JSON, CSV, Excel, API, webhook, and scheduled-run workflows on Apify.

### What can you do with this InfoJobs scraper?

- Build a current feed of InfoJobs Spain jobs for selected roles.
- Compare vacancy demand across Spanish cities and provinces.
- Monitor remote, hybrid, or on-site hiring patterns.
- Collect employer, salary, contract, and publication-date signals.
- Enrich search results with full descriptions, requirements, skills, and languages.
- Deduplicate recurring exports with the stable `offerId` field.
- Send normalized vacancy rows to a spreadsheet, database, BI tool, or ATS workflow.

The Actor uses public InfoJobs pages. It does not apply to jobs, access candidate accounts, reveal private recruiter information, or bypass login-only features.

### Who is it for?

**Recruiters and talent teams** can build role-specific prospecting and market maps without copying listings manually.

**Labor-market analysts** can compare advertised salaries, work modes, contracts, employers, locations, and publication activity.

**Job aggregators** can ingest normalized public vacancy records with stable IDs and canonical application links.

**Data and automation teams** can schedule repeat runs, detect additions or changes downstream, and connect datasets to existing pipelines.

**Researchers** can produce bounded snapshots of public Spanish vacancy demand while retaining source URLs and collection timestamps.

### Why use this Actor?

- **Search and detail data in one workflow:** list pages provide fast discovery; optional detail enrichment adds requirements and skills.
- **Normalized fields:** work mode, salary boundaries, locations, dates, and URLs use stable keys.
- **Useful without enrichment:** set `includeDetails` to `false` for faster search-result collection.
- **Input flexibility:** combine keywords with real InfoJobs search or vacancy URLs.
- **Safe limits:** `maxItems` and `maxResultsPerSearch` bound output and cost.
- **Source traceability:** every row contains `sourceUrl`, `applicationUrl`, and `scrapedAt`.
- **No proxy setup:** the current implementation uses the public server-rendered InfoJobs surface directly.

### What InfoJobs job data is extracted?

| Field | Meaning |
| --- | --- |
| `offerId` | Stable InfoJobs vacancy identifier |
| `title` | Public job title |
| `employer` | Displayed employer name |
| `employerUrl` | Public InfoJobs company profile when available |
| `employerLogoUrl` | Public company logo URL when available |
| `location`, `city`, `province` | Displayed and normalized location fields |
| `workMode` | `remote`, `hybrid`, `on-site`, or `unspecified` |
| `salary`, `salaryMin`, `salaryMax` | Displayed and numeric compensation fields |
| `salaryCurrency`, `salaryPeriod` | Compensation currency and period |
| `contractType`, `workday` | Contract and schedule information |
| `publicationDate` | Vacancy publication timestamp |
| `description`, `descriptionHtml` | Plain-text and original rich-text descriptions |
| `requirements` | Combined stated requirements, skills, and languages |
| `minimumEducation`, `minimumExperience` | Minimum qualification fields |
| `skills`, `languages` | Structured public skill and language labels |
| `category`, `subcategory`, `industry` | InfoJobs classification fields |
| `applications`, `vacancies` | Public application and position counts when shown |
| `applicationUrl` | Canonical public vacancy/application page |
| `sourceUrl` | Input search or vacancy URL that produced the row |
| `scrapedAt` | UTC collection timestamp |

Fields may be `null` or empty when InfoJobs or the employer does not display them. Detail-only fields require `includeDetails: true`.

### How to get started

1. Open the Actor in Apify Console.
2. Add one or more values to **Search keywords**, such as `developer` or `analista de datos`.
3. Optionally set a Spanish province such as `Madrid`.
4. Optionally select work modes or add InfoJobs category IDs.
5. Keep **Include full job details** enabled when requirements and skills matter.
6. Choose a small `maxItems` value for the first run.
7. Click **Start**.
8. Open the **Vacancies** dataset view and export JSON, CSV, Excel, XML, or RSS.
9. Save the task or schedule it for recurring monitoring when the result fits your workflow.

### Input parameters

| Input | Type | Default | Description |
| --- | --- | --- | --- |
| `searchTerms` | string array | `['developer']` prefill | Independent role or keyword searches |
| `location` | string | empty | Spanish city or province name for keyword searches and output filtering |
| `categoryIds` | string array | empty | Numeric category IDs copied from an InfoJobs search URL |
| `workModes` | string array | empty | Any of `remote`, `hybrid`, or `on-site` |
| `startUrls` | URL array | empty | Public InfoJobs search or vacancy URLs |
| `includeDetails` | boolean | `true` | Fetch each vacancy page for full enrichment |
| `maxItems` | integer | `100` | Maximum unique jobs across the run, up to 10,000 |
| `maxResultsPerSearch` | integer | `100` | Maximum jobs accepted from each source, up to 10,000 |

When no keyword or URL is supplied, the Actor uses a small default `developer` search so the prefilled run produces useful output.

#### Example: Madrid hybrid data jobs

```json
{
  "searchTerms": ["analista de datos"],
  "location": "Madrid",
  "workModes": ["hybrid"],
  "includeDetails": true,
  "maxItems": 20,
  "maxResultsPerSearch": 20
}
```

#### Example: use a public search URL

```json
{
  "startUrls": [
    {
      "url": "https://www.infojobs.net/ofertas-trabajo?keyword=developer&provinceIds=33"
    }
  ],
  "includeDetails": false,
  "maxItems": 50,
  "maxResultsPerSearch": 50
}
```

### Output example

A current enriched result has this shape:

```json
{
  "offerId": "7636f407c7468da6721c6f4c6ec090",
  "title": "Analista de datos",
  "employer": "Mobius Group",
  "location": "Alcobendas, Madrid",
  "city": "Alcobendas",
  "province": "Madrid",
  "workMode": "hybrid",
  "salary": null,
  "salaryMin": null,
  "salaryMax": null,
  "contractType": "Indefinido",
  "workday": "Completa",
  "publicationDate": "2026-08-20T09:29:34.000Z",
  "description": "Public vacancy description...",
  "requirements": "Skills: Análisis de datos, Visualización de datos...",
  "skills": ["Análisis de datos", "Visualización de datos"],
  "languages": [],
  "category": "Administración de empresas",
  "subcategory": "Análisis de datos",
  "applicationUrl": "https://www.infojobs.net/alcobendas/analista-datos/of-i7636f407c7468da6721c6f4c6ec090",
  "sourceUrl": "https://www.infojobs.net/ofertas-trabajo?keyword=analista+de+datos&teleworkingIds=3",
  "scrapedAt": "2026-08-23T14:35:00.000Z"
}
```

Public listings change continuously, so titles, counts, and available fields will differ between runs.

### How much does it cost to extract InfoJobs Spain jobs?

The Actor uses pay per event: one **Run started** event and one **Item extracted** event for each dataset row. The current BRONZE rates are a $0.005 start event and $0.0016 per extracted job. Higher usage tiers receive lower per-job rates automatically.

| Jobs | Start event | Item calculation | Estimated BRONZE total |
| ---: | ---: | --- | ---: |
| 10 | one | 10 × BRONZE item rate | $0.021 |
| 100 | one | 100 × BRONZE item rate | $0.165 |
| 1,000 | one | 1,000 × BRONZE item rate | $1.605 |

Only records saved to the default dataset receive the item event. Duplicate or rejected rows are not charged as jobs. Apify applies the tier associated with your account; Console shows the active tier and run budget before execution.

Detail enrichment does not have a separate charge event, but it makes one extra public request per vacancy and therefore takes longer than search-only extraction.

### Search, pagination, and filtering behavior

Keyword searches use InfoJobs' public server-rendered search data. For location input, the Actor resolves the matching province from current InfoJobs aggregations and then applies its source ID to pagination.

`categoryIds` and `workModes` are sent with every search page. Explicit search URLs also receive configured category and work-mode filters. Location and work mode are checked again against normalized output where applicable.

The Actor deduplicates vacancies by `offerId` across every input. It stops when the total limit, per-search limit, or upstream final page is reached.

A direct vacancy URL returns one enriched record. Category filters cannot meaningfully be applied to one already-selected vacancy, so that combination fails with a clear input error rather than silently ignoring the filter.

### Monitoring new or changed vacancies

Use an Apify schedule for recurring collection:

1. Save a Task with narrow keywords and filters.
2. Set a daily or weekly schedule.
3. Send completed-run data through a webhook or integration.
4. Compare `offerId` values with the previous dataset.
5. Treat a changed salary, work mode, description, or publication field as an update according to your own business rules.

The Actor exports snapshots. It does not maintain history, send alerts, or label changes inside a run. Apify schedules, webhooks, storage, and downstream automation provide those capabilities.

### Integration ideas

- **Google Sheets:** append current vacancies for recruiter review.
- **Airtable:** upsert by `offerId` and track status over time.
- **Zapier or Make:** trigger workflows after successful scheduled runs.
- **Webhooks:** send dataset IDs to a data warehouse loader.
- **Power BI or Tableau:** analyze employer, province, salary, and work-mode trends.
- **ATS enrichment:** attach source-attributed public market context to role research.
- **Data lake:** store dated JSON snapshots and compute changes downstream.

### Run from the Apify API with cURL

Replace `YOUR_APIFY_TOKEN` with your token:

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~infojobs-spain-jobs-scraper/runs?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "searchTerms": ["developer"],
    "location": "Madrid",
    "includeDetails": true,
    "maxItems": 20
  }'
```

To wait for completion and receive dataset items directly, use the `run-sync-get-dataset-items` endpoint for small bounded jobs.

### Run with JavaScript

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/infojobs-spain-jobs-scraper').call({
  searchTerms: ['analista de datos'],
  workModes: ['hybrid'],
  includeDetails: true,
  maxItems: 25,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Run with Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/infojobs-spain-jobs-scraper').call(run_input={
    'searchTerms': ['ventas'],
    'location': 'Barcelona',
    'includeDetails': False,
    'maxItems': 50,
})
items = client.dataset(run['defaultDatasetId']).list_items().items
print(items)
```

### Use with MCP and AI agents

Add the Actor to Claude Code through Apify MCP:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/infojobs-spain-jobs-scraper"
```

#### Claude Desktop, Cursor, and VS Code MCP setup

Claude Desktop, Cursor, and VS Code can use this equivalent HTTP MCP configuration:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/infojobs-spain-jobs-scraper"
    }
  }
}
```

Example prompts:

- “Find 20 current hybrid data analyst jobs in Madrid on InfoJobs and summarize the most common required skills.”
- “Collect 50 remote developer vacancies from InfoJobs Spain and group employers by contract type.”
- “Run my saved InfoJobs monitoring task and compare stable offer IDs with yesterday's export.”

Your MCP client still needs valid Apify authentication and permission to run the Actor.

### Performance and cost tips

- Disable `includeDetails` when search-card fields are sufficient.
- Start with 10–20 items while validating a keyword or location.
- Use `maxResultsPerSearch` to prevent one broad term from consuming the whole run.
- Prefer specific role phrases over generic terms for cleaner data.
- Combine related searches in one run to share the start event.
- Deduplicate downstream by `offerId`, not by title.
- Avoid requesting thousands of enriched details when a smaller representative sample answers the analysis question.

### Limitations and failure behavior

- InfoJobs controls page structure, filters, availability, and field completeness; source changes can require an Actor update.
- A search may naturally return no vacancies.
- Salary and requirements are often omitted by employers and remain `null` or empty.
- Search-only mode cannot provide detail-only fields.
- A disappeared vacancy may fail detail enrichment; when a search row still exists, the Actor preserves its search fields and logs the degraded enrichment.
- A failed search page stops the run so incomplete pagination is not presented as complete coverage.
- Location resolution uses currently available province aggregations for the searched term.
- Output reflects public data at run time and is not guaranteed to represent every vacancy on InfoJobs.
- The Actor does not apply to vacancies or access login-only candidate features.

### Troubleshooting

#### The search returned no jobs

Try the same keyword on InfoJobs, remove a location or work-mode filter, check the spelling, and lower the specificity. No-result searches complete normally with an empty dataset.

#### My location was not found

Use a province label shown by InfoJobs, such as `Madrid`, `Barcelona`, or `Valencia`. The Actor reports an error rather than silently broadening a location it cannot resolve.

#### Some requirement fields are empty

Enable `includeDetails`. If it is already enabled, the employer may not have published structured requirements. Check `description`, which may contain free-text requirements.

#### The run failed on an InfoJobs URL

Confirm that the URL uses HTTPS, belongs to `infojobs.net`, and points to `/ofertas-trabajo` or a public `/of-i...` vacancy. Remove tracking fragments if the source URL is malformed.

#### Why are there fewer records than `maxItems`?

The search may contain fewer matching jobs, filters may reject rows, duplicate offer IDs may appear across inputs, or `maxResultsPerSearch` may be lower than the global limit.

### Responsible use and legality

InfoJobs pages and vacancy descriptions may be protected by terms of service and intellectual-property rights. Use the Actor only for lawful purposes and public data you are authorized to process.

Follow applicable privacy, database, employment, anti-discrimination, and data-protection rules, including GDPR where relevant. Minimize retention, preserve source attribution, secure exports, and avoid using scraped data for unlawful profiling, spam, or automated decisions about individuals.

You are responsible for your inputs, schedule, downstream processing, and compliance. This documentation is not legal advice.

### Related Automation Lab Actors

- [Company Career Page Jobs Scraper](https://apify.com/automation-lab/company-career-page-jobs-scraper) for supplied employer career sites.
- [Computrabajo LATAM Jobs Scraper](https://apify.com/automation-lab/computrabajo-jobs-scraper) for public Computrabajo country sites.
- [We Work Remotely Jobs Scraper](https://apify.com/automation-lab/we-work-remotely-jobs-scraper) for remote-first vacancy research.

Use this Actor when the target source is specifically InfoJobs Spain; use a related Actor only when its source and coverage match the required workflow.

### FAQ

#### Does the Actor require an InfoJobs account?

No. It extracts public search and vacancy information without candidate login.

#### Can I search several roles in one run?

Yes. Add multiple `searchTerms`. `maxResultsPerSearch` applies independently and `maxItems` caps the combined unique output.

#### Can I provide a single vacancy URL?

Yes. Add the public InfoJobs URL to `startUrls`; the Actor returns the normalized enriched vacancy when it is still available.

#### Does it detect new or changed jobs?

It provides stable IDs and current snapshots. Schedule runs and compare datasets downstream to implement change detection.

#### Can I export to CSV or Excel?

Yes. Open the default dataset and choose any Apify-supported export format, or fetch items through the Dataset API.

#### Is every result charged separately?

Every unique accepted vacancy saved to the dataset emits one `item` event. Start, rejected, duplicate, empty, and failed rows do not add item events.

#### Does it use residential proxies?

No. The current HTTP route accesses public server-rendered pages directly. No automatic paid proxy fallback is enabled.

#### How fresh is the data?

Each run reads the source at execution time. `publicationDate` comes from InfoJobs, while `scrapedAt` records when this Actor saved the row.

# Actor input Schema

## `searchTerms` (type: `array`):

Job titles or keywords to search independently, such as developer, sales, or data analyst.

## `location` (type: `string`):

Optional Spanish city or province slug/name applied to keyword searches and output filtering, such as Madrid or Barcelona.

## `categoryIds` (type: `array`):

Optional numeric InfoJobs category IDs. These are preserved in search requests; obtain IDs from an InfoJobs search URL.

## `workModes` (type: `array`):

Optional work-mode filters applied to every search.

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

Public InfoJobs Spain search-result or individual vacancy URLs. URLs can be combined with keyword searches.

## `includeDetails` (type: `boolean`):

Open each vacancy to add requirements, skills, languages, category, structured location, application count, and full HTML description.

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

Maximum unique vacancy records saved across the whole run.

## `maxResultsPerSearch` (type: `integer`):

Maximum records accepted from each keyword or search URL before moving to the next source.

## Actor input object example

```json
{
  "searchTerms": [
    "developer"
  ],
  "categoryIds": [],
  "workModes": [],
  "startUrls": [],
  "includeDetails": true,
  "maxItems": 20,
  "maxResultsPerSearch": 20
}
```

# Actor output Schema

## `overview` (type: `string`):

Default dataset containing all extracted vacancy records.

# 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 = {
    "searchTerms": [
        "developer"
    ],
    "location": "",
    "categoryIds": [],
    "workModes": [],
    "startUrls": [],
    "includeDetails": true,
    "maxItems": 20,
    "maxResultsPerSearch": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/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 = {
    "searchTerms": ["developer"],
    "location": "",
    "categoryIds": [],
    "workModes": [],
    "startUrls": [],
    "includeDetails": True,
    "maxItems": 20,
    "maxResultsPerSearch": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/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 '{
  "searchTerms": [
    "developer"
  ],
  "location": "",
  "categoryIds": [],
  "workModes": [],
  "startUrls": [],
  "includeDetails": true,
  "maxItems": 20,
  "maxResultsPerSearch": 20
}' |
apify call automation-lab/infojobs-spain-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/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/eZ6Y2oQni5iCJ8VAq/builds/5geih6fw1gIMBL0at/openapi.json
