This project builds visual embeddings from website screenshots and clusters them using two agglomerative-style strategies:
- Min-distance merging over a precomputed similarity matrix
- Centroid-based iterative merging directly on embeddings
The pipeline is designed for screenshots stored in parquet files (column screenshots), where each screenshot payload is expected to contain base64-encoded image data.
graph-based-phishing-datalake/
├── screen_encoding.py
├── agglomerative_clustering_min_distance.py
├── agglomerative_clustering_centroids.py
├── utils.py
├── data/
│ ├── .gitignore
│ ├── screen_similarity_matrix.npy # generated
│ └── valid_screenshots.txt # generated
├── output/
│ ├── .gitignore
│ └── clusters_*.txt # generated
└── README.md
- Python 3.9+
- Optional GPU for faster embedding inference
Install dependencies:
pip install pandas pillow torch transformers tqdm numpy scikit-learn joblib pyarrowThe input directory (default: data/sample/history__web_crawler/) should contain parquet files with at least:
domainscreenshots
screenshots is expected to be iterable-like per row, with the first element containing a base64 image string (used as screenshot[0]).
Script: screen_encoding.py
What it does:
- Loads parquet files and reads screenshots
- Encodes images using a Hugging Face vision model
- Computes pairwise cosine similarity between screenshot embeddings
- Saves:
- similarity matrix (
.npy) - optional raw embeddings (
.npy) - list of valid screenshots (
.txt)
- similarity matrix (
Run:
python screen_encoding.py \
--input-dir data/sample/history__web_crawler/ \
--vision-encoder-name google/vit-base-patch16-224-in21k \
--cache-dir /data2/hf_cache/models \
--output-npy data/screen_similarity_matrix.npy \
--save-embeddingsUseful options:
--max-files N: process only the firstNparquet files
Arguments:
| Argument | Type | Default | Description |
|---|---|---|---|
--input-dir |
str |
data/sample/history__web_crawler/ |
Directory containing parquet files with the screenshots column. |
--vision-encoder-name |
str |
google/vit-base-patch16-224-in21k |
Hugging Face vision model used to encode screenshots. |
--cache-dir |
str |
/data2/hf_cache/models |
Local Hugging Face cache directory for model files. |
--output-npy |
str |
data/screen_similarity_matrix.npy |
Output path for the similarity matrix in .npy format. |
--max-files |
int |
None |
Optional limit on how many parquet files are loaded. |
--save-embeddings |
flag |
False |
If set, also saves screenshot embeddings to data/screen_embeddings.npy. |
Script: agglomerative_clustering_min_distance.py
What it does:
- Loads a precomputed similarity matrix
- Converts it to distance (
1 - similarity) - Builds clusters using a union-find strategy for each threshold
- Computes silhouette score (precomputed distance metric)
- Writes one output report per threshold
Run:
python agglomerative_clustering_min_distance.py \
--similarity-matrix-path data/screen_similarity_matrix.npy \
--thresholds 0.01 0.02 0.05 0.1 0.2 0.3 0.4 0.5Output files are written to output/ with names like:
clusters_min_distance_threshold_<threshold>.txt
Arguments:
| Argument | Type | Default | Description |
|---|---|---|---|
--similarity-matrix-path |
str |
data/screen_similarity_matrix.npy |
Path to the precomputed similarity matrix (.npy). |
--thresholds |
list[float] |
0.01 0.02 0.05 0.1 0.2 0.3 0.4 0.5 |
One or more merge thresholds to evaluate. |
Script: agglomerative_clustering_centroids.py
What it does:
- Uses embeddings (either precomputed or generated on the fly)
- Iteratively merges the closest pair of active cluster centroids
- Stops when minimum centroid distance exceeds threshold
- Computes silhouette score and writes per-threshold reports
This method is an alternative to the min-distance approach above, not a required next step.
Run with precomputed embeddings:
python agglomerative_clustering_centroids.py \
--precomputed-embeddings-path data/screen_embeddings.npy \
--thresholds 0.01 0.02 0.05 0.1 0.2 0.3 0.4 0.5Run with on-the-fly embedding generation:
python agglomerative_clustering_centroids.py \
--input-dir data/sample/history__web_crawler/ \
--model-name google/vit-base-patch16-224-in21k \
--cache-dir /data2/hf_cache/models \
--thresholds 0.01 0.02 0.05 0.1 0.2 0.3 0.4 0.5Output files are written to output/ with names like:
clusters_centroids_threshold_<threshold>.txt
Arguments:
| Argument | Type | Default | Description |
|---|---|---|---|
--input-dir |
str |
data/sample/history__web_crawler/ |
Input parquet directory (used when embeddings are not precomputed). |
--precomputed-embeddings-path |
str |
None |
Optional path to embeddings (.npy); skips embedding generation if provided. |
--thresholds |
list[float] |
0.01 0.02 0.05 0.1 0.2 0.3 0.4 0.5 |
One or more centroid-distance thresholds to evaluate. |
--model-name |
str |
google/vit-base-patch16-224-in21k |
Hugging Face model used when generating embeddings on the fly. |
--cache-dir |
str |
/data2/hf_cache/models |
Local Hugging Face cache directory for model files. |
--max-files |
int |
None |
Optional limit on how many parquet files are loaded for embedding generation. |
- Generate similarity matrix (and optionally embeddings):
python screen_encoding.py --input-dir <parquet_dir> --save-embeddings- Choose one clustering strategy:
Option A: run min-distance clustering:
python agglomerative_clustering_min_distance.pyOption B: run centroid-based clustering:
python agglomerative_clustering_centroids.py --precomputed-embeddings-path data/screen_embeddings.npy- Compare silhouette scores in
output/*.txtacross thresholds and methods.
data/andoutput/artifacts are intentionally ignored by git (except.gitignoreplaceholders).- Very large datasets may require significant RAM due to pairwise similarity matrix computation.
- If no valid screenshots are decoded,
screen_encoding.pyraises a runtime error.