L'immagine social di Napoli: sulle tracce della stigmatizzazione mediatica di una città.

Strumenti adoperati
Raccolta dati Analisi dati
Instagram
4CAT
Zeeschuimer
Docker
Vedi la sezione Costruzione per maggiori informazioni
Kaggle Notebook
Python (librerie principali: torch, matplot, pandas, numpy, spacy, nltk, transformers)
Corso di Sociologia digitale (AA 2024/25) a cura di Davide Bennato e Guido Anselmi
Corso di Linguistica computazionale (AA 2024/25) a cura di Misael Mongiovì
Università di Catania, Aprile 2025

Clicca qui per il codice completo

Successivamente alla pandemia da Covid-19, Napoli ha registrato un sensibile aumento dei flussi turistici in entrata – con oltre 5 milioni di visitatori annui, secondo dati ENIT – diventando una tra le più ambite mete del sud Italia.

Questo fenomeno trova ovviamente riscontro sulle piattaforme social, prima fra tutte Instagram, principale spazio virtuale in cui agisce la costruzione dal basso delle narrazioni e delle immagini nel mondo contemporaneo.

Il linguaggio turistico è un potente strumento capace di plasmare la realtà, pertanto questo studio si occupa di ponderare l'influsso di quest'ultimo sull'identità della città partenopea attraverso gli strumenti del data-mining e dell'analisi statistica applicata al testo.

In questo quadro, si parte da una definizione di stigmatizzazione affine all'orientamento dei Cultural Studies, ossia come di un fenomeno che cristallizza l'identità di un popolo, luogo o cultura – con accezioni tanto positive quanto negative. Lo studio esposto di seguito si propone di verificare se la stigmatizzazione mediatica persista come tendenza prevalente nei contenuti social legati a Napoli.

Per farlo si è proceduto secondo i seguenti passaggi:

  • Costruzione del dataset
  • Pulizia del dataset
  • Sentiment analysis
  • Analisi linguistica

Costruzione del dataset

Per lo studio è stato creato un profilo apposito, il meno possibile profilato. Sono state scelte 8 query da lanciare nella sezione "Per te" di Instagram: #igersnapoli, #napoletani, #napoletanità, #napoli, #napolicolera, #napolidavivere, #napolimerda, #vesuviolavalicolfuoco.

Queste sono state scelte in maniera da ottenere un dataset quanto più possibile bilanciato tra contenuti presumibilmente dispreggiativi e presumibilmente promozionali/positivi.

Per le operazioni di scraping si è fatto utilizzo dell'estensione Zeeschuimer messa a disposizione da Digital Methods sul framework open-source 4CAT. Docker è stato utilizzaro per creare un ambiente isolato, senza la necessità di installare il sistema Ubuntu, ma sfruttando un container preconfigurato.

Dal dataset risultante - in formato .ndjson - sono state selezionate solo le chiavi interessanti per lo studio: id del post, numero di like, numero di commenti, username dell'autore, se quest'ultimo è o meno un account verificato da Meta, caption, riconoscimento dell'immagine tramite Google Vision API.

Per farlo si è definita la seguente funzione:

def json_todict (file_path):
  hashposts = []
  with open(file_path, 'r') as file:
    reader = ndjson.reader(file)
    for obj in reader:
      dict_select = {}
      dict_select['id'] = obj['id']
      dict_select['likes'] = obj['like_count']
      dict_select['comments'] = obj['comment_count']
      dict_select['username'] = obj['user']['username']
      dict_select['verified'] = obj['user']['is_verified']

      try:
        caption = obj['caption']['text']
        text, hashtags = caption_split(caption)
      except:
        text = None
        hashtags = None
      dict_select['text'] = text
      dict_select['hashtags'] = hashtags

      try:
        recognition = obj['accessibility_caption']
      except:
        try:
          recognition = obj['carousel_media'][0]['accessibility_caption']
        except:
          recognition = None
      dict_select['image_recognition'] = recognition

      hashposts.append(dict_select)
  return hashposts

Pulizia del dataset

Il dataset ottenuto contiene ancora post senza caption o con caption vuota e condivisioni di uno stesso post. Perciò ci si è innanzitutto premurati di eliminare le righe corrispondenti a questi casi.

Si è ottenuto così un dataset di 7584 post, da cui è stato necessario eliminare qualsiasi post che esuli dagli scopi dello studio, primariamente tutti quei post relativi al mondo del calcio.

Per individuarli si sono utilizzati dei pattern Regex del tipo

pattern = r'soccer|football'

