Da HTML a CSV: Come Estrarre Dati con JavaScript

Nel mondo dello sviluppo web, spesso ci troviamo a dover estrarre dati da pagine HTML per elaborarli o visualizzarli in altri formati. Una delle operazioni comuni è convertire elenchi HTML in file CSV, al fine di memorizzare le informazioni all'interno di una base dati e successivamente eseguire operazioni di reportistica. In questo articolo, eseguiremo tale operazione usando il linguaggio di programmazione Javascript.

La conversione da HTML a CSV, infatti, è utile in molte situazioni, come l'automazione di report, l'analisi dei dati estratti da pagine web, e molto altro. JavaScript si rivela uno strumento potente per manipolare il DOM (Document Object Model) e estrarre dati da pagine web, rendendolo ideale per questo compito.

In questo articolo, esploreremo come eseguire questa operazione in modo semplice e efficace. Il codice mostrato nell'articolo può essere eseguito su qualsiasi piattaforma poiché lavora con Docker.

Prima di iniziare, assicurati di avere le conoscenze di base di HTML, CSS e JavaScript. Inoltre, è utile avere familiarità con i concetti di selezione degli elementi del DOM e manipolazione degli array in JavaScript.

Obiettivo

L'articolo ti mostra come prelevare le informazioni dal seguente frammento di codice HTML:

<ul class="dropdown-menu inner show" role="presentation" style="margin-top: 0px; margin-bottom: 0px;">
    <li>
        <a role="option" class="dropdown-item" id="bs-select-1-0" tabindex="0">
            <span class="text">AL: Albania</span>
        </a>
    </li>
    <li>
        <a role="option" class="dropdown-item" id="bs-select-1-1" tabindex="0">
            <span class="text">AD: Andorra</span>
        </a>
    </li>
    <li>
        <a role="option" class="dropdown-item" id="bs-select-1-2" tabindex="0">
            <span class="text">AM: Armenia</span>
        </a>
    </li>
    <!-- Altri li con le nazioni -->
</ul>

e memorizzarle all'interno di un file csv, come segue:

ABBREVIATION,NAME
AL,Albania
AD,Andorra
AM,Armenia
...

Librerie utilizzate

Nello script che andremo a vedere, sono utilizzate le librerie fs, cheerio e csv-writer.

  • fs(File System) è un modulo in Node.js che fornisce un'API per interagire con il file system del sistema operativo.
  • cheerio è una libreria di parsing e manipolazione di HTML, ispirata a jQuery, ma ottimizzata per l'utilizzo in ambienti server-side con Node.js.
  • csv-writer è una libreria Node.js che semplifica la scrittura di dati in formato CSV (Comma-Separated Values).

Estrazione dati

Il primo passo da eseguire è quello di prelevare la regola CSS per prelevare il testo contenuto all'interno dell'elemento <span>. La regola che possiamo individuare è: li a .text.

Quindi per poter eseguire dei cicli su tutti gli elementi dell'elenco puntato, possiamo eseguire il seguente codice:

$('li a .text').each((index, element) => {
    const text = $(element).text();
    console.log(text);
});

Il precedente codice stampa su console le nazioni presenti sul nostro file. Tuttavia ogni riga ha due componenti, la sigla e il nome, come segue IT: Italia. La fortuna è che questa struttura si ripete per ogni riga. Possiamo quindi prelevare le informazioni, memorizzate nella costante text, come segue:

const [abbreviation, name] = text.split(': ').map(item => item.trim());

adesso abbreviation memorizza la sigla e name il nome della nazione.

A questo punto possiamo eseguire la memorizzazione delle nazioni all'interno di un array, come segue:

// Definizione dell'array
const nations = [];

$('li a .text').each((index, element) => {
    const text = $(element).text();
    const [abbreviation, name] = text.split(': ').map(item => item.trim());
    // Memorizzazione nell'array come oggetto
    nations.push({ abbreviation, name });
});

Il precedente codice estrae le nazioni dall'HTML e le memorizza nel javascript. Stampando l'array otterremo:

[
  { abbreviation: 'AL', name: 'Albania' },
  { abbreviation: 'AD', name: 'Andorra' },
  { abbreviation: 'AM', name: 'Armenia' },
]

Scrittura su file CSV

A questo punto del codice, all'interno di nations abbiamo tutte le nazioni memorizzate nella struttura dati mostrata. Ora è il momento di creare il file csv.

