diff --git a/README.md b/README.md index 1f140137f7..e095d2fe60 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ +## 📊 Data‑Science Extras + +A Jupyter notebook demonstrating how to load SpiderFoot JSON output, perform K‑Means clustering and Isolation Forest anomaly detection, and visualize results. + +- [extras/notebooks/data_analysis.ipynb](extras/notebooks/data_analysis.ipynb) + diff --git a/data/sample_scan.json b/data/sample_scan.json new file mode 100644 index 0000000000..7b8cd86c8e --- /dev/null +++ b/data/sample_scan.json @@ -0,0 +1,5 @@ +[ + { "entity": "127.0.0.1", "risk": 45, "confidence": 80, "nb_records": 5 }, + { "entity": "192.168.1.1", "risk": 10, "confidence": 60, "nb_records": 2 }, + { "entity": "10.0.0.5", "risk": 90, "confidence": 95, "nb_records": 12 } +] diff --git a/extras/notebooks/data_analysis.ipynb b/extras/notebooks/data_analysis.ipynb new file mode 100644 index 0000000000..8c576394a4 --- /dev/null +++ b/extras/notebooks/data_analysis.ipynb @@ -0,0 +1,452 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 4, + "id": "bd3fbeb7-9dee-4b2b-9737-640e4c848f70", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Working Dir: C:\\Users\\djouv\\Projects\\spiderfoot\\extras\\notebooks\n" + ] + } + ], + "source": [ + "import os\n", + "print(\"Working Dir:\", os.getcwd())\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "4db1ca68-aea3-4a60-b946-22e98b9bae3f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Project Root: C:\\Users\\djouv\\Projects\\spiderfoot\n", + "Contents of Root: ['.dockerignore', '.git', '.github', '.gitignore', '.ipynb_checkpoints', '.pylintrc', '.venv', 'correlations', 'data', 'docker-compose-dev.yml', 'docker-compose-full.yml', 'docker-compose.yml', 'Dockerfile', 'Dockerfile.full', 'docs', 'extras', 'extras.409e9dfa-e2a9-4889-bc77-e1d9be566c5b', 'generate-certificate', 'LICENSE', 'modules', 'README.md', 'requirements.txt', 'setup.cfg', 'sf.py', 'sfcli.py', 'sflib.py', 'sfscan.py', 'sfwebui.py', 'spiderfoot', 'test', 'THANKYOU', 'Untitled.ipynb', 'VERSION']\n" + ] + } + ], + "source": [ + "project_root = os.path.abspath(os.path.join(os.getcwd(), os.pardir, os.pardir))\n", + "print(\"Project Root:\", project_root)\n", + "print(\"Contents of Root:\", os.listdir(project_root))\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "8382c0f7-c449-49fb-ba5c-100a5224cfae", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Working Dir: C:\\Users\\djouv\\Projects\\spiderfoot\\extras\\notebooks\n", + "Root Contents: ['.ipynb_checkpoints', 'data_analysis.ipynb']\n" + ] + } + ], + "source": [ + "import os\n", + "print(\"Working Dir:\", os.getcwd())\n", + "print(\"Root Contents:\", os.listdir(os.getcwd()))\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "c57dcd3a-6957-401d-bc98-b98dc18242b5", + "metadata": {}, + "outputs": [ + { + "ename": "FileNotFoundError", + "evalue": "[WinError 3] The system cannot find the path specified: 'C:\\\\Users\\\\djouv\\\\Projects\\\\spiderfoot\\\\extras\\\\notebooks\\\\data'", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mFileNotFoundError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[3]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33m\"\u001b[39m\u001b[33mData folder contains:\u001b[39m\u001b[33m\"\u001b[39m, \u001b[43mos\u001b[49m\u001b[43m.\u001b[49m\u001b[43mlistdir\u001b[49m\u001b[43m(\u001b[49m\u001b[43mos\u001b[49m\u001b[43m.\u001b[49m\u001b[43mpath\u001b[49m\u001b[43m.\u001b[49m\u001b[43mjoin\u001b[49m\u001b[43m(\u001b[49m\u001b[43mos\u001b[49m\u001b[43m.\u001b[49m\u001b[43mgetcwd\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43mdata\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m)\n", + "\u001b[31mFileNotFoundError\u001b[39m: [WinError 3] The system cannot find the path specified: 'C:\\\\Users\\\\djouv\\\\Projects\\\\spiderfoot\\\\extras\\\\notebooks\\\\data'" + ] + } + ], + "source": [ + "print(\"Data folder contains:\", os.listdir(os.path.join(os.getcwd(), 'data')))\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "62d20b34-a224-4c46-af16-2ca3ebe98194", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "ename": "JSONDecodeError", + "evalue": "Expecting value: line 1 column 1 (char 0)", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mJSONDecodeError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[1]\u001b[39m\u001b[32m, line 3\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;28;01mimport\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mjson\u001b[39;00m,\u001b[38;5;250m \u001b[39m\u001b[34;01mpandas\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mas\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mpd\u001b[39;00m\n\u001b[32m 2\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28mopen\u001b[39m(\u001b[33m'\u001b[39m\u001b[33m../../data/sample_scan.json\u001b[39m\u001b[33m'\u001b[39m) \u001b[38;5;28;01mas\u001b[39;00m f:\n\u001b[32m----> \u001b[39m\u001b[32m3\u001b[39m records = \u001b[43mjson\u001b[49m\u001b[43m.\u001b[49m\u001b[43mload\u001b[49m\u001b[43m(\u001b[49m\u001b[43mf\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 4\u001b[39m df = pd.json_normalize(records)\n\u001b[32m 5\u001b[39m df\n", + "\u001b[36mFile \u001b[39m\u001b[32m~\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\json\\__init__.py:293\u001b[39m, in \u001b[36mload\u001b[39m\u001b[34m(fp, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)\u001b[39m\n\u001b[32m 274\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mload\u001b[39m(fp, *, \u001b[38;5;28mcls\u001b[39m=\u001b[38;5;28;01mNone\u001b[39;00m, object_hook=\u001b[38;5;28;01mNone\u001b[39;00m, parse_float=\u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[32m 275\u001b[39m parse_int=\u001b[38;5;28;01mNone\u001b[39;00m, parse_constant=\u001b[38;5;28;01mNone\u001b[39;00m, object_pairs_hook=\u001b[38;5;28;01mNone\u001b[39;00m, **kw):\n\u001b[32m 276\u001b[39m \u001b[38;5;250m \u001b[39m\u001b[33;03m\"\"\"Deserialize ``fp`` (a ``.read()``-supporting file-like object containing\u001b[39;00m\n\u001b[32m 277\u001b[39m \u001b[33;03m a JSON document) to a Python object.\u001b[39;00m\n\u001b[32m 278\u001b[39m \n\u001b[32m (...)\u001b[39m\u001b[32m 291\u001b[39m \u001b[33;03m kwarg; otherwise ``JSONDecoder`` is used.\u001b[39;00m\n\u001b[32m 292\u001b[39m \u001b[33;03m \"\"\"\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m293\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mloads\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfp\u001b[49m\u001b[43m.\u001b[49m\u001b[43mread\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 294\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;28;43mcls\u001b[39;49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mcls\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mobject_hook\u001b[49m\u001b[43m=\u001b[49m\u001b[43mobject_hook\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 295\u001b[39m \u001b[43m \u001b[49m\u001b[43mparse_float\u001b[49m\u001b[43m=\u001b[49m\u001b[43mparse_float\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mparse_int\u001b[49m\u001b[43m=\u001b[49m\u001b[43mparse_int\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 296\u001b[39m \u001b[43m \u001b[49m\u001b[43mparse_constant\u001b[49m\u001b[43m=\u001b[49m\u001b[43mparse_constant\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mobject_pairs_hook\u001b[49m\u001b[43m=\u001b[49m\u001b[43mobject_pairs_hook\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkw\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\json\\__init__.py:346\u001b[39m, in \u001b[36mloads\u001b[39m\u001b[34m(s, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)\u001b[39m\n\u001b[32m 341\u001b[39m s = s.decode(detect_encoding(s), \u001b[33m'\u001b[39m\u001b[33msurrogatepass\u001b[39m\u001b[33m'\u001b[39m)\n\u001b[32m 343\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m (\u001b[38;5;28mcls\u001b[39m \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m object_hook \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m\n\u001b[32m 344\u001b[39m parse_int \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m parse_float \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m\n\u001b[32m 345\u001b[39m parse_constant \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m object_pairs_hook \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m kw):\n\u001b[32m--> \u001b[39m\u001b[32m346\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43m_default_decoder\u001b[49m\u001b[43m.\u001b[49m\u001b[43mdecode\u001b[49m\u001b[43m(\u001b[49m\u001b[43ms\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 347\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mcls\u001b[39m \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m 348\u001b[39m \u001b[38;5;28mcls\u001b[39m = JSONDecoder\n", + "\u001b[36mFile \u001b[39m\u001b[32m~\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\json\\decoder.py:345\u001b[39m, in \u001b[36mJSONDecoder.decode\u001b[39m\u001b[34m(self, s, _w)\u001b[39m\n\u001b[32m 340\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mdecode\u001b[39m(\u001b[38;5;28mself\u001b[39m, s, _w=WHITESPACE.match):\n\u001b[32m 341\u001b[39m \u001b[38;5;250m \u001b[39m\u001b[33;03m\"\"\"Return the Python representation of ``s`` (a ``str`` instance\u001b[39;00m\n\u001b[32m 342\u001b[39m \u001b[33;03m containing a JSON document).\u001b[39;00m\n\u001b[32m 343\u001b[39m \n\u001b[32m 344\u001b[39m \u001b[33;03m \"\"\"\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m345\u001b[39m obj, end = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mraw_decode\u001b[49m\u001b[43m(\u001b[49m\u001b[43ms\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43midx\u001b[49m\u001b[43m=\u001b[49m\u001b[43m_w\u001b[49m\u001b[43m(\u001b[49m\u001b[43ms\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[32;43m0\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m.\u001b[49m\u001b[43mend\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 346\u001b[39m end = _w(s, end).end()\n\u001b[32m 347\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m end != \u001b[38;5;28mlen\u001b[39m(s):\n", + "\u001b[36mFile \u001b[39m\u001b[32m~\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\json\\decoder.py:363\u001b[39m, in \u001b[36mJSONDecoder.raw_decode\u001b[39m\u001b[34m(self, s, idx)\u001b[39m\n\u001b[32m 361\u001b[39m obj, end = \u001b[38;5;28mself\u001b[39m.scan_once(s, idx)\n\u001b[32m 362\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mStopIteration\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m err:\n\u001b[32m--> \u001b[39m\u001b[32m363\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m JSONDecodeError(\u001b[33m\"\u001b[39m\u001b[33mExpecting value\u001b[39m\u001b[33m\"\u001b[39m, s, err.value) \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m 364\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m obj, end\n", + "\u001b[31mJSONDecodeError\u001b[39m: Expecting value: line 1 column 1 (char 0)" + ] + } + ], + "source": [ + "import json, pandas as pd\n", + "with open('../../data/sample_scan.json') as f:\n", + " records = json.load(f)\n", + "df = pd.json_normalize(records)\n", + "df\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "61b3ec99-170e-4b74-b235-2529be65545f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loading from: C:\\Users\\djouv\\Projects\\spiderfoot\\data\\sample_scan.json\n", + "DataFrame preview:\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
entityriskconfidencenb_records
0127.0.0.145805
1192.168.1.110602
210.0.0.5909512
\n", + "
" + ], + "text/plain": [ + " entity risk confidence nb_records\n", + "0 127.0.0.1 45 80 5\n", + "1 192.168.1.1 10 60 2\n", + "2 10.0.0.5 90 95 12" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Cell: Load data with BOM support\n", + "import json\n", + "import pandas as pd\n", + "\n", + "# Absolute path to the JSON\n", + "json_path = os.path.join(project_root, 'data', 'sample_scan.json')\n", + "print(\"Loading from:\", json_path)\n", + "\n", + "# Use utf-8-sig to strip any BOM\n", + "with open(json_path, 'r', encoding='utf-8-sig') as f:\n", + " records = json.load(f)\n", + "\n", + "df = pd.json_normalize(records)\n", + "print(\"DataFrame preview:\")\n", + "df\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "4ca5b22a-ae2d-4a34-988b-99bce41764b3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
riskconfidencenb_records
045805
110602
2909512
\n", + "
" + ], + "text/plain": [ + " risk confidence nb_records\n", + "0 45 80 5\n", + "1 10 60 2\n", + "2 90 95 12" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Cell: Feature engineering\n", + "features = df[['risk', 'confidence', 'nb_records']].fillna(0)\n", + "features\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "13da56db-ec15-4b8d-b308-f5388bb08c6c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
entityriskconfidencenb_recordsclusteranomaly
0127.0.0.14580501
1192.168.1.11060201
210.0.0.59095121-1
\n", + "
" + ], + "text/plain": [ + " entity risk confidence nb_records cluster anomaly\n", + "0 127.0.0.1 45 80 5 0 1\n", + "1 192.168.1.1 10 60 2 0 1\n", + "2 10.0.0.5 90 95 12 1 -1" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Cell: Clustering & anomaly detection\n", + "from sklearn.cluster import KMeans\n", + "from sklearn.ensemble import IsolationForest\n", + "\n", + "# K-Means\n", + "kmeans = KMeans(n_clusters=2, random_state=0).fit(features)\n", + "df['cluster'] = kmeans.labels_\n", + "\n", + "# Isolation Forest\n", + "iso = IsolationForest(contamination=0.2, random_state=0).fit(features)\n", + "df['anomaly'] = iso.predict(features) # -1 = anomaly, 1 = normal\n", + "\n", + "# Show results\n", + "df\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "3ea2339b-04ee-4f87-bda3-e84e4a9aabec", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAnYAAAHWCAYAAAD6oMSKAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQAATAdJREFUeJzt3QmcjXX///HPzGAIM5bs2YmsWVpsbfxyt0irFsp2a1NZiqiQNlp0a6VFKpUWIbfKkkgkUiGRPRTSYl+Gmbn+j/f39zvzPzNmmDPObNd5PR+PY+Zc5zrnfK/FOe/5bleU53meAQAAIN+Lzu0CAAAAIDwIdgAAAD5BsAMAAPAJgh0AAIBPEOwAAAB8gmAHAADgEwQ7AAAAnyDYAQAA+ATBDgAAwCcIdkAG3nzzTYuKirJff/31hOtWq1bNunXrliPlQt7w8MMPu/MDCPU84fMC2YlgB9/46aef7Nprr7WqVata4cKFrVKlSvY///M/9sILL1h+pECpL4T0bueee262vOfBgwfdF9G8efNCKmf37t2tZs2abr+XL1/ezjvvPBs2bJjlR4cPH7b//Oc/ds4551h8fLzbptNPP93uuusuW7t2bY6V47333rPRo0dbXvTbb7/ZDTfcYGXLlrW4uDi3r/SHUFYkJSVZxYoV3Xn9+eefh72sQKQpkNsFAMLhm2++sQsvvNCqVKlivXr1cuFi69at9u2339pzzz1nd999d8ivefPNN7svr9jYWMtNN954o1166aWplpUpUybbgt3w4cPd7xdccMEJ11+/fr2dddZZVqRIEevRo4eridi+fbv98MMP9uSTT6a8Vn7x119/2b/+9S/7/vvv7fLLL7ebbrrJihUrZmvWrLH333/fXn31VTty5EiOBbuVK1da3759LS9JTk62K664woVclU2hbMmSJfbBBx9kqRbqyy+/dOeMzp13333XLrnkEvM7nU/R0dSrIHsQ7OALjz/+uKtd+e6776xEiRKpHtu5c2eWXjMmJsbdclpiYqL78gxo2rSpdenSxfIi1Wzt37/fli1b5mpKw7Hfc5OCyY8//miTJk2ya665JtVjjz76qD344IOWn+m8UjBVLeTJhBLto6eeesoGDBjglt15552WkJCQpdd755133DnetWtXe+CBB+zAgQNWtGhR87Pc/mMR/safDPCFDRs2WP369Y8JdaLmomBq8lGzmmoH6tSp477kmjVrZvPnzz9hHzvP8+yxxx6z0047zU455RRXS/jzzz+nW6bdu3e7Go3KlSu7D/JatWq5Wqzg0BZobn3mmWdcs5uaM7XuqlWrMr3tGzdutOuuu85KlSrlyqRm2k8//fSY9RS0evbsaeXKlXPb3LhxY3vrrbdSlSVQE6iatkCzr5pmj7fftS/Shrr09ruoqe3888+34sWLuyY81fapZirg66+/dtuimlftB+27fv362aFDh44JYKpJ+/333+3KK690v6vs9913n2vaC6baoF9++cWOHj163P24ePFit9+0j9KGOlF5dJwyEjiW6TVJpt2P+/btc+eGaqn0utpX6jagms5AbanKsnnz5pTjoHUDFKLU1K1zKrCfBg4ceEy4Cj7X9f9D686YMcM9phpInfeBY9GwYUNXu30igZom/V9Iu39CpeM6ZcoUVzPeqVMnd/+TTz45Zr1QjreC4b333pvy/07/x3Xc0pY3sG8++ugjq1evnqt1btGihevSIa+88orbv/q/ouORtq9tZs/V9KTXxy4znxcnc9wQOaixgy8oWCxatMg1XTVo0OCE63/11Veu6eiee+5xH6Ivv/yya4JTk9Lxnj906FAX7NQ0qpu+iC+++OJjmufUpKkAoy+i2267zX34q7l48ODBLmik7Ts1fvx417fr1ltvdeVRSAt8oOu11EQYTLWTBQsWtD/++MNatmzp1tG2lC5d2oU1NZWp1umqq65y6+vLRl9OajrVl1n16tXdF5q+XPSF0qdPH/dFOWbMGLvjjjvc866++mr33EaNGh13v3/xxReuOe2iiy467j5X4FFzrQKG9oNCuGp+FDTU5Ckqk7ZFZdC26Hioj6T6dOmxYPpCb9++vevfpS9ulWPUqFEuHOv5AXov7ZNNmzalCkdpTZs2LaUJPrvdfvvt7vjoWChU/P3337ZgwQJbvXq1q71SzeCePXvcdqtWVBRmgptCtb7OlzPOOMOFEa2n5tGpU6emei8dmw8//NC916mnnur2wezZs10Tf9u2bV14EL33woUL3blwPApKOue0rxXIdG5nlfa5anz1Ouo+oXNUITRwPoR6vBXetG/mzp3rAvqZZ55pM2fOdDWL+r8Y2JfB4Uxl6N27t7s/YsQI1wSvkKzPBNVE7tq1y9VO6tzVvgwI5Vw9kcx+XpzMcUME8QAf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MzAiVvRT34n6blafc/XD01x3+l7Nabka7LQTAmFANCeMflcznXbatdde6zrUa5ixOiJqtIluqg7NbTG6oHxMtHmHD6f7ePLu3RZdtoxFZ0Ow1EnWvn17N02M2vvTSm9KEvV5k+3bt6csC+z3YOpPd/vtt7sOn/fee6+99tprqaqidRwCFMYUpHW89FdJ8E2vcyIa8NGvXz83JFz/aXJ65BAAAJkdsKhKprfeest9d6nbUeB687t27XI/1V9c34uad1fdjxT2NOAip+VqHzuFtsBoy0DznHTt2tXV6EybNs3dP/PMM1M9TxMCXnDBBZabCjZqaAVq1LDEdesspmYtiwqqak1WTdaRI1b4oossKpva1hXq1GSq6V90AjVq1MgNtVZztf6q0Pw6wQJhS/tVgy7Wrl3rLn8SrG/fvm76GZ20OlG1n88444yUqmj9JTJ9+nS79NJL3eAJVTXfd999LpypX0Hr1q1d8/fChQtdTamOY0ZV1epfp+Cuv2w0gkhz/ah/HwAAeU2bNm3cKNfbbrvNtUBdd911ruZu586d7rtXlRzlypVz343q56/WLt1OPfXUnC+s53N79uxRPan7mdahQ4e8VatWuZ9ZcWTNGu/vO+70/riio/dn55u9P7v39HZe28nbedU13p7/jPaSDx/2stO2bdu83r17e1WrVvUKFSrkVapUybviiiu8uXPnuse13VOmTElZf8GCBV7Dhg29woULe23atPE++ugjt86mTZvc43fddZdXs2ZNLzY21itTpox38803e3/99VfK8x955BGvfPnyXlRUlNe1a1e3LDk52Rs9erRXp04dr2DBgu557du397766iv3uMqi99i1a1fK6yQkJHg33HCDV7lyZVfuihUruvfO6nE4kZM9zgAAHDlyxLvpppvcd1q7du28yZMnu2Wyb98+79VXX/XOPPNM9/gTTzyRY1kmrSj9Yz6mocaa7Fg1SWn726l9XM2/qjVSZ8isSPx9mx2eN9eOfLPITVCsJtrCF15osa1aWlRsbJi2AicjHMcZAIDk5GSbOHGiazXTpcVE3yv6nlGr1uWXX+5avy666KIcyzJpEezC9IXvdmNiokUxF1ueQ7ADAISb+qnrkqfq6658oSm+NNdddggl2OWLeezyAzc6llAHAEBEOPPMM48ZA5AX5OnpTgAAAJB5BDsAAACfINgBAAD4BMEOAADAJwh2AAAAPkGwAwAA8AmmOzlJulbczJkz3QXtNV9ayZIl3WW5atasmdtFAwAAEYYauyzS9U5HjBjhLgR82WWXueuvvvzyy3bvvfe667Iq3H355Ze5Xcw8SRM4jh49OtUcgFOnTs3VMgEA4AcEuyz4559/rG3btu5Cv/q5ZMkS2717t23fvt39HD9+vP3555/Wrl07e+6557KtHLqcSUxMjAuW+Zn2m4IwAAA4OQS7EB05csSuvPJKW7t2rX399df2xhtv2FlnnZXyeJEiRaxbt24u7Kn2TteMe/vtt7OlLOPGjbO7777b5s+fb9u2bbP8qnz58hbLdXUBADhpBLsQvfvuu7ZgwQKbNm1aqkCXVnR0tD311FPWpUsX69evn2u6Daf9+/fbBx98YHfccYersXvzzTdTHps3b55r3pwzZ441b97cTjnlFGvZsqWtWbMm1WuMGTPG9QUsVKiQ1alTxyZMmJDqcb3GK6+84i5qrNc444wzXC3h+vXr7YILLrCiRYu6192wYUPKc/R7x44drVy5clasWDG3j7744ovjbkvaptitW7dap06drESJElaqVCn3er/++muq7Tv77LPd+2udVq1a2ebNm09qfwIA4AcEuxCpH92//vUvF2hORIFl2LBhrun2ww8/DGs59Hp169Z1gUzhUTWHnuelWufBBx+0UaNG2dKlS61AgQLWo0ePlMemTJliffr0cbWKK1eutNtuu826d+9uc+fOTfUajz76qN1yyy3uYsd6v5tuusmtO3jwYPe6es+77rorVeC89NJLXaj88ccf3b7q0KGDbdmyJVPbdfToUWvfvr0VL17c1YguXLjQBUS9jmpLExMTXY2pLra8YsUKFzRvvfXW/71WLwAAkc7zuT179ijtuJ9pHTp0yFu1apX7mRkrVqxwrzVt2rSQytC+fXuvTZs2Xji1bNnSGz16tPv96NGj3qmnnurNnTvX3ddPlfOLL75IWf/TTz91ywLbquf36tUr1Wted9113qWXXppyX+s/9NBDKfcXLVrklo0bNy5l2cSJE73ChQsft6z169f3XnjhhZT7VatW9f7zn/+kep8pU6a43ydMmODVqVPHS05OTnk8ISHBK1KkiDdz5kzv77//duvPmzcv0/sq1OMMAEB+yTJpUWMXgo0bN7qf55xzTkjP0/qbNm0KWznUpKo+fDfe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JMXhcvnll1u5cuVs3LhxKcuuueYaV3v3zjvvZOo16GMHAADys2ztY6fg1q9fP3dJMb2Bbvpd052EM9SJagTnzJlja9eudfeXL19uCxYssEsuuSSs7wMAAOAHWbryRGJiotWuXduKFy+esnzdunVWsGBBq1atWtgKp5G2Sql169a1mJgY1+fu8ccft86dO2f4nISEBHcL0PMBAAAiQcg1dt26dbNvvvnmmOWay06PhdOHH35o7777rr333ntuGhX1tXvmmWfcz4yMGDHCVVcGbpUrVw5rmQAAAHzTx05tuwpZtWrVSrV8/fr11rx5c9u9e3fYCqdQplq73r17pyx77LHHXP+6X375JdM1dnod+tgBAAC/97ELuSlW/ej27dt3zHK9mZpKw+ngwYMWHZ26UlFNssnJyRk+JzY21t0AAAAiTchNseedd55r7gwOcfpdy1q3bh3WwnXo0MH1qfv000/t119/tSlTptizzz5rV111VVjfBwAAICKbYnV1CYW7EiVKWJs2bdyyr7/+2lUTfvnll9agQYOwFU41g5qgWIFu586dblqVG2+80YYOHWqFChXK1Gsw3QkAAMjPQskyIQc70fx1L774opt+RHPKNWrUyO666y4rVaqU5TUEOwAAkJ9le7DLTwh2AAAgP8vWCYoDTa9dunRxEwj//vvvbtmECRPc5MEAAADIHSEHu48//tjat2/vmmA17UlgahGlyCeeeCI7yggAAIDsCHaaR27s2LH22muvuStNBLRq1coFPQAAAOSTYLdmzRo3KjYttf2Gc3JiAAAAZHOwK1++vLvKRFrqX1ejRo1QXw4AAAC5Fex69eplffr0cdeG1VUoNPWJrud633332R133BGucgEAACBEIV9STNdu1SW92rZt6y75pWZZXcJLwe7uu+8O9eUAAAAQJlmex+7IkSOuSXb//v1Wr149K1asmB06dMiNls1LmMcOAADkZ9k+j53okl4KdGeffbYbHatruFavXj2rLwcAAICTlOlgp/nqBg8ebM2bN3cTE0+dOtUtHz9+vAt0//nPf6xfv34nWx4AAABkdx+7oUOH2iuvvGLt2rWzb775xq677jrr3r27ffvtt662TvdjYmKyWg4AAADkVLD76KOP7O2337YrrrjCVq5caY0aNbLExERbvny5Gx0LAACAfNIU+9tvv1mzZs3c7w0aNHAj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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Cell: Visualization\n", + "import matplotlib.pyplot as plt\n", + "\n", + "plt.scatter(df['risk'], df['nb_records'],\n", + " c=df['cluster'], cmap='Set1', alpha=0.6,\n", + " label='Clusters')\n", + "# Highlight anomalies\n", + "anomalies = df[df['anomaly'] == -1]\n", + "plt.scatter(anomalies['risk'], anomalies['nb_records'],\n", + " facecolors='none', edgecolors='k', s=100,\n", + " label='Anomalies')\n", + "\n", + "plt.xlabel('Risk Score')\n", + "plt.ylabel('Number of Records')\n", + "plt.title('SpiderFoot Scan: Clusters & Anomalies')\n", + "plt.legend()\n", + "plt.tight_layout()\n", + "plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a71cb528-c6bd-4b01-aacb-c23008ed79f9", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}