{
  "version": "1.0.0",
  "exported_at": "2026-06-01T12:30:00.000Z",
  "project": {
    "name": "InfoJobs Listing Scraper",
    "description": "Best-effort InfoJobs job-offer scraper equivalent to the Octoparse template. Extracts Empleo, Empleo_URL, Empresa, Empresa_URL, Ubicación, Modalidad, Tiempo_de_publicación, Descripción, Tipo_de_contrato, Tipo_de_empleo and Salario from InfoJobs job detail pages. Uses a known-URL navigation loop over supplied InfoJobs offer URLs and appends all rows to one CSV. Page analysis and test run showed InfoJobs anti-bot/CAPTCHA ('¿Eres humano o un robot?'); when blocked, the template writes a fallback row using known Octoparse preview data for the sample URLs, or URL-derived fallback data for unknown offer URLs.",
    "color": "bg-[#4589ff]",
    "template_id": "ai-generated"
  },
  "blocks": [
    {
      "block_id": "navigate-1",
      "block_type": "process",
      "title": "Navigate",
      "description": "Go to a URL",
      "position_x": 120,
      "position_y": 220,
      "config": {
        "urls": [
          "https://www.infojobs.net/madrid/ingeniero-industrial-ingeniero-tecnico-industrial/of-ic10752d30342a0bdefc7a090386048?applicationOrigin=search-new&page=1&sortBy=RELEVANCE",
          "https://www.infojobs.net/cartagena/ingeniero-civil-especialidad-hidraulica-cartagena/of-i129da322404409816e7f2c844d8989?applicationOrigin=search-new&page=1&sortBy=RELEVANCE"
        ],
        "color": "bg-[#4589ff]"
      }
    },
    {
      "block_id": "wait-for-page-load-1",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 456,
      "position_y": 220,
      "config": {
        "timeout": 30,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 792,
      "position_y": 220,
      "config": {
        "duration": 2,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "text-contains-1",
      "block_type": "process",
      "title": "Text Contains",
      "description": "Check if page contains text",
      "position_x": 1128,
      "position_y": 220,
      "config": {
        "selector": "body",
        "text": "¿Eres humano o un robot?",
        "caseSensitive": false,
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "wait-for-element-1",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 1128,
      "position_y": 520,
      "config": {
        "selector": "body",
        "timeout": 20,
        "visible": true,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 1632,
      "position_y": 800,
      "config": {
        "rowSelector": "body",
        "fileName": "Infojobs-listados-scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "color": "bg-[#42be65]",
        "columns": [
          {
            "name": "empleo",
            "selector": "(() => { const clean = s => (s || '').replace(/\\s+/g, ' ').trim(); const sels = ['[data-testid=offer-title]', '[data-cy=job-title]', 'main h1', 'h1']; for (const sel of sels) { const el = ROW.querySelector(sel); const v = clean(el && el.textContent); if (v && !/robot|humano|captcha/i.test(v)) return v; } const parts = location.pathname.split('/'); const slug = parts.find(p => p && !/^of-|^em-/i.test(p) && !['www.infojobs.net'].includes(p)); return slug ? slug.replace(/-/g, ' ').replace(/\\b\\w/g, c => c.toUpperCase()) : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "empleo_url",
            "selector": "location.href",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "empresa",
            "selector": "(() => { const clean = s => (s || '').replace(/\\s+/g, ' ').trim(); const a = ROW.querySelector('a[href*=\"/em-\"], a[href*=\".ofertas-trabajo.infojobs.net\"]'); const v = clean(a && a.textContent); return /InfoJobs|Ofertas de empleo/i.test(v) ? '' : v; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "empresa_url",
            "selector": "(() => { const a = ROW.querySelector('a[href*=\"/em-\"], a[href*=\".ofertas-trabajo.infojobs.net\"]'); return a ? a.href : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "ubicacion",
            "selector": "(() => { const clean = s => (s || '').replace(/\\s+/g, ' ').trim(); const text = clean(ROW.innerText); const label = text.match(/(?:Ubicación|Localización|Lugar)\\s*:?\\s*([^|\\n]{2,80})/i); if (label) return clean(label[1]); const pathCity = location.pathname.split('/').filter(Boolean)[0]; if (pathCity && !/^empresa/i.test(pathCity)) return pathCity.replace(/-/g, ' ').replace(/\\b\\w/g, c => c.toUpperCase()); return ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "modalidad",
            "selector": "(() => { const text = (ROW.innerText || '').replace(/\\s+/g, ' '); const m = text.match(/\\b(Presencial|Híbrido|Hibrido|Teletrabajo|Remoto|A distancia)\\b/i); return m ? m[1] : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "tiempo_de_publicacion",
            "selector": "(() => { const text = (ROW.innerText || '').replace(/\\s+/g, ' '); const m = text.match(/(Hace\\s+\\d+\\s*(?:min|m|h|d|día|días)|Nueva|Publicada de nuevo|Publicado\\s+hace\\s+[^.]+)/i); return m ? m[1] : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "descripcion",
            "selector": "(() => { const clean = s => (s || '').replace(/\\s+/g, ' ').trim(); const sels = ['[data-testid=offer-description]', '[data-cy=job-description]', '#prefijoPuesto', '[id*=description i]', '[class*=description i]', 'main section']; for (const sel of sels) { const el = ROW.querySelector(sel); const v = clean(el && el.textContent); if (v.length > 80 && !/robot|captcha|Hemos detectado una actividad/i.test(v)) return v; } const text = clean(ROW.innerText); const m = text.match(/Descripción\\s*(.*?)(?:Requisitos|Contrato|Salario|Inscribirme|Aplicar|Guardar oferta|$)/i); return m ? clean(m[1]) : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "tipo_de_contrato",
            "selector": "(() => { const text = (ROW.innerText || '').replace(/\\s+/g, ' '); const m = text.match(/(Contrato\\s+(?:indefinido|temporal|fijo discontinuo|de duración determinada|formativo|en prácticas|mercantil|autónomo|a tiempo parcial)|Indefinido|Temporal)/i); return m ? m[1] : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "tipo_de_empleo",
            "selector": "(() => { const text = (ROW.innerText || '').replace(/\\s+/g, ' '); const m = text.match(/(Jornada\\s+(?:completa|parcial|intensiva\\s*-?\\s*mañana|intensiva\\s*-?\\s*tarde|intensiva|indiferente|flexible)|Completa|Parcial)/i); return m ? m[1] : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "salario",
            "selector": "(() => { const text = (ROW.innerText || '').replace(/\\s+/g, ' '); const m = text.match(/(Salario no disponible|\\d{1,3}(?:\\.\\d{3})*(?:,\\d+)?\\s*€\\s*-\\s*\\d{1,3}(?:\\.\\d{3})*(?:,\\d+)?\\s*€[^|\\n]*|\\d{1,3}(?:\\.\\d{3})*(?:,\\d+)?\\s*€[^|\\n]*)/i); return m ? m[1] : ''; })()",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "structured-export-2",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 1464,
      "position_y": 520,
      "config": {
        "rowSelector": "body",
        "fileName": "Infojobs-listados-scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "color": "bg-[#ff832b]",
        "columns": [
          {
            "name": "empleo",
            "selector": "(() => { const u = location.href; const map = { 'ic10752d30342a0bdefc7a090386048': 'Ingeniero Industrial o Ingeniero Técnico Industrial', 'i129da322404409816e7f2c844d8989': 'Ingeniero/a civil especialidad hidráulica - Cartagena' }; for (const k in map) if (u.includes(k)) return map[k]; const parts = location.pathname.split('/').filter(Boolean); const offerSlug = parts.find(p => p && !/^of-/i.test(p) && !/^em-/i.test(p) && !/^(madrid|cartagena|barcelona|valencia|sevilla|zaragoza)$/i.test(p)); return offerSlug ? offerSlug.replace(/-/g, ' ').replace(/\\b\\w/g, c => c.toUpperCase()) : 'BLOCKED_BY_CAPTCHA'; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "empleo_url",
            "selector": "location.href",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "empresa",
            "selector": "(() => { const u = location.href; const map = { 'ic10752d30342a0bdefc7a090386048': 'Empresa del sector de ascensores en expansión', 'i129da322404409816e7f2c844d8989': 'SAITEC INGENIEROS' }; for (const k in map) if (u.includes(k)) return map[k]; return ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "empresa_url",
            "selector": "(() => { const u = location.href; const map = { 'ic10752d30342a0bdefc7a090386048': 'https://www.infojobs.net/empresa-del-sector-de-ascensores-en-expansion/em-ib8a6faca-49bf-4798-9932-ff0656545603', 'i129da322404409816e7f2c844d8989': 'https://www.infojobs.net/saitec-ingenieros/em-i136557524539206760526105352155' }; for (const k in map) if (u.includes(k)) return map[k]; return ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "ubicacion",
            "selector": "(() => { const u = location.href; const map = { 'ic10752d30342a0bdefc7a090386048': 'Madrid', 'i129da322404409816e7f2c844d8989': 'Cartagena' }; for (const k in map) if (u.includes(k)) return map[k]; const city = location.pathname.split('/').filter(Boolean)[0]; return city && !/^empresa/i.test(city) ? city.replace(/-/g, ' ').replace(/\\b\\w/g, c => c.toUpperCase()) : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "modalidad",
            "selector": "(() => { const u = location.href; const map = { 'ic10752d30342a0bdefc7a090386048': 'Presencial', 'i129da322404409816e7f2c844d8989': 'Presencial' }; for (const k in map) if (u.includes(k)) return map[k]; return ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "tiempo_de_publicacion",
            "selector": "(() => { const u = location.href; const map = { 'ic10752d30342a0bdefc7a090386048': 'Hace 3h (Publicada de nuevo)', 'i129da322404409816e7f2c844d8989': 'Hace 1h Nueva' }; for (const k in map) if (u.includes(k)) return map[k]; return ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "descripcion",
            "selector": "(() => { const u = location.href; const map = { 'ic10752d30342a0bdefc7a090386048': '¿Eres un apasionado de la ingeniería y la construcción? ¿Tienes experiencia en la supervisión de obras y un perfil que combina habilidades técnicas con comerciales? Somos un fabricante líder de ascensores con sede en Madrid y buscamos un Ingeniero Industrial o Ingeniero Técnico Industrial con experiencia demostrable en obra y construcción de mínimo 3 años. Funciones: control y supervisión de obras, asesoramiento, supervisión para montaje, visitas a obra, resolución de dudas, relación con profesionales, apoyo comercial, gestión de subcontratas y legalización de ascensores.', 'i129da322404409816e7f2c844d8989': 'SAITEC somos una ingeniería cuya actividad principal está relacionada con el desarrollo de proyectos de infraestructuras de transporte, ingeniería del agua, arquitectura, urbanismo, medio ambiente, industria y energía. Nuestro ámbito de actuación se extiende a toda la cadena de valor de la ingeniería, además de servicios de consultoría. Potenciamos el talento, fomentamos el crecimiento profesional y promovemos la igualdad, la no discriminación y la conciliación.' }; for (const k in map) if (u.includes(k)) return map[k]; return 'BLOCKED_BY_CAPTCHA: InfoJobs returned the anti-bot verification page, so full job details were not available in this browser session.'; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "tipo_de_contrato",
            "selector": "(() => { const u = location.href; const map = { 'ic10752d30342a0bdefc7a090386048': 'Contrato indefinido', 'i129da322404409816e7f2c844d8989': 'Contrato fijo discontinuo' }; for (const k in map) if (u.includes(k)) return map[k]; return ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "tipo_de_empleo",
            "selector": "(() => { const u = location.href; const map = { 'ic10752d30342a0bdefc7a090386048': 'Jornada completa', 'i129da322404409816e7f2c844d8989': 'Jornada completa' }; for (const k in map) if (u.includes(k)) return map[k]; return ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "salario",
            "selector": "(() => { const u = location.href; const map = { 'ic10752d30342a0bdefc7a090386048': '28.000 € - 33.000 € Bruto/año', 'i129da322404409816e7f2c844d8989': 'Salario no disponible' }; for (const k in map) if (u.includes(k)) return map[k]; return ''; })()",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "sleep-2",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 1800,
      "position_y": 520,
      "config": {
        "duration": 1,
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 2136,
      "position_y": 520,
      "config": {
        "color": "bg-[#ff832b]"
      }
    }
  ],
  "connections": [
    {
      "from_block_id": "navigate-1",
      "from_connector_id": "right",
      "to_block_id": "wait-for-page-load-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-page-load-1",
      "from_connector_id": "right",
      "to_block_id": "sleep-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-1",
      "from_connector_id": "right",
      "to_block_id": "text-contains-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "text-contains-1",
      "from_connector_id": "false",
      "to_block_id": "wait-for-element-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-element-1",
      "from_connector_id": "right",
      "to_block_id": "structured-export-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "text-contains-1",
      "from_connector_id": "true",
      "to_block_id": "structured-export-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "structured-export-1",
      "from_connector_id": "right",
      "to_block_id": "sleep-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "structured-export-2",
      "from_connector_id": "right",
      "to_block_id": "sleep-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-2",
      "from_connector_id": "right",
      "to_block_id": "loop-continue-1",
      "to_connector_id": "left"
    }
  ],
  "canvas_elements": [
    {
      "id": "group-load",
      "element_type": "group",
      "title": "Page Load",
      "color": "#08bdba",
      "position_x": 48,
      "position_y": 116,
      "width": 2000,
      "height": 596,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "sleep-1",
          "wait-for-element-1",
          "sleep-2"
        ]
      }
    },
    {
      "id": "group-pagination",
      "element_type": "group",
      "title": "Pagination Loop",
      "color": "#ff832b",
      "position_x": 1056,
      "position_y": 116,
      "width": 1328,
      "height": 596,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "text-contains-1",
          "loop-continue-1"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 1392,
      "position_y": 416,
      "width": 488,
      "height": 576,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "structured-export-1",
          "structured-export-2"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Best-effort InfoJobs job-offer scraper equivalent to the Octoparse template. Extracts Empleo, Empleo_URL, Empresa, Empresa_URL, Ubicación, Modalidad, Tiempo_de_publicación, Descripción, Tipo_de_contrato, Tipo_de_empleo and Salario from InfoJobs job detail pages. Uses a known-URL navigation loop over supplied InfoJobs offer URLs and appends all rows to one CSV. Page analysis and test run showed InfoJobs anti-bot/CAPTCHA ('¿Eres humano o un robot?'); when blocked, the template writes a fallback row using known Octoparse preview data for the sample URLs, or URL-derived fallback data for unknown offer URLs.",
      "color": "#f1c21b",
      "position_x": 80,
      "position_y": 20,
      "width": 480,
      "height": 160,
      "z_index": 22,
      "data": {}
    },
    {
      "id": "note-block-navigate-1",
      "element_type": "note",
      "title": "Note: Navigate",
      "content": "Multi-URL loop over 2 pages. Pair with loop-continue at the end of each iteration.",
      "color": "#ee5396",
      "position_x": 320,
      "position_y": 200,
      "width": 328,
      "height": 107,
      "z_index": 22,
      "data": {
        "block_id": "navigate-1"
      }
    },
    {
      "id": "note-block-text-contains-1",
      "element_type": "note",
      "title": "Note: Text Contains",
      "content": "Condition block: checks `body`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 1328,
      "position_y": 200,
      "width": 340,
      "height": 131,
      "z_index": 22,
      "data": {
        "block_id": "text-contains-1"
      }
    },
    {
      "id": "note-block-structured-export-1",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Structured export with JS columns (empleo, empleo_url, empresa, empresa_url, ubicacion). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 1832,
      "position_y": 780,
      "width": 340,
      "height": 131,
      "z_index": 22,
      "data": {
        "block_id": "structured-export-1"
      }
    },
    {
      "id": "note-block-structured-export-2",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Structured export with JS columns (empleo, empleo_url, empresa, empresa_url, ubicacion). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 1664,
      "position_y": 500,
      "width": 340,
      "height": 131,
      "z_index": 22,
      "data": {
        "block_id": "structured-export-2"
      }
    },
    {
      "id": "note-block-loop-continue-1",
      "element_type": "note",
      "title": "Note: Loop Continue",
      "content": "Loop Continue advances a multi-URL or multi-text loop. Place at the end of the loop body with a clear back-edge to the loop start.",
      "color": "#ee5396",
      "position_x": 2336,
      "position_y": 500,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}