AutomationFlowsWeb Scraping › Extrator De Imóveis — PDF Para API

Extrator De Imóveis — PDF Para API

Extrator de Imóveis — PDF para API. Uses httpRequest. Webhook trigger; 11 nodes.

Webhook trigger★★★★☆ complexity11 nodesHTTP Request
Web Scraping Trigger: Webhook Nodes: 11 Complexity: ★★★★☆ Added:

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "Extrator de Im\u00f3veis \u2014 PDF para API",
  "nodes": [
    {
      "id": "a1b2c3d4-0001",
      "name": "Webhook \u2014 Receber PDF",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        240,
        300
      ],
      "parameters": {
        "httpMethod": "POST",
        "path": "extrator-imoveis",
        "responseMode": "responseNode",
        "options": {
          "binaryData": true,
          "rawBody": false
        }
      }
    },
    {
      "id": "a1b2c3d4-0002",
      "name": "Extrair Texto do PDF",
      "type": "n8n-nodes-base.extractFromFile",
      "typeVersion": 1,
      "position": [
        460,
        300
      ],
      "parameters": {
        "operation": "pdf",
        "binaryPropertyName": "data",
        "options": {}
      }
    },
    {
      "id": "a1b2c3d4-0003",
      "name": "Preparar Prompt para OpenAI",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        680,
        300
      ],
      "parameters": {
        "jsCode": "const text = $input.first().json.text || '';\n\n// Trunca para n\u00e3o ultrapassar o limite de tokens do modelo\nconst MAX_CHARS = 8000;\nconst truncated = text.slice(0, MAX_CHARS);\n\nif (!truncated.trim()) {\n  throw new Error('PDF sem texto extra\u00edvel. Pode ser um PDF escaneado \u2014 use o fluxo de Vision API para esse caso.');\n}\n\nreturn [{\n  json: {\n    model: 'gpt-4o',\n    response_format: { type: 'json_object' },\n    temperature: 0.1,\n    messages: [\n      {\n        role: 'system',\n        content: 'Voc\u00ea \u00e9 um extrator especializado em documentos imobili\u00e1rios brasileiros. Retorne APENAS JSON v\u00e1lido e puro, sem markdown, sem texto adicional.'\n      },\n      {\n        role: 'user',\n        content: 'Extraia os seguintes dados deste documento imobili\u00e1rio:\\n- titulo: string descritiva do im\u00f3vel (ex: \"Apartamento 3 quartos em Copacabana\")\\n- valor: n\u00famero inteiro em reais SEM pontos ou v\u00edrgulas (ex: 850000)\\n- endereco: endere\u00e7o completo\\n- bairro: nome do bairro\\n- cidade: nome da cidade\\n- quartos: n\u00famero inteiro ou null\\n- suites: n\u00famero inteiro ou null\\n- vagas: n\u00famero inteiro ou null\\n- area_m2: n\u00famero decimal da \u00e1rea \u00fatil ou null\\n- iptu: valor anual do IPTU em reais como n\u00famero inteiro ou null\\n\\nSe o formato do documento for diferente do esperado, identifique os campos pelo contexto sem\u00e2ntico.\\nRetorne APENAS JSON com essas chaves exatas, sem campos extras.\\n\\nDocumento:\\n' + truncated\n      }\n    ]\n  }\n}];"
      }
    },
    {
      "id": "a1b2c3d4-0004",
      "name": "OpenAI \u2014 Extrair Dados Estruturados",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        900,
        300
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.openai.com/v1/chat/completions",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json) }}",
        "options": {
          "retry": {
            "enabled": true,
            "maxTries": 2,
            "waitBetweenTries": 2000
          },
          "timeout": 30000
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "a1b2c3d4-0005",
      "name": "Mapear para IngestPropertyPayload",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1120,
        300
      ],
      "parameters": {
        "jsCode": "// Extrai o conte\u00fado da resposta do OpenAI\nconst choice = $input.first().json.choices?.[0];\nconst content = choice?.message?.content || '{}';\n\nlet extracted;\ntry {\n  extracted = JSON.parse(content);\n} catch (e) {\n  throw new Error('OpenAI retornou JSON inv\u00e1lido: ' + content.slice(0, 200));\n}\n\n// Gera slug \u00fanico a partir do t\u00edtulo\nfunction toSlug(str) {\n  return (str || 'imovel')\n    .toLowerCase()\n    .normalize('NFD')\n    .replace(/[\\u0300-\\u036f]/g, '')\n    .replace(/[^a-z0-9\\s-]/g, '')\n    .trim()\n    .replace(/\\s+/g, '-')\n    .replace(/-+/g, '-')\n    .slice(0, 70) + '-' + Date.now();\n}\n\n// Normaliza valor monet\u00e1rio (ex: \"R$ 1.200.000\" \u2192 1200000)\nfunction normalizePrice(val) {\n  if (val === null || val === undefined) return 0;\n  if (typeof val === 'number') return Math.round(val);\n  const clean = String(val)\n    .replace(/R\\$\\s*/gi, '')\n    .replace(/\\./g, '')\n    .replace(',', '.')\n    .trim();\n  return Math.round(parseFloat(clean)) || 0;\n}\n\n// Normaliza n\u00famero inteiro\nfunction toInt(val) {\n  if (val === null || val === undefined || val === '') return undefined;\n  const n = parseInt(String(val), 10);\n  return isNaN(n) ? undefined : n;\n}\n\n// Normaliza decimal\nfunction toFloat(val) {\n  if (val === null || val === undefined || val === '') return undefined;\n  const n = parseFloat(String(val).replace(',', '.'));\n  return isNaN(n) ? undefined : n;\n}\n\nconst title = (extracted.titulo || extracted.title || 'Im\u00f3vel').trim();\n\nconst payload = {\n  slug: toSlug(title),\n  title,\n  price: normalizePrice(extracted.valor ?? extracted.value ?? extracted.price),\n  address: extracted.endereco || extracted.address || null,\n  neighborhood: extracted.bairro || extracted.neighborhood || null,\n  city: extracted.cidade || extracted.city || null,\n  status: 'disponivel',\n  visibility: 'publico',\n  features: {\n    quartos:  toInt(extracted.quartos),\n    suites:   toInt(extracted.suites),\n    vagas:    toInt(extracted.vagas),\n    area_m2:  toFloat(extracted.area_m2),\n    iptu:     toInt(extracted.iptu),\n  },\n  tags: [],\n};\n\n// Remove chaves undefined dentro de features\nObject.keys(payload.features).forEach(k => {\n  if (payload.features[k] === undefined) delete payload.features[k];\n});\n\nreturn [{ json: payload }];"
      }
    },
    {
      "id": "a1b2c3d4-0006",
      "name": "Validar Campos Obrigat\u00f3rios",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1340,
        300
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "cond_title",
              "leftValue": "={{ $json.title }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEmpty"
              }
            },
            {
              "id": "cond_price",
              "leftValue": "={{ $json.price }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ],
          "combinator": "and"
        }
      }
    },
    {
      "id": "a1b2c3d4-0007",
      "name": "Ingest API \u2014 Salvar Im\u00f3vel",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1560,
        200
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $vars.IMOB_API_URL }}/api/properties/ingest",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $vars.API_INGEST_TOKEN }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json) }}",
        "options": {
          "retry": {
            "enabled": true,
            "maxTries": 3,
            "waitBetweenTries": 3000
          },
          "timeout": 15000
        }
      }
    },
    {
      "id": "a1b2c3d4-0008",
      "name": "Checar Resultado da Ingest\u00e3o",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1780,
        200
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false
          },
          "conditions": [
            {
              "id": "cond_errors",
              "leftValue": "={{ $json.errors }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        }
      }
    },
    {
      "id": "a1b2c3d4-0009",
      "name": "Resposta \u2014 Sucesso",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        2000,
        160
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ ok: true, action: $json.results?.[0]?.action, slug: $json.results?.[0]?.slug, processed: $json.processed }) }}",
        "options": {
          "responseCode": 200
        }
      }
    },
    {
      "id": "a1b2c3d4-0010",
      "name": "Resposta \u2014 Erro na Ingest\u00e3o",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        2000,
        280
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ ok: false, error: 'Falha ao salvar no sistema', details: $json.results }) }}",
        "options": {
          "responseCode": 500
        }
      }
    },
    {
      "id": "a1b2c3d4-0011",
      "name": "Resposta \u2014 Dados Insuficientes",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1560,
        420
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ ok: false, error: 'N\u00e3o foi poss\u00edvel extrair t\u00edtulo ou valor do documento', extracted: $json }) }}",
        "options": {
          "responseCode": 422
        }
      }
    }
  ],
  "connections": {
    "Webhook \u2014 Receber PDF": {
      "main": [
        [
          {
            "node": "Extrair Texto do PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extrair Texto do PDF": {
      "main": [
        [
          {
            "node": "Preparar Prompt para OpenAI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preparar Prompt para OpenAI": {
      "main": [
        [
          {
            "node": "OpenAI \u2014 Extrair Dados Estruturados",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI \u2014 Extrair Dados Estruturados": {
      "main": [
        [
          {
            "node": "Mapear para IngestPropertyPayload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mapear para IngestPropertyPayload": {
      "main": [
        [
          {
            "node": "Validar Campos Obrigat\u00f3rios",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validar Campos Obrigat\u00f3rios": {
      "main": [
        [
          {
            "node": "Ingest API \u2014 Salvar Im\u00f3vel",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Resposta \u2014 Dados Insuficientes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ingest API \u2014 Salvar Im\u00f3vel": {
      "main": [
        [
          {
            "node": "Checar Resultado da Ingest\u00e3o",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Checar Resultado da Ingest\u00e3o": {
      "main": [
        [
          {
            "node": "Resposta \u2014 Sucesso",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Resposta \u2014 Erro na Ingest\u00e3o",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "saveManualExecutions": true,
    "callerPolicy": "workflowsFromSameOwner",
    "errorWorkflow": ""
  },
  "staticData": null,
  "tags": [
    "im\u00f3veis",
    "pdf",
    "ingest"
  ],
  "active": false
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Extrator de Imóveis — PDF para API. Uses httpRequest. Webhook trigger; 11 nodes.

Source: https://github.com/uilliamrd/Imob/blob/0ce742c2a39a4bd34cc4b782517f177ad55153c9/extrator_imoveis.json — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Web Scraping

This n8n template provides enterprise-level version control for your workflows using GitHub integration. Stop losing hours to broken workflows and manual exports – get proper commit history, visual di

n8n, Execute Workflow Trigger, HTTP Request +1
Web Scraping

This flow creates dummy files for every item added in your *Arrs (Radarr/Sonarr) with the tag .

HTTP Request, Ssh
Web Scraping

eek-Go v2 (Batch-Then-Review). Uses httpRequest. Webhook trigger; 75 nodes.

HTTP Request
Web Scraping

This workflow receives webhook requests from a content calendar and uses the X API v2 to publish text posts, threads, image/video posts, and polls, as well as delete existing posts and run a credentia

HTTP Request
Web Scraping

This workflow acts as a central API gateway for all technical indicator agents in the Binance Spot Market Quant AI system. It listens for incoming webhook requests and dynamically routes them to the c

HTTP Request