hashtags_select = ["championsleague", "calcio", "football", "sscn", "universocalcistico", "forzanapoli"]
pattern = r'|'.join(hash for hash in hashtags_select)

Il primo è stato applicato alla colonna "image_recognition" e il secondo alla colonna "hahstags".

Sentiment Analysis

Per la sentiment analysis è stato scelto il modello preaddestrato twitter-xlm-roberta-base-sentiment (clicca qui per maggiori informazioni sul modello).

Esso è stato lanciato su tutte le caption restituendo per ognuna un label (positive/negative/neutral) e un indice da 0 a 1 che rappresenta la sicurezza con cui il modello attribuisce il label.

Poiché ai fini dello studio non interessano i post taggati con sentiment neutral, essi sono stati eliminati. Dopo aver filtrato per score=>0.6 si è ottenuta la seguente distribuzione di post con sentiment positivo e post con sentiment negativo:

                              num_pos  num_neg
igersnapoli.ndjson                158       47
napoletani.ndjson                 102       62
napoletanit�.ndjson               234       57
napoli.ndjson                      78       25
napolicolera.ndjson               127      364
napolidavivere.ndjson             204       37
napolimerda.ndjson                272      300
vesuviolavalicolfuoco.ndjson       14       13
totale                             1.189    905

Oltre a confermare con una certa nettezza la previsione iniziale del sentiment delle singole query, la tabella sovrastante dimostra anche che il datset rispetta il crterio del bilanciamento sotto il punto di vista del sentiment.

Analisi testuale

Dopo aver effettuato un'ulteriore pulizia del dataset - filtrando per caption con più testo che emojii - è stato istanziato il modello all-mpnet-base-v2 di SentenceTransformer con cui si è calcolata la similarità tra tutti i post positivi tra loro e tutti i post negativi, ottenendo così due tensori aventi tante righe e tante colonne quanti sono rispettivamente i post con sentiment positivo e i post con sentiment negativo.

I due tensori sono stati resi sottoforma di matrici attraverso matplotlib.

pos_similarity
pos_similarity
neg_similarity
neg_similarity

Poiché i grafici sono in grado di dare solo un'idea generale della coerenza interna dei due dataset (positivo e negativo), si è proceduto col calcolo della deviazione standard, più appropriata per un confronto lampante e scientifico.

def inner_coherence(clipped_similarity):
    sim_np = clipped_similarity.cpu().numpy()
    values = sim_np[sim_np != -1]
    std_sim = np.std(values, ddof=0)
    return std_sim

[...]

Il dataset dei post con sentiment positivo hanno deviazione standard = 0.1399

[...]

Il dataset dei post con sentiment positivo hanno deviazione standard = 0.1251

Ciò vuol dire che tanto i post positivi, quanto quelli negativi hanno un'alta coerenza interna, dovuta certo in parte all'utilizzo dello stesso canale comunicativo - che porta con sé affordances (o culture di utilizzo) e un certo stile. Ma in parte ciò conferma la tesi iniziale: che la costruzione dell'identità della città di Napoli è intrisa di stigma, nel bene come nel male.

Fissato un intervallo compreso tra 1.0 e 0.8, lo si è applicato ai tensori per selezionare solo i post tra loro più simili (esclusa ovviamente la diagonale della matrice - che contiene il calcolo della similarità tra una caption e se stessa).

Per procedere ad un'analisi linguistica più approfondita si è reso necessario scegliere una sola lingua, poiché Spacy dispone di modelli multilingua, che però non offrono tutte le features necessarie. Il numero di post si è ridotto quindi a 133 per i positivi e 163 per i negativi.

Si è proceduto a definire la funzione text_analysis che è servita ad applicare il modello di Spacy ad entrambi i dataset, restituendo per ogni caption: la lista delle entità, la lista dei token, e per ogni token, lemma, POS, morfologia e figli diretti

def text_analysis (most_similar_it):
    diz = dict()
    nlp = spacy.load('it_core_news_lg')
    for i, t in enumerate(most_similar_it):
      doc = nlp(t)
      token_info = []
      ent_info = []
      for token in doc:
        token_info.append({"token":token.text, "lemma":token.lemma_, "pos":token.pos_, "morph":str(token.morph) if str(token.morph) != "" else "NOT_FOUND", "children":[child.text for child in token.children]})
      for ent in doc.ents:
        ent_info.append((ent.text, ent.label_))
      diz[i]={"doc":doc, "tokens":token_info, "entities":ent_info}
    return diz

