# Tecnoempleo Scraper — 2,400+ Spain IT Jobs, Salaries & Empleo (`nomad-agent/tecnoempleo-scraper`) Actor

Scrape Tecnoempleo.com's full live catalogue — 2,400+ Spanish IT & developer jobs (empleo), far beyond the ~80-item cap of its RSS feed. Get title, company, province, EUR salary, tech stack, date & apply URL. Filter by province, work modality, category & keyword; delta mode for daily alerts.

- **URL**: https://apify.com/nomad-agent/tecnoempleo-scraper.md
- **Developed by:** [Nomad.Dev](https://apify.com/nomad-agent) (community)
- **Categories:** Jobs
- **Stats:** 4 total users, 2 monthly users, 99.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 job 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/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

## Tecnoempleo Scraper — 2,400+ Spain IT & Tech Jobs

Scrape current IT and developer vacancies from Tecnoempleo.com, Spain's dedicated tech job board.

> **Claude / Codex skill to describe and setup this actor: [SKILL.md](https://github.com/Exdenta/OinkAIJobSearch/blob/main/skill/tecnoempleo-scraper/SKILL.md)**

Tecnoempleo is one of Spain's largest specialist IT job boards, carrying developer, DevOps, data, QA and technical-management roles from Spanish employers and consultancies. This Actor crawls its live listing pages — **thousands of vacancies, far beyond the ~80-item cap of the site's RSS feed** — into clean, deduplicated JSON, so you can filter Spanish tech empleo by province, work modality, category and keyword and pipe it straight into your own tools. No login, no proxies.

If the HTML listing is ever unreachable, the Actor automatically falls back to the site's RSS feed instead of failing, so a run always returns the most recent postings it can.

### What Tecnoempleo data does this scraper extract?

Each result is one flat JSON record per job posting:

| Field | Meaning |
|---|---|
| `title` | Job title as posted |
| `company` | Hiring company / organisation |
| `location` | Location or work-mode label (e.g. `Madrid`, `100% remoto`, `Híbrido`) |
| `category` | Professional function / role category (e.g. `Analista`, `Programador`) |
| `technologies` | Technology / skill tags on the posting (e.g. `["Java","AWS"]`) |
| `url` | Direct link to the posting |
| `postedAt` | Posting date (`DD/MM/YYYY`; ISO when `includeDescription` is on) |
| `snippet` | Short description excerpt |
| `description` | Full description — only when `includeDescription` is enabled |
| `salaryMin` / `salaryMax` / `salaryCurrency` / `salaryPeriod` | Structured salary in EUR — only when `includeDescription` is enabled and the employer discloses it (never guessed) |
| `id` | Stable source-side identifier |

### How to scrape Tecnoempleo with this Actor

1. Click **Try for free** / **Run** — no login to the target site, no cookies, no proxies to configure.
2. Adjust the input (keyword, filters, `maxItems`) or keep the defaults.
3. Run it and export the dataset as JSON, CSV or Excel, or read it over the [API](https://docs.apify.com/api/v2).

Run it from your own code:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("nomad-agent/tecnoempleo-scraper").call(run_input={"maxItems": 50})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["title"], "—", item["company"], item["url"])
```

Or a single HTTP call that runs the Actor and returns items in one response:

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/nomad-agent~tecnoempleo-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"maxItems": 50}'
```

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `keyword` | string | *(empty)* | Sent to Tecnoempleo's own search (narrows the crawl) and applied client-side against `title`, `company`, `snippet` and `technologies`. Leave empty to return everything. |
| `province` | string | *(empty)* | Spanish province name (e.g. `Madrid`, `Barcelona`, `Malaga`). Accent/case-insensitive, resolved live against the site's province list. Unrecognised names are ignored. |
| `remote` | string | `any` | Work modality: `remote` (100%), `hybrid`, `on-site`, or `any`. |
| `category` | string | *(empty)* | Professional-function / category name (e.g. `Programador`, `Analista`, `DevOps`). Accent/case-insensitive, resolved live. Unrecognised names are ignored. |
| `includeDescription` | boolean | `false` | Fetch each posting's detail page to fill the full `description` and structured salary (EUR). One extra request per returned posting. |
| `titleExclude` | array | `[]` | Drop listings whose `title` contains any of these case-insensitive terms. |
| `companyExclude` | array | `[]` | Drop listings whose `company` contains any of these case-insensitive terms. |
| `postedSince` | integer | `0` | Drop listings older than N days, from the posting date. Listings with no parseable date are always kept. Set 0 to disable. |
| `onlyNewSinceLastRun` | boolean | `false` | Delta mode: emit only postings not seen on previous runs (state persists across runs). Great for scheduled daily alerts. |
| `maxItems` | integer | `80` | Maximum postings to return. HTML pagination goes well beyond the RSS feed's ~80-item cap. Set 0 for no limit. |
| `cacheTtlSeconds` | integer | `1800` | *(Advanced)* Reuses fetched listing/detail pages for this many seconds so rapid re-runs don't hit Tecnoempleo again. Set 0 to always fetch live. |

### Output example

```json
{
  "id": "rf-118842abc123def4567890abcdef1234",
  "title": "Programador/a Python",
  "company": "Sopra Steria",
  "location": "Valencia",
  "category": "Programador",
  "technologies": ["Python", "AWS", "Django"],
  "url": "https://www.tecnoempleo.com/programador-python-valencia/python-aws/rf-118842abc123def4567890abcdef1234",
  "postedAt": "2026-06-30",
  "snippet": "Se busca programador/a Python con experiencia...",
  "description": "Se busca programador/a Python... (full text, only with includeDescription)",
  "salaryMin": 30000,
  "salaryMax": 42000,
  "salaryCurrency": "EUR",
  "salaryPeriod": "year",
  "source": "tecnoempleo"
}
```

### Integrations

Export results as JSON, CSV or Excel; connect via Make, Zapier or n8n; call directly with `run-sync-get-dataset-items`; or plug into AI agents through the Apify MCP server.

### Pricing

Pay per event: **$0.005 per Actor start** and **$1.50 per 1,000 job results** ($0.0015 each), dropping to **$1.20 / 1,000** on higher Apify plans.

| Jobs returned | You pay |
|---|---|
| 100 | ≈ $0.16 |
| 1,000 | ≈ $1.51 |
| 2,400 (the full live catalogue) | ≈ $3.61 |

A run that finds nothing pushes a single free diagnostic row explaining why — you are never billed for results you did not get. No subscription, no rental.

### Use cases

- Spanish tech-job boards and alert bots
- Sourcing developers in Spain
- Tracking which companies are actively hiring IT talent in Spain
- Filtering the live feed for specific tech keywords via the `keyword` input

### FAQ

**Is it legal to scrape Tecnoempleo?**
This Actor reads only publicly available job postings — data any visitor can see without logging in. No personal data behind authentication is touched. Review the target site's terms and your local regulations for your specific use case.

**Do I need an account on the target site?**
No. Postings are fetched from public pages/APIs — no login, cookies or session tokens.

**How fresh is the data?**
Every run fetches live listings. Results are cached for `cacheTtlSeconds` (default 30 min, set 0 to always hit the source live).

**How many jobs can I get?**
`maxItems` caps the run (set 0 where supported for no cap). Most sources paginate from newest to oldest.

**Which roles does Tecnoempleo cover?**
Primarily IT and engineering — backend/frontend developers, DevOps, data, QA, IT support and technical management — mostly across Spain, with a growing share of remote-friendly listings.

**Can I get only remote jobs?**
Set `remote` to `remote` (100% en remoto), `hybrid` or `on-site`. You can also filter by `province` and `category`, or post-process the `location` field.

**Can I get salaries and full descriptions?**
Enable `includeDescription`. The Actor then reads each posting's structured JobPosting data to fill the full `description` and `salaryMin`/`salaryMax`/`salaryCurrency`/`salaryPeriod` in EUR (only when the employer discloses a salary — it is never guessed).

**How do I track only new jobs?**
Enable `onlyNewSinceLastRun` and schedule the Actor. It remembers listing ids across runs and returns only postings it hasn't seen before.

**Something broken or missing?**
Open an issue on the Actor's **Issues** tab — it is monitored and reliability fixes ship fast.

**Getting fewer results than you expected?**
The Actor tells you why: a run that matches nothing pushes one free diagnostic row instead of an empty dataset. Relax `keyword`, `province`, `category`, `remote` or `postedSince`, and remember that `onlyNewSinceLastRun` returns *only* postings it has not seen on an earlier run — the second run of an unchanged feed is empty by design.

### Found it useful?

Leave a rating on the **Reviews** tab — it takes ten seconds, and it is the single biggest thing that helps other people in Spain's tech-hiring niche find this Actor.

### Related Actors

- [Web Developer Jobs Scraper — 10 Boards in One](https://apify.com/nomad-agent/web-dev-bundle)
- [InfoJobs Scraper — Ofertas de Trabajo y Empleo España](https://apify.com/nomad-agent/infojobs-scraper) — AI skill tags, structured salary, delta mode
- [JustJoin.it Jobs Scraper — Polish Tech & IT Jobs](https://apify.com/nomad-agent/justjoinit-scraper)

***

**From the maker of [Oink](https://github.com/Exdenta/OinkAIJobSearch)** — an open-source, AI-powered job-search bot for Telegram that runs on these Actors. [Try the free bot](https://t.me/job_search_everyday_bot), get a managed instance at [oinkjobsearch.com](https://oinkjobsearch.com), or browse the [full catalog of 50+ Actors](https://apify.com/nomad-agent).

# Actor input Schema

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

Optional keyword. Sent to Tecnoempleo's own search (so it narrows the crawl to matching offers) and also applied client-side (case-insensitive) against the title, company, description snippet and technology tags. Leave empty to return all listings.

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

Optional Spanish province name to filter by (e.g. 'Madrid', 'Barcelona', 'Valencia', 'Malaga'). Accent- and case-insensitive; resolved live against Tecnoempleo's own province list, so it always matches the site's current options. Unrecognised names are ignored (the run is not filtered by province).

## `remote` (type: `string`):

Filter by work arrangement: 'remote' (100% en remoto), 'hybrid' (híbrido) or 'on-site' (presencial). 'any' (default) returns all.

## `category` (type: `string`):

Optional professional-function / category name to filter by (e.g. 'Programador', 'Analista', 'DevOps', 'Business Intelligence'). Accent- and case-insensitive; resolved live against Tecnoempleo's own category list. Unrecognised names are ignored.

## `includeDescription` (type: `boolean`):

Fetch each posting's detail page to fill the full job description and structured salary (salaryMin/salaryMax/salaryCurrency/salaryPeriod, in EUR, from the site's own JobPosting data — never guessed). On by default. Turn off for lighter, faster output — costs one extra request per returned posting, applied after maxItems truncation.

## `titleExclude` (type: `array`):

Skip listings whose title contains any of these case-insensitive terms.

## `companyExclude` (type: `array`):

Skip listings whose company name contains any of these case-insensitive terms.

## `postedSince` (type: `integer`):

Only return listings posted within this many days, based on the posting date shown by Tecnoempleo. Listings whose date can't be parsed are always kept (can't be judged, so they're not dropped). Set 0 (default) to disable.

## `onlyNewSinceLastRun` (type: `boolean`):

Incremental mode: remember every listing id returned on previous runs and emit only postings not seen before. Ideal for scheduled runs / daily alerts. State is kept in a named key-value store and persists across runs.

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

Maximum number of postings to return. Thanks to HTML pagination this can go well beyond the RSS feed's ~80-item cap (thousands of listings are available). Set 0 for no limit (bounded only by the total matching offers).

## `cacheTtlSeconds` (type: `integer`):

Reuses fetched listing/detail pages for this many seconds so rapid re-runs don't hit Tecnoempleo again. Set 0 to always fetch live.

## Actor input object example

```json
{
  "keyword": "python",
  "province": "Madrid",
  "remote": "any",
  "category": "Programador",
  "includeDescription": true,
  "titleExclude": [
    "becario",
    "practicas"
  ],
  "companyExclude": [],
  "postedSince": 0,
  "onlyNewSinceLastRun": false,
  "maxItems": 80,
  "cacheTtlSeconds": 1800
}
```

# Actor output Schema

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

No description

# 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("nomad-agent/tecnoempleo-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("nomad-agent/tecnoempleo-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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 nomad-agent/tecnoempleo-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=nomad-agent/tecnoempleo-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

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