# Scrap Any Website with Source Code (`mikolabs/web-extractor`) Actor

Download complete websites and get them as ZIP archives. Perfect for creating offline backups, archiving websites, or downloading entire sites with all assets. Includes source code. For Research purposes

- **URL**: https://apify.com/mikolabs/web-extractor.md
- **Developed by:** [mikolabs](https://apify.com/mikolabs) (community)
- **Categories:** Developer tools, Automation, Other
- **Stats:** 8 total users, 0 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2,500.00 / 1,000 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 a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use 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

## Scrap Any Website with Source Code

Download complete websites and get them as ZIP archives. Perfect for creating offline backups, archiving websites, or downloading entire sites with all assets. Includes source code.

### Features

✅ **Complete Website Downloads** - Downloads entire websites with all assets and source code  
✅ **ZIP Archive Output** - Automatically creates compressed ZIP files with full source code  
✅ **Configurable Depth** - Control how deep to follow links (1-10 levels)  
✅ **Rate Limiting** - Respect servers with configurable download rates  
✅ **Domain Filtering** - Stay on same domain or follow external links  
✅ **Content Selection** - Choose to download images, videos, or just HTML/CSS/JS  
✅ **Robots.txt Support** - Optionally respect website's robots.txt  
✅ **Progress Tracking** - Real-time logging of scraping progress  
✅ **Statistics** - File counts, sizes, and compression ratios  

### Input Configuration

#### Required

- **Website URL** - The URL to scrape (must include `http://` or `https://`)

#### Optional

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `depth` | Integer | 2 | How many links deep to follow (1-10) |
| `stayOnDomain` | Boolean | true | Only download from the same domain |
| `externalDepth` | Integer | 0 | How deep to follow external links |
| `connections` | Integer | 4 | Number of simultaneous downloads |
| `maxRate` | Integer | 0 | Max download rate in KB/s (0 = unlimited) |
| `maxSize` | Integer | 0 | Max total size in MB (0 = unlimited) |
| `maxTime` | Integer | 0 | Max scraping time in seconds (0 = unlimited) |
| `retries` | Integer | 2 | Number of retry attempts on error |
| `timeout` | Integer | 30 | Connection timeout in seconds |
| `getImages` | Boolean | true | Download image files |
| `getVideos` | Boolean | true | Download video files |
| `followRobots` | Boolean | true | Respect robots.txt |
| `outputName` | String | null | Custom output name (auto-generated if empty) |
| `cleanup` | Boolean | true | Remove source files after creating ZIP |

### Output

The Actor provides two types of output:

#### 1. Dataset

Statistics and metadata for each scrape:

```json
{
  "url": "https://example.com",
  "outputName": "example.com_20241205_130000",
  "zipFile": "example.com_20241205_130000.zip",
  "fileCount": 156,
  "totalSize": 5242880,
  "zipSize": 2621440,
  "compressionRatio": 50.0,
  "timestamp": "2024-12-05T13:00:00.000Z",
  "config": { ... },
  "status": "success"
}
````

#### 2. Key-Value Store

The complete website as a ZIP archive. Access it via:

- Apify Console: Storage → Key-Value Store → \[filename].zip
- API: `https://api.apify.com/v2/key-value-stores/{storeId}/keys/{filename}.zip`

### Usage Examples

#### Example 1: Basic Website Backup

```json
{
  "url": "https://example.com",
  "depth": 2,
  "stayOnDomain": true
}
```

Downloads the website up to 2 levels deep, staying on the same domain.

#### Example 2: Deep Archive with External Links

```json
{
  "url": "https://example.com",
  "depth": 5,
  "externalDepth": 1,
  "stayOnDomain": false
}
```

Downloads 5 levels deep and follows external links 1 level.

#### Example 3: Fast Scrape (HTML/CSS/JS Only)

```json
{
  "url": "https://example.com",
  "depth": 3,
  "getImages": false,
  "getVideos": false,
  "connections": 8
}
```

Fast scraping without images or videos, using 8 parallel connections.

#### Example 4: Rate-Limited Polite Scrape

```json
{
  "url": "https://example.com",
  "depth": 2,
  "maxRate": 500,
  "connections": 2,
  "followRobots": true
}
```

Polite scraping with rate limiting and respecting robots.txt.

#### Example 5: Time-Limited Scrape

```json
{
  "url": "https://example.com",
  "depth": 10,
  "maxTime": 300,
  "maxSize": 100
}
```

Stops after 5 minutes or 100 MB, whichever comes first.

### How It Works

1. **Input Validation** - Validates the URL and configuration
2. **HTTrack Execution** - Runs HTTrack with configured parameters to download website source code
3. **Progress Monitoring** - Logs progress in real-time
4. **Pre-ZIP Cleanup** - Removes HTTrack cache files and index files before archiving
5. **ZIP Creation** - Creates a compressed archive of all website files and source code
6. **Storage** - Saves ZIP to Key-Value Store and stats to Dataset
7. **Post-ZIP Cleanup** - Optionally removes temporary files after ZIP creation

### Technical Details

#### Based On

- **HTTrack 3.49+** - Industry-standard website copier
- **Python 3.11** - Modern async Python runtime
- **Apify SDK 2.7+** - For Actor integration and storage

#### Limitations

- Some JavaScript-heavy SPAs may not download completely
- Websites with aggressive bot protection may block scraping
- Dynamic content loaded after page load may be missed
- Maximum recommended depth is 5-6 for most websites

#### Performance

- **Small websites** (< 100 pages): 1-5 minutes
- **Medium websites** (100-1000 pages): 5-30 minutes
- **Large websites** (1000+ pages): 30+ minutes

Performance depends on:

- Website size and structure
- Number of connections
- Network speed
- Rate limiting settings

### Legal and Ethical Considerations

⚠️ **Important**: Always ensure you have permission to scrape websites.

- ✅ Respect `robots.txt` files (enabled by default)
- ✅ Don't overload servers (use rate limiting)
- ✅ Check website Terms of Service
- ✅ Don't scrape copyrighted content without permission
- ✅ Use reasonable connection limits (2-8)

### Troubleshooting

#### Scraping Takes Too Long

- Reduce `depth` to 1 or 2
- Disable `getVideos` and `getImages`
- Increase `connections` (but be respectful)
- Set `maxTime` or `maxSize` limits

#### ZIP File Too Large

- Reduce `depth`
- Disable `getVideos`
- Set `maxSize` limit
- Use `maxTime` to stop early

#### Website Blocks Scraping

- Enable `followRobots`
- Reduce `connections` to 2-4
- Add rate limiting with `maxRate`
- Increase `timeout` if connections are slow

#### Missing Content

- Increase `depth`
- Enable `externalDepth` if content is on other domains
- Check if website uses heavy JavaScript (may not work)
- Enable `getImages` and `getVideos` if needed

### Development

#### Local Testing

```bash
## Install dependencies
pip install -r requirements.txt

## Run locally
apify run
```

#### Building

```bash
## Build Docker image
docker build -t httrack-scraper .

## Run container
docker run httrack-scraper
```

### Support

For issues or questions:

- Check Actor logs for detailed error messages
- Review HTTrack documentation: https://www.httrack.com/
- Contact Apify support through the platform

### License

This Actor uses HTTrack, which is licensed under GPL v3.

### Version History

- **1.0** - Initial release with full HTTrack integration, source code download, and ZIP archive output

# Actor input Schema

## `url` (type: `string`):

The URL of the website to scrape (must include http:// or https://)

## `depth` (type: `integer`):

How many links deep to follow (1 = only main page, 2-3 recommended)

## `stayOnDomain` (type: `boolean`):

Only download content from the same domain

## `externalDepth` (type: `integer`):

How deep to follow external links (0 = don't follow external links)

## `connections` (type: `integer`):

Number of parallel downloads (2-8 recommended)

## `maxRate` (type: `integer`):

Maximum download rate in KB/s (0 = unlimited)

## `maxSize` (type: `integer`):

Maximum total size to download in MB (0 = unlimited)

## `maxTime` (type: `integer`):

Maximum scraping time in seconds (0 = unlimited)

## `retries` (type: `integer`):

Number of retry attempts on error

## `timeout` (type: `integer`):

Connection timeout in seconds

## `getImages` (type: `boolean`):

Download image files (jpg, png, gif, etc.)

## `getVideos` (type: `boolean`):

Download video files (mp4, avi, mov, etc.)

## `followRobots` (type: `boolean`):

Respect website's robots.txt file

## `outputName` (type: `string`):

Custom name for output files (leave empty for auto-generated)

## `cleanup` (type: `boolean`):

Remove source directory after creating ZIP (saves storage)

## Actor input object example

```json
{
  "url": "https://example.com",
  "depth": 2,
  "stayOnDomain": true,
  "externalDepth": 0,
  "connections": 4,
  "maxRate": 0,
  "maxSize": 0,
  "maxTime": 0,
  "retries": 2,
  "timeout": 30,
  "getImages": true,
  "getVideos": true,
  "followRobots": true,
  "cleanup": true
}
```

# Actor output Schema

## `zipFile` (type: `string`):

Download the scraped website as a ZIP file. Check OUTPUT for direct download URL or browse keys below.

## `output` (type: `string`):

View output JSON containing ZIP filename and direct download URL

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

View scraping statistics and metadata

# 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 = {
    "url": "https://example.com"
};

// Run the Actor and wait for it to finish
const run = await client.actor("mikolabs/web-extractor").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 = { "url": "https://example.com" }

# Run the Actor and wait for it to finish
run = client.actor("mikolabs/web-extractor").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 '{
  "url": "https://example.com"
}' |
apify call mikolabs/web-extractor --silent --output-dataset

```

## MCP server setup

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

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Scrap Any Website with Source Code",
        "description": "Download complete websites and get them as ZIP archives. Perfect for creating offline backups, archiving websites, or downloading entire sites with all assets. Includes source code. For Research purposes",
        "version": "0.0",
        "x-build-id": "IrhNYtqNMbQiscUHb"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/mikolabs~web-extractor/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-mikolabs-web-extractor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/mikolabs~web-extractor/runs": {
            "post": {
                "operationId": "runs-sync-mikolabs-web-extractor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/mikolabs~web-extractor/run-sync": {
            "post": {
                "operationId": "run-sync-mikolabs-web-extractor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "url"
                ],
                "properties": {
                    "url": {
                        "title": "Website URL",
                        "type": "string",
                        "description": "The URL of the website to scrape (must include http:// or https://)"
                    },
                    "depth": {
                        "title": "Mirror Depth",
                        "minimum": 1,
                        "maximum": 10,
                        "type": "integer",
                        "description": "How many links deep to follow (1 = only main page, 2-3 recommended)",
                        "default": 2
                    },
                    "stayOnDomain": {
                        "title": "Stay on Same Domain",
                        "type": "boolean",
                        "description": "Only download content from the same domain",
                        "default": true
                    },
                    "externalDepth": {
                        "title": "External Links Depth",
                        "minimum": 0,
                        "maximum": 5,
                        "type": "integer",
                        "description": "How deep to follow external links (0 = don't follow external links)",
                        "default": 0
                    },
                    "connections": {
                        "title": "Simultaneous Connections",
                        "minimum": 1,
                        "maximum": 16,
                        "type": "integer",
                        "description": "Number of parallel downloads (2-8 recommended)",
                        "default": 4
                    },
                    "maxRate": {
                        "title": "Max Download Rate (KB/s)",
                        "minimum": 0,
                        "type": "integer",
                        "description": "Maximum download rate in KB/s (0 = unlimited)",
                        "default": 0
                    },
                    "maxSize": {
                        "title": "Max Total Size (MB)",
                        "minimum": 0,
                        "type": "integer",
                        "description": "Maximum total size to download in MB (0 = unlimited)",
                        "default": 0
                    },
                    "maxTime": {
                        "title": "Max Time (seconds)",
                        "minimum": 0,
                        "type": "integer",
                        "description": "Maximum scraping time in seconds (0 = unlimited)",
                        "default": 0
                    },
                    "retries": {
                        "title": "Retry Attempts",
                        "minimum": 0,
                        "maximum": 10,
                        "type": "integer",
                        "description": "Number of retry attempts on error",
                        "default": 2
                    },
                    "timeout": {
                        "title": "Connection Timeout (seconds)",
                        "minimum": 5,
                        "maximum": 300,
                        "type": "integer",
                        "description": "Connection timeout in seconds",
                        "default": 30
                    },
                    "getImages": {
                        "title": "Download Images",
                        "type": "boolean",
                        "description": "Download image files (jpg, png, gif, etc.)",
                        "default": true
                    },
                    "getVideos": {
                        "title": "Download Videos",
                        "type": "boolean",
                        "description": "Download video files (mp4, avi, mov, etc.)",
                        "default": true
                    },
                    "followRobots": {
                        "title": "Follow robots.txt",
                        "type": "boolean",
                        "description": "Respect website's robots.txt file",
                        "default": true
                    },
                    "outputName": {
                        "title": "Output Name (Optional)",
                        "type": "string",
                        "description": "Custom name for output files (leave empty for auto-generated)"
                    },
                    "cleanup": {
                        "title": "Cleanup After Zipping",
                        "type": "boolean",
                        "description": "Remove source directory after creating ZIP (saves storage)",
                        "default": true
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