Con una semplice funzione di tipo contatore abbiamo ottenuto la seguente lista di tuple, ciascuna avente struttura ((entity, label),occorrenze).

Top-entities dei post con sentiment positivo:

(('Napoli', 'LOC'), 79)
(('Milan', 'ORG'), 13)
(('Napoli', 'ORG'), 11)
(('❤️ Lascia like ✔️Seguimi per notizie ogni giorno', 'MISC'), 11)
(('📲 Condividi', 'MISC'), 11)
(("Umberto I d'Italia", 'PER'), 5)
(('Re', 'PER'), 5)

Top-entities dei post con sentiment negativo:

(('Napoli', 'LOC'), 139)
(('Italia', 'LOC'), 42)
(('Vaffanculo', 'LOC'), 22)
(('Napoli', 'ORG'), 17)
(('cimitero delle Fontanelle', 'LOC'), 12)
(('Juventus', 'ORG'), 7)
(('Senti', 'PER'), 7)
(('Maradona', 'PER'), 7)
(('Diego', 'PER'), 7)

I risultati portano a tre conclusioni di natura tecnica:

Con un'altra funsione atta a percorrere i dizionari ottenuti dalla funzione text_analysis, e selezionati token con senso lessicale pieno, si sono ottenuti i seguenti risultati:

Nelle caption con sentiment positivo Napoli è
head di: ['nobilissima', 'colera', 'tempi', 'vivere', 'sotterranea', 'napolidavivere', 'campione', 'finestra', 'unica', 'Scoprendo', 'ammirando', 'dintorni', 'arte', 'Ritrovata', 'casa', 'contro', 'core']

child di: ['amore', 'Nata', 'Complimenti', 'Veduta', 'scomparsa', 'abbatte', 'prende', 'promessa', 'sentimento', 'contraddizione', 'capisci', 'Vedi', 'Abbiamo', 'passo', 'Complesso', 'bellezza', 'Via', 'BUONA', 'Galleria', 'città', '️Maison', 'cuore', 'costruita', 'bella', 'Galleria', 'maestosi', 'miracolo', 'Vedi', 'luce', 'diversa', 'rimasta', 'sogno', 'Vacanza', 'meta', 'Grazie', 'entra', 'Napoli', 'vivere', 'conoscere', 'tappa', 'Tours', 'quartieri', 'magia', 'rendere', 'primo', 'costa', 'Golfo', 'città', 'cuore', 'godendo', 'creare', 'amicx', 'dedicata', 'amore', 'Napoli', 'weekend', 'portare', 'balcone', 'vedere', 'angolo', 'difficile', 'racconta', 'emozione']
Nelle caption con sentiment negativo Napoli è
head di: ['merda', 'colera', 'Vaffanculo', 'epidemia', 'bellissima', 'straordinaria', 'miseria', 'vergogna', 'scendeva', 'sotterranea']

child di: ['città', 'sfida', 'Complesso', 'centro', 'fontanelle', 'Vedi', 'multa', 'complimento', 'scritto', 'viene', 'vincere', 'Scuole', 'Epidemia', 'Cotugno', 'vergogna', 'togliere', 'amministrazione', 'colpita', 'morti', 'colera', 'schiaffi', 'lutto', 'schierare', 'arrivando', 'capace', 'Odio', 'accadde', 'medici', 'rovina', 'colpita', 'subisce', 'Vaccinazioni', 'palle', 'popolazione', 'vergogna', 'afflitta', 'diede', 'rovina', 'sfida', 'caratteristiche', 'sbalordito', 'lavati', 'dolore', 'nato', 'schifati', 'parola', 'tornato', 'rinascerei', 'scattata', 'Scegliesti', 'scendevano', 'epicentro', 'casi', 'paese', 'amato', 'sopranominato', 'manifestazione', 'Sanità', 'vergogna']

Dai figli diretti di "Napoli" nei post con sentiment negativo emergono anche diversi token relativi la pandemia da Covid-19; mentre nei figli di "Napoli" dal dataset dei post 'positivi' emerge chiaramente il tema del viaggio turistico che ha come meta la città oggetto di studio.

Conclusioni

Considerata:

dallo studio condotto si può dedurre che la stigmatizzazione gioca un ruolo fondamentale nella costruzione dell'immagine di Napoli, sia nei casi di sentiment negativo che positivo.

Come evidenziato nella sezione Analisi testuale, la deviazione standard dei testi nei due dataset (positivo e negativo) risulta essere notevolmente bassa, con valori rispettivamente di 0.1399 e 0.1251.

I futuri sviluppi potrebbero includere: