AutomationFlowsData & Sheets › Saleroom Lot Scraper

Saleroom Lot Scraper

Saleroom Lot Scraper. Uses notionTrigger, httpRequest. Event-driven trigger; 7 nodes.

Event trigger★★★★☆ complexity7 nodesNotion TriggerHTTP Request
Data & Sheets Trigger: Event Nodes: 7 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": "Saleroom Lot Scraper",
  "nodes": [
    {
      "parameters": {
        "event": "pageUpdatedInDatabase",
        "databaseId": {
          "__rl": true,
          "value": "REPLACE_WITH_YOUR_DATABASE_ID",
          "mode": "id"
        },
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "simple": true,
        "options": {}
      },
      "id": "a1000000-0000-0000-0000-000000000001",
      "name": "Notion Trigger",
      "type": "n8n-nodes-base.notionTrigger",
      "typeVersion": 1,
      "position": [
        0,
        0
      ],
      "credentials": {
        "notionApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "leftValue": "={{ $json['Lot URL'] }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              }
            },
            {
              "leftValue": "={{ $json.Status }}",
              "rightValue": "Done",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              }
            },
            {
              "leftValue": "={{ $json.Status }}",
              "rightValue": "Error",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "a1000000-0000-0000-0000-000000000002",
      "name": "Needs Scraping?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        220,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://REPLACE-WITH-YOUR-SCRAPER.onrender.com/scrape",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-API-Key",
              "value": "REPLACE_WITH_YOUR_SCRAPER_API_KEY"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ url: $json['Lot URL'] }) }}",
        "options": {
          "timeout": 300000
        }
      },
      "id": "a1000000-0000-0000-0000-000000000003",
      "name": "Scrape Lot",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        440,
        0
      ],
      "retryOnFail": true,
      "maxTries": 2,
      "waitBetweenTries": 5000,
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "jsCode": "// \"Build Notion Payloads\" Code node (n8n, Run Once for All Items).\n// Input: the Scrape Lot response. Output: one item PER BLOCK BATCH (Notion\n// caps block appends at 100/request), each carrying the same pageId and\n// propertiesPayload. The \"Update Properties\" node must have Settings ->\n// \"Execute Once\" = ON; \"Write Page Body\" runs once per item (per batch).\n\nconst scraped = $input.first().json;\nconst pageId = $('Notion Trigger').first().json.id;\n\n// Notion caps each rich_text item at 2000 chars \u2014 chunk long text.\nconst chunk = (s, size = 1900) => {\n  const out = [];\n  for (let i = 0; i < s.length; i += size) out.push(s.slice(i, i + size));\n  return out;\n};\nconst rt = (s) => ({ type: 'text', text: { content: s } });\n\n// Inline markdown: supports **bold** only (enough for the report format).\nconst rich = (s) => {\n  const parts = [];\n  s.split('**').forEach((seg, i) => {\n    if (!seg) return;\n    for (const piece of chunk(seg)) {\n      const item = { type: 'text', text: { content: piece } };\n      if (i % 2 === 1) item.annotations = { bold: true };\n      parts.push(item);\n    }\n  });\n  return parts.length ? parts : [rt(' ')];\n};\n\nconst paragraphs = (s) => chunk(s).map((c) => ({\n  object: 'block', type: 'paragraph', paragraph: { rich_text: [rt(c)] },\n}));\nconst divider = () => ({ object: 'block', type: 'divider', divider: {} });\nconst heading = (level, s) => ({\n  object: 'block', type: `heading_${level}`, [`heading_${level}`]: { rich_text: rich(s) },\n});\n\n// Line-based markdown -> Notion blocks for the AI report.\nconst mdToBlocks = (md) => {\n  const blocks = [];\n  for (const raw of md.split('\\n')) {\n    const line = raw.trim();\n    if (!line) continue;\n    if (/^-{3,}$/.test(line)) blocks.push(divider());\n    else if (line.startsWith('### ')) blocks.push(heading(3, line.slice(4)));\n    else if (line.startsWith('## ')) blocks.push(heading(2, line.slice(3)));\n    else if (line.startsWith('# ')) blocks.push(heading(1, line.slice(2)));\n    else if (/^[-\u2022*] /.test(line)) blocks.push({\n      object: 'block', type: 'bulleted_list_item',\n      bulleted_list_item: { rich_text: rich(line.slice(2)) },\n    });\n    else if (line.startsWith('> ')) blocks.push({\n      object: 'block', type: 'quote', quote: { rich_text: rich(line.slice(2)) },\n    });\n    else blocks.push({\n      object: 'block', type: 'paragraph', paragraph: { rich_text: rich(line) },\n    });\n  }\n  return blocks;\n};\n\n// ---------- Database properties ----------\nconst properties = {\n  Name: { title: [rt(scraped.title || `Lot ${scraped.lotNumber || '?'} \u2014 ${scraped.auctionHouse || 'Unknown'}`)] },\n  Status: { select: { name: 'Done' } },\n};\nif (scraped.lotNumber) {\n  properties['Lot Number'] = { rich_text: [rt(String(scraped.lotNumber))] };\n}\nif (scraped.auctionHouse) {\n  // Select option names cannot contain commas\n  properties['Auction House'] = { select: { name: scraped.auctionHouse.replace(/,/g, ' ').slice(0, 100) } };\n}\nif (/^\\d{4}-\\d{2}-\\d{2}$/.test(scraped.auctionDate || '')) {\n  properties['Auction Date'] = { date: { start: scraped.auctionDate } };\n}\nif (scraped.imageUrl) {\n  properties['Photo'] = { files: [{ type: 'external', name: 'Lot photo', external: { url: scraped.imageUrl } }] };\n}\n\n// ---------- Page body blocks ----------\nconst blocks = [];\n\nblocks.push({\n  object: 'block', type: 'callout',\n  callout: {\n    icon: { type: 'emoji', emoji: '\ud83d\udd28' },\n    color: 'gray_background',\n    rich_text: [rt([\n      `Lot ${scraped.lotNumber || '\u2014'}`,\n      scraped.auctionHouse,\n      scraped.auctionDate,\n    ].filter(Boolean).join('   \u2022   '))],\n  },\n});\nblocks.push(divider());\n\n// --- AI buy-analysis report (the main dossier) ---\nif (scraped.aiReport) {\n  blocks.push(heading(1, '\ud83e\udd16 AI Buy Analysis'));\n  blocks.push(...mdToBlocks(scraped.aiReport));\n  blocks.push(divider());\n} else if (scraped.aiReportError) {\n  blocks.push({\n    object: 'block', type: 'callout',\n    callout: { icon: { type: 'emoji', emoji: '\u26a0\ufe0f' }, color: 'red_background', rich_text: [rt(scraped.aiReportError)] },\n  });\n  blocks.push(divider());\n}\n\n// --- Photos ---\nconst gallery = (scraped.imageUrls && scraped.imageUrls.length ? scraped.imageUrls : [scraped.imageUrl])\n  .filter(Boolean).slice(0, 4);\nif (gallery.length) {\n  blocks.push(heading(2, '\ud83d\uddbc\ufe0f Lot Photos'));\n  for (const url of gallery) {\n    blocks.push({ object: 'block', type: 'image', image: { type: 'external', external: { url } } });\n  }\n  blocks.push(divider());\n}\n\n// --- Original catalogue text ---\nif (scraped.description) {\n  blocks.push(heading(2, '\ud83d\udcdd Catalogue Description'));\n  blocks.push(...paragraphs(scraped.description));\n}\nblocks.push(heading(2, '\ud83d\udd0d Published Condition Text'));\nif (scraped.condition) {\n  const parts = chunk(scraped.condition);\n  blocks.push({\n    object: 'block', type: 'callout',\n    callout: { icon: { type: 'emoji', emoji: '\u26a0\ufe0f' }, color: 'yellow_background', rich_text: [rt(parts[0])] },\n  });\n  for (const extra of parts.slice(1)) {\n    blocks.push({ object: 'block', type: 'paragraph', paragraph: { rich_text: [rt(extra)] } });\n  }\n} else {\n  blocks.push({\n    object: 'block', type: 'callout',\n    callout: { icon: { type: 'emoji', emoji: '\u2139\ufe0f' }, color: 'gray_background', rich_text: [rt('No condition report published for this lot.')] },\n  });\n}\nblocks.push(divider());\nblocks.push({ object: 'block', type: 'bookmark', bookmark: { url: scraped.sourceUrl } });\n\n// ---------- Batch (Notion: max 100 blocks per append request) ----------\nconst BATCH = 90;\nconst items = [];\nfor (let i = 0; i < blocks.length; i += BATCH) {\n  items.push({ json: {\n    pageId,\n    propertiesPayload: { properties },\n    blocksPayload: { children: blocks.slice(i, i + BATCH) },\n  } });\n}\nreturn items;\n"
      },
      "id": "a1000000-0000-0000-0000-000000000004",
      "name": "Build Notion Payloads",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        -80
      ]
    },
    {
      "parameters": {
        "method": "PATCH",
        "url": "=https://api.notion.com/v1/pages/{{ $json.pageId }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "notionApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Notion-Version",
              "value": "2022-06-28"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.propertiesPayload) }}",
        "options": {}
      },
      "id": "a1000000-0000-0000-0000-000000000005",
      "name": "Update Properties",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        880,
        -80
      ],
      "credentials": {
        "notionApi": {
          "name": "<your credential>"
        }
      },
      "executeOnce": true
    },
    {
      "parameters": {
        "method": "PATCH",
        "url": "=https://api.notion.com/v1/blocks/{{ $('Build Notion Payloads').first().json.pageId }}/children",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "notionApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Notion-Version",
              "value": "2022-06-28"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($('Build Notion Payloads').first().json.blocksPayload) }}",
        "options": {}
      },
      "id": "a1000000-0000-0000-0000-000000000006",
      "name": "Write Page Body",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1100,
        -80
      ],
      "credentials": {
        "notionApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "PATCH",
        "url": "=https://api.notion.com/v1/pages/{{ $('Notion Trigger').first().json.id }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "notionApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Notion-Version",
              "value": "2022-06-28"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ properties: { Status: { select: { name: 'Error' } } } }) }}",
        "options": {}
      },
      "id": "a1000000-0000-0000-0000-000000000007",
      "name": "Mark Error",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        660,
        120
      ],
      "credentials": {
        "notionApi": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Notion Trigger": {
      "main": [
        [
          {
            "node": "Needs Scraping?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Needs Scraping?": {
      "main": [
        [
          {
            "node": "Scrape Lot",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Scrape Lot": {
      "main": [
        [
          {
            "node": "Build Notion Payloads",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Mark Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Notion Payloads": {
      "main": [
        [
          {
            "node": "Update Properties",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Properties": {
      "main": [
        [
          {
            "node": "Write Page Body",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "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

Saleroom Lot Scraper. Uses notionTrigger, httpRequest. Event-driven trigger; 7 nodes.

Source: https://github.com/Vitofe666/Auction-Sourcing-Hub/blob/4fa325dc597a1cf900e6e30cb6d3c4c8587a4816/n8n/saleroom_workflow.json — original creator credit. Request a take-down →

More Data & Sheets workflows → · Browse all categories →

Related workflows

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

Data & Sheets

This workflow converts Notion pages to markdown, and then converts that markdown back to Notion blocks. It will triple the content of the last updated page it finds. This is useless by itself, but you

Notion Trigger, Notion, HTTP Request
Data & Sheets

Invoice OCR with NOTION and MINDEE. Uses httpRequest, mindee, notion, notionTrigger. Event-driven trigger; 11 nodes.

HTTP Request, Mindee, Notion +1
Data & Sheets

파이프라인 ① CS 매뉴얼 초안 생성. Uses notionTrigger, httpRequest. Event-driven trigger; 8 nodes.

Notion Trigger, HTTP Request
Data & Sheets

Create images with NOTION and RENDERFORM. Uses notionTrigger, httpRequest, notion. Event-driven trigger; 9 nodes.

Notion Trigger, HTTP Request, Notion
Data & Sheets

AEGIS Pro 무료 체험 자동화. Uses notionTrigger, httpRequest, notion. Event-driven trigger; 5 nodes.

Notion Trigger, HTTP Request, Notion