AutomationFlowsWeb Scraping › Szybkie Kursiki - Course Generator (simple)

Szybkie Kursiki - Course Generator (simple)

Szybkie Kursiki - Course Generator (Simple). Uses httpRequest. Event-driven trigger; 7 nodes.

Event trigger★★★★☆ complexity7 nodesHTTP Request
Web Scraping 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": "Szybkie Kursiki - Course Generator (Simple)",
  "nodes": [
    {
      "parameters": {},
      "id": "manual-trigger",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        250,
        300
      ]
    },
    {
      "parameters": {
        "url": "https://api.anthropic.com/v1/messages",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "anthropicApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({\n  model: 'claude-sonnet-4-5-20250929',\n  max_tokens: 32000,\n  messages: [\n    {\n      role: 'user',\n      content: $json.coursePrompt\n    }\n  ],\n  system: \"Jeste\u015b ekspertem w tworzeniu kurs\u00f3w edukacyjnych online. Twoim zadaniem jest wygenerowanie kompletnej struktury kursu w formacie JSON.\\n\\nKurs musi zawiera\u0107:\\n1. Podstawowe informacje o kursie (tytu\u0142, kr\u00f3tki opis, pe\u0142ny opis, ikona Font Awesome, tagi)\\n2. DOK\u0141ADNIE 3 lekcje (nie wi\u0119cej, nie mniej)\\n3. Ka\u017cda lekcja MUSI zawiera\u0107:\\n   - Tytu\u0142\\n   - Kolejno\u015b\u0107 (order)\\n   - Tre\u015b\u0107 w formacie Markdown (content_markdown) - szczeg\u00f3\u0142owa, merytoryczna tre\u015b\u0107 lekcji z przyk\u0142adami kodu\\n   - Quiz z 3-5 pytaniami wielokrotnego wyboru\\n   - Zadanie praktyczne (practical_task) z przyk\u0142adami i rozwi\u0105zaniem\\n\\nFormat JSON:\\n```json\\n{\\n  \\\"course\\\": {\\n    \\\"title\\\": \\\"Nazwa kursu\\\",\\n    \\\"short_description\\\": \\\"Kr\u00f3tki opis kursu (max 200 znak\u00f3w)\\\",\\n    \\\"description\\\": \\\"Pe\u0142ny opis kursu - czego si\u0119 nauczysz, dla kogo jest kurs\\\",\\n    \\\"icon\\\": \\\"fas fa-code\\\",\\n    \\\"tags\\\": [\\\"Python\\\", \\\"Backend\\\", \\\"API\\\"]\\n  },\\n  \\\"lessons\\\": [\\n    {\\n      \\\"title\\\": \\\"Tytu\u0142 lekcji\\\",\\n      \\\"order\\\": 0,\\n      \\\"content_markdown\\\": \\\"# Wprowadzenie\\\\n\\\\nTre\u015b\u0107 lekcji...\\\",\\n      \\\"quiz\\\": {\\n        \\\"title\\\": \\\"Quiz - Tytu\u0142 lekcji\\\",\\n        \\\"description\\\": \\\"Sprawd\u017a swoj\u0105 wiedz\u0119\\\",\\n        \\\"questions\\\": [\\n          {\\n            \\\"text\\\": \\\"Tre\u015b\u0107 pytania?\\\",\\n            \\\"order\\\": 0,\\n            \\\"explanation\\\": \\\"Wyja\u015bnienie poprawnej odpowiedzi\\\",\\n            \\\"answers\\\": [\\n              {\\n                \\\"text\\\": \\\"Odpowied\u017a 1\\\",\\n                \\\"is_correct\\\": true,\\n                \\\"order\\\": 0\\n              },\\n              {\\n                \\\"text\\\": \\\"Odpowied\u017a 2\\\",\\n                \\\"is_correct\\\": false,\\n                \\\"order\\\": 1\\n              }\\n            ]\\n          }\\n        ]\\n      },\\n      \\\"practical_task\\\": {\\n        \\\"title\\\": \\\"Zadanie praktyczne - Tytu\u0142\\\",\\n        \\\"content_markdown\\\": \\\"# Zadanie\\\\n\\\\nOpis zadania do wykonania...\\\",\\n        \\\"instructions_markdown\\\": \\\"## Instrukcje\\\\n\\\\n1. Krok 1\\\\n2. Krok 2\\\",\\n        \\\"example_markdown\\\": \\\"## Przyk\u0142ad\\\\n\\\\n```python\\\\nprzyk\u0142adowy_kod()\\\\n```\\\",\\n        \\\"hints_markdown\\\": \\\"## Wskaz\u00f3wki\\\\n\\\\n- Wskaz\u00f3wka 1\\\\n- Wskaz\u00f3wka 2\\\",\\n        \\\"solution_markdown\\\": \\\"## Rozwi\u0105zanie\\\\n\\\\n```python\\\\nrozwiazanie_zadania()\\\\n```\\\\n\\\\nWyja\u015bnienie...\\\"\\n      }\\n    }\\n  ]\\n}\\n```\\n\\nKRYTYCZNE WYMAGANIA JSON:\\n- Wszystkie cudzys\u0142owy w tre\u015bci markdown MUSZ\u0104 zosta\u0107 poprawnie escape'owane\\n- Znaki nowej linii w tre\u015bci markdown zapisuj jako \\\\n (podw\u00f3jny backslash + n)\\n- Upewnij si\u0119, \u017ce ka\u017cdy otwarty cudzys\u0142\u00f3w ma swoje zamkni\u0119cie\\n- Nie u\u017cywaj pojedynczych backslash przed cudzys\u0142owami w warto\u015bciach string\\n- Zweryfikuj, \u017ce JSON jest poprawnie sformatowany przed zwr\u00f3ceniem\\n\\nWA\u017bNE:\\n- Stw\u00f3rz DOK\u0141ADNIE 3 lekcje (nie wi\u0119cej, nie mniej)\\n- Tre\u015b\u0107 lekcji (content_markdown) powinna by\u0107 zwi\u0119z\u0142a ale merytoryczna (200-400 s\u0142\u00f3w)\\n- U\u017cywaj prawid\u0142owego formatowania Markdown\\n- Bloki kodu musz\u0105 mie\u0107 okre\u015blony j\u0119zyk (```python, ```javascript, itp.)\\n- Ka\u017cde pytanie w quizie musi mie\u0107 co najmniej 4 odpowiedzi\\n- Tylko jedna odpowied\u017a mo\u017ce by\u0107 poprawna (is_correct: true)\\n- W quizie dodaj 3-4 pytania (nie wi\u0119cej)\\n- Zadania praktyczne musz\u0105 by\u0107 konkretne ale zwi\u0119z\u0142e\\n- U\u017cywaj polskich znak\u00f3w (\u0105, \u0107, \u0119, \u0142, \u0144, \u00f3, \u015b, \u017a, \u017c)\\n\\nODPOWIED\u0179 WY\u0141\u0104CZNIE POPRAWNYM, ZWALIDOWANYM JSON-em w bloku ```json, bez dodatkowych komentarzy ani wyja\u015bnie\u0144.\"\n}) }}",
        "options": {}
      },
      "id": "anthropic-request",
      "name": "Call Claude API",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [
        450,
        300
      ],
      "credentials": {
        "anthropicApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Extract JSON from Claude's response\nconst response = $input.first().json;\n\n// Get text from content array\nlet text = '';\nif (response.content && Array.isArray(response.content)) {\n  text = response.content[0].text;\n} else {\n  throw new Error('No content in Claude response');\n}\n\nconsole.log('Raw Claude response length:', text.length);\nconsole.log('First 300 chars:', text.substring(0, 300));\n\n// Function to repair common JSON issues\nfunction repairJSON(jsonStr) {\n  // Remove any text before the first { or [\n  const start = jsonStr.search(/[{\\[]/);\n  if (start > 0) {\n    jsonStr = jsonStr.substring(start);\n  }\n  \n  // Remove any text after the last } or ]\n  const end = jsonStr.lastIndexOf('}') > jsonStr.lastIndexOf(']') \n    ? jsonStr.lastIndexOf('}') + 1 \n    : jsonStr.lastIndexOf(']') + 1;\n  if (end > 0) {\n    jsonStr = jsonStr.substring(0, end);\n  }\n  \n  return jsonStr;\n}\n\n// Try to parse the response\nlet courseData;\nlet jsonText = '';\n\ntry {\n  // Check if response contains markdown code blocks\n  const jsonMatch = text.match(/```(?:json)?\\s*([\\s\\S]*?)```/);\n  \n  if (jsonMatch) {\n    // Extract JSON from code block\n    jsonText = jsonMatch[1].trim();\n    console.log('Extracted JSON from code block, length:', jsonText.length);\n  } else {\n    // Try to parse directly\n    jsonText = text.trim();\n    console.log('Using raw text as JSON');\n  }\n  \n  // Try to repair common JSON issues\n  jsonText = repairJSON(jsonText);\n  \n  // Attempt to parse\n  try {\n    courseData = JSON.parse(jsonText);\n    console.log('Successfully parsed JSON');\n  } catch (parseError) {\n    console.error('Initial parse failed:', parseError.message);\n    \n    // Log context around the error\n    if (parseError.message.includes('position')) {\n      const match = parseError.message.match(/position (\\d+)/);\n      if (match) {\n        const pos = parseInt(match[1]);\n        const start = Math.max(0, pos - 100);\n        const end = Math.min(jsonText.length, pos + 100);\n        console.error('\\n=== Context around error position ===');\n        console.error('Position:', pos);\n        console.error('Context:', jsonText.substring(start, end));\n        console.error('Character at position:', jsonText[pos]);\n        console.error('=====================================\\n');\n      }\n    }\n    \n    // Try one more time with more aggressive cleaning\n    console.log('Attempting aggressive JSON repair...');\n    let cleaned = jsonText\n      .replace(/\\\\n/g, '\\\\\\\\n')  // Double escape newlines\n      .replace(/([^\\\\])\"([^\":,\\]}\\s])/g, '$1\\\\\"$2');  // Escape unescaped quotes\n    \n    try {\n      courseData = JSON.parse(cleaned);\n      console.log('Successfully parsed with aggressive repair');\n    } catch (finalError) {\n      // Save the problematic JSON to help debug\n      console.error('\\n=== FULL PROBLEMATIC JSON ===');\n      console.error(jsonText);\n      console.error('=============================\\n');\n      throw new Error(`JSON parsing failed after repair attempts: ${parseError.message}`);\n    }\n  }\n  \n  // Validate required fields\n  if (!courseData.course || !courseData.course.title) {\n    throw new Error('Missing course title');\n  }\n  \n  if (!courseData.lessons || courseData.lessons.length === 0) {\n    throw new Error('No lessons provided');\n  }\n  \n  // Ensure all lessons have required fields\n  courseData.lessons.forEach((lesson, index) => {\n    if (!lesson.title) {\n      throw new Error(`Lesson ${index} missing title`);\n    }\n    if (lesson.order === undefined) {\n      lesson.order = index;\n    }\n    if (!lesson.content_markdown) {\n      throw new Error(`Lesson ${index} missing content_markdown`);\n    }\n  });\n  \n  console.log('Successfully validated course data');\n  console.log('Course:', courseData.course.title);\n  console.log('Lessons:', courseData.lessons.length);\n  \n  return {\n    json: courseData\n  };\n  \n} catch (error) {\n  throw new Error(`Failed to parse Claude response: ${error.message}\\n\\nCheck execution logs for full details.`);\n}"
      },
      "id": "code-parser",
      "name": "Parse Claude Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        650,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.DJANGO_APP_URL.replace(/\\/$/, '') }}/api/import-course/",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Import-Token",
              "value": "={{ $env.COURSE_IMPORT_TOKEN }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json) }}",
        "options": {}
      },
      "id": "http-import",
      "name": "Import Course to Django",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [
        850,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.status }}",
              "operation": "equals",
              "value2": "ok"
            }
          ]
        }
      },
      "id": "check-success",
      "name": "Check Import Success",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        1050,
        300
      ]
    },
    {
      "parameters": {
        "values": {
          "string": [
            {
              "name": "status",
              "value": "success"
            },
            {
              "name": "message",
              "value": "Kurs zosta\u0142 pomy\u015blnie utworzony!"
            },
            {
              "name": "course_title",
              "value": "={{ $json.course_title }}"
            },
            {
              "name": "course_slug",
              "value": "={{ $json.course_slug }}"
            },
            {
              "name": "lessons_count",
              "value": "={{ $json.lessons_count }}"
            },
            {
              "name": "admin_url",
              "value": "={{ $env.DJANGO_APP_URL }}{{ $json.admin_url }}"
            },
            {
              "name": "preview_url",
              "value": "={{ $env.DJANGO_APP_URL }}/course/{{ $json.course_slug }}/"
            }
          ]
        },
        "options": {}
      },
      "id": "success-output",
      "name": "Success Output",
      "type": "n8n-nodes-base.set",
      "typeVersion": 2,
      "position": [
        1250,
        200
      ]
    },
    {
      "parameters": {
        "values": {
          "string": [
            {
              "name": "status",
              "value": "error"
            },
            {
              "name": "message",
              "value": "Nie uda\u0142o si\u0119 zaimportowa\u0107 kursu"
            },
            {
              "name": "error",
              "value": "={{ $json.error || 'Unknown error' }}"
            }
          ]
        },
        "options": {}
      },
      "id": "error-output",
      "name": "Error Output",
      "type": "n8n-nodes-base.set",
      "typeVersion": 2,
      "position": [
        1250,
        400
      ]
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Call Claude API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Call Claude API": {
      "main": [
        [
          {
            "node": "Parse Claude Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Claude Response": {
      "main": [
        [
          {
            "node": "Import Course to Django",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Import Course to Django": {
      "main": [
        [
          {
            "node": "Check Import Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Import Success": {
      "main": [
        [
          {
            "node": "Success Output",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "tags": [],
  "triggerCount": 1,
  "updatedAt": "2026-02-02T00:00:00.000Z",
  "versionId": "1"
}

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

Szybkie Kursiki - Course Generator (Simple). Uses httpRequest. Event-driven trigger; 7 nodes.

Source: https://github.com/Sul3j/szybkie-kursiki/blob/7a41c67048c38e3d720c52f72553d5ea0cd49935/n8n-workflows/course-generator-simple.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 workflow allows you to import any workflow from a file or another n8n instance and map the credentials easily. A multi-form setup guides you through the entire process At the beginning you have t

Execute Command, Read Write File, HTTP Request +3
Web Scraping

[n8n] Advanced URL Parsing and Shortening Workflow - Switchy.io Integration. Uses splitInBatches, stickyNote, httpRequest, html. Event-driven trigger; 56 nodes.

HTTP Request, GitHub, Stop And Error +1
Web Scraping

[](https://youtu.be/c7yCZhmMjtI)

HTTP Request, GitHub, Stop And Error +1
Web Scraping

This automation organizes your n8n workflows files into categorizes (Active, Template, Done, Archived) and uploads them directly to a categorized Google Drive folders. It is designed to help users man

Google Drive, HTTP Request, Time Saved
Web Scraping

Create Animated Stories using GPT-4o-mini, Midjourney, Kling and Creatomate API. Uses httpRequest. Event-driven trigger; 51 nodes.

HTTP Request