Per fare ciò, inizializziamo un oggetto csvWriter, come segue:

const OUTPUT_FILEPATH = '/app/output/nazioni.csv';

const csvWriter = createCsvWriter({
    path: OUTPUT_FILEPATH,
    header: [
        { id: 'abbreviation', title: 'ABBREVIATION' },
        { id: 'name', title: 'NAME' }
    ]
});

Nel frammento di codice si nota che il path sul quale sarà scritto il file è indicato nella costante OUTPUT_FILEPATH. Inoltre, il file csv prodotto avrà una prima riga di header, all'interno della quale saranno scritti i nomi delle colonne.

Con l'oggetto csvWriter creato, possiamo scrivere gli elementi dell'array, come segue:

csvWriter.writeRecords(nations)
    .then(() => {
        console.log('File CSV creato con successo');
    });

Al termine della scrittura del file csv, il programma notifica l'avvenuta scrittura del file.

Script completo

Possiamo riportare lo script completo per l'estrazione dei dati, che deve essere memorizzato nel file src/extract.js:

const INPUT_FILEPATH = '/app/input/nazioni.html';
const OUTPUT_FILEPATH = '/app/output/nazioni.csv';

const fs = require('fs');
const cheerio = require('cheerio');
const createCsvWriter = require('csv-writer').createObjectCsvWriter;

// Leggi il file HTML
const html = fs.readFileSync(INPUT_FILEPATH, 'utf8');
const $ = cheerio.load(html);

const nations = [];

// Estrai le informazioni
$('li a .text').each((index, element) => {
    const text = $(element).text();
    const [abbreviation, name] = text.split(': ').map(item => item.trim());
    nations.push({ abbreviation, name });
});

// Configura il writer CSV
const csvWriter = createCsvWriter({
    path: OUTPUT_FILEPATH,
    header: [
        { id: 'abbreviation', title: 'ABBREVIATION' },
        { id: 'name', title: 'NAME' }
    ]
});

// Scrivi i dati nel file CSV
csvWriter.writeRecords(nations)
    .then(() => {
        console.log('File CSV creato con successo');
    });

Esecuzione dello script usando Docker

Per eseguire lo script implementato, configuriamo un ambiente isolato usando Docker.

La configurazione di un ambiente Docker per eseguire codice usando Node.js prevede:

  1. Creazione del Dockerfile
  2. Creazione del package.json
  3. Creazione del docker-compose.yml
  4. Creazione cartelle locali
  5. Build e Up del container

1. Creazione Dockerfile

Crea un file chiamato Dockerfile, all'interno della cartella principale del tuo progetto, aggiungendo il seguente contenuto:

# Usa un'immagine base con Node.js
FROM node:20

# Imposta la directory di lavoro all'interno del container
WORKDIR /app

# Copia il file package.json e package-lock.json (se presente)
COPY package*.json ./

# Installa le dipendenze del progetto
RUN npm install

# Copia il contenuto della cartella contenente il codice sorgente sul container
COPY src /app/src

2. Creazione package.json

Crea un file chiamato package.json, all'interno della cartella principale del tuo progetto, con il seguente contenuto:

{
  "name": "html-extractor",
  "version": "1.0.0",
  "description": "Script per estrarre dati HTML e creare un file CSV",
  "scripts": {
    "start": "node src/extract.js"
  },
  "dependencies": {
    "cheerio": "^1.0.0-rc.10",
    "csv-writer": "^1.6.0"
  }
}

3. Creazione docker-compose.yml

Crea un file chiamato docker-compose.yml, all'interno della cartella principale del tuo progetto, con il seguente contenuto:

services:
  nodeapp:
    build: .
    volumes:
      - ./input:/app/input
      - ./output:/app/output
    command: npm start

come si evince il docker-compose.yml imposta due volumi sulle cartelle locali input e output.

4. Creazione cartelle locali

Prima di avviare il container, occorre creare le cartelle che saranno montate sui volumi Docker. Quindi puoi creare all'interno della cartella principale le cartelle input e output.

La struttura delle cartelle prima dell'esecuzione del container dovrà essere come segue:

cartella_progetto
|- Dockerfile
|- docker-compose.yml
|- package.json
|- input
|- output
|- src
    |- extract.js

All'interno della cartella di input devi inserire il file delle nazioni che lo script leggerà. Di default nell'esempio il file l'ho chiamato nazioni.html ed il contenuto è il seguente:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Nazioni Europee</title>
</head>
<body>
    <h1>Nazioni Europee</h1>
    <ul class="dropdown-menu inner show" role="presentation" style="margin-top: 0px; margin-bottom: 0px;">
        <li><a role="option" class="dropdown-item" id="bs-select-1-0" tabindex="0"><span class="text">AL: Albania</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-1" tabindex="0"><span class="text">AD: Andorra</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-2" tabindex="0"><span class="text">AM: Armenia</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-3" tabindex="0"><span class="text">AT: Austria</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-4" tabindex="0"><span class="text">AZ: Azerbaijan</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-5" tabindex="0"><span class="text">BY: Belarus</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-6" tabindex="0"><span class="text">BE: Belgium</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-7" tabindex="0"><span class="text">BA: Bosnia and Herzegovina</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-8" tabindex="0"><span class="text">BG: Bulgaria</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-9" tabindex="0"><span class="text">HR: Croatia</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-10" tabindex="0"><span class="text">CY: Cyprus</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-11" tabindex="0"><span class="text">CZ: Czech Republic</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-12" tabindex="0"><span class="text">DK: Denmark</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-13" tabindex="0"><span class="text">EE: Estonia</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-14" tabindex="0"><span class="text">FI: Finland</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-15" tabindex="0"><span class="text">FR: France</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-16" tabindex="0"><span class="text">GE: Georgia</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-17" tabindex="0"><span class="text">DE: Germany</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-18" tabindex="0"><span class="text">GR: Greece</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-19" tabindex="0"><span class="text">HU: Hungary</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-20" tabindex="0"><span class="text">IS: Iceland</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-21" tabindex="0"><span class="text">IE: Ireland</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-22" tabindex="0"><span class="text">IT: Italy</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-23" tabindex="0"><span class="text">KZ: Kazakhstan</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-24" tabindex="0"><span class="text">LV: Latvia</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-25" tabindex="0"><span class="text">LI: Liechtenstein</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-26" tabindex="0"><span class="text">LT: Lithuania</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-27" tabindex="0"><span class="text">LU: Luxembourg</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-28" tabindex="0"><span class="text">MT: Malta</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-29" tabindex="0"><span class="text">MD: Moldova</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-30" tabindex="0"><span class="text">MC: Monaco</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-31" tabindex="0"><span class="text">ME: Montenegro</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-32" tabindex="0"><span class="text">NL: Netherlands</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-33" tabindex="0"><span class="text">MK: North Macedonia</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-34" tabindex="0"><span class="text">NO: Norway</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-35" tabindex="0"><span class="text">PL: Poland</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-36" tabindex="0"><span class="text">PT: Portugal</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-37" tabindex="0"><span class="text">RO: Romania</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-38" tabindex="0"><span class="text">RU: Russia</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-39" tabindex="0"><span class="text">SM: San Marino</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-40" tabindex="0"><span class="text">RS: Serbia</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-41" tabindex="0"><span class="text">SK: Slovakia</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-42" tabindex="0"><span class="text">SI: Slovenia</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-43" tabindex="0"><span class="text">ES: Spain</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-44" tabindex="0"><span class="text">SE: Sweden</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-45" tabindex="0"><span class="text">CH: Switzerland</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-46" tabindex="0"><span class="text">TR: Turkey</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-47" tabindex="0"><span class="text">UA: Ukraine</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-48" tabindex="0"><span class="text">GB: United Kingdom</span></a></li>
        <li><a role="option" class="dropdown-item" id="bs-select-1-49" tabindex="0"><span class="text">VA: Vatican City</span></a></li>
    </ul>
</body>
</html>

Build e Up del container

L'esecuzione dello script per l'estrazione del file csv prevede l'esecuzione del comando:

docker-compose up --build

se l'esecuzione va a buon fine, nella cartella output troverai il file nazioni.csv.

Conclusioni

Nell'articolo hai visto come poter estrarre informazioni dalle pagine HTML memorizzando le stesse su file CSV. Queste operazioni sono molto utili per poter eseguire attività di reportistica o di semplice scambio di dati tra applicazioni diverse.

L'articolo ha basato l'implementazione del codice sul linguaggio di programmazione Javascript su ambiente Docker.