Nel refactoring di un ecommerce, ci sono due tipi di problemi:
Tutti conosciamo la teoria delle migrazioni perfette: ambienti di staging, backup ridondanti, controlli QA rigorosi. E poi c’è la realtà.
Recentemente mi sono trovato in uno scenario da incubo durante il refactoring di un e-commerce (migrazione da WooCommerce a Next.js + LunarPHP).
Durante la migrazione, un freelance si occupa dell’importazione dei dati: prodotti, varianti, prezzi, categorie, media. Alla consegna facciamo controlli preliminari: tutto sembra ok, il freelance si congeda, passano settimane, poi mesi, si va online.
Dopo il go-live, ci accorgiamo che qualcosa non torna. Un gruppo di prodotti, nell’ordine delle migliaia, risulta senza immagini.
E nel commercio elettronico, un prodotto senza immagine è praticamente un prodotto invisibile:
A quel punto iniziano le domande: recuperiamo dal vecchio hosting? Chiediamo al freelance di rigenerare la migrazione?
Peccato che: il contratto con il vecchio hosting era stato disattivato e quindi sorgenti e gli upload non erano più accessibili, il freelance era impegnato su altro e quindi… sparito.
La risposta classica sarebbe:
Ma su migliaia di SKU non è una soluzione. Serviva qualcosa di diverso. Una soluzione creativa disponibile in poche ore, al massimo giorni, non settimane.
Automatizzare l’impossibile usando PHP, Google Custom Search e Computer Vision.
Ma c’è un problema: cercare su Google non è semplice come sembra. Non è sempre tutto oro quel che luccica nelle prime posizioni.
Se cerchi PRODOTTO-123 Bracciale Argento potresti non trovare nulla. Magari Google ha indicizzato il prodotto, ma senza il codice SKU nel titolo. Oppure ha lo SKU, ma il nome è leggermente diverso.
Per questo, nel mio GoogleSearchService, non mi sono limitato a una singola query “secca”. Ho implementato una strategia a 4 livelli di profondità (Fallback Strategy) per “scavare” nella SERP finché non emerge qualcosa di utile.
Ecco come ragiona l’algoritmo quando cerca un’immagine:
SKU + Nome Prodotto. È la situazione ideale, ma spesso fallisce se il vecchio sito non aveva un buon SEO o se lo SKU è interno._ o / in eccesso.Solo combinando questi quattro approcci siamo riusciti a passare dal “Nessun risultato trovato” a una lista di candidati plausibili per il 90% dei prodotti mancanti.
Trovare l’immagine è solo metà dell’opera. Scaricare l’immagine sbagliata è peggio che non averne nessuna. Come distinguere un “Bracciale Pandora” dal “Vaso di Pandora” o dal pianeta di Avatar?
Ho creato un GoogleVisionService che funge da giudice imparziale:
['Jewelry', 'Silver', 'Bracelet']).0.7 (o il 70% di confidenza), l’immagine è approvata.L’idea è stata usare Google Vision AI come “semantic gatekeeper”, quindi non come riconoscitore generico, ma come filtro semantico automatico:
“Questa immagine appartiene davvero al contesto del prodotto?”
Poiché Google Vision restituisce etichette in inglese, abbiamo usato DeepL (anche questo strumento è AI based) come traduttore in tempo reale degli attributi del prodotto. Questo ci ha permesso di confrontare “Silver” (etichetta AI) con “Argento” (attributo colore presente a DB), garantendo che il match semantico non andasse perso nella traduzione. Un saluto a Silver, il creatore di Lupo Aberto, che grazie a questo hack non è finito in mezzo alle foto dei prodotti.
Quando automatizzi il download di immagini da Google, il rischio “contenuti inappropriati” è dietro l’angolo. Nel codice ho integrato un filtro basato sulla SafeSearch detection di Vision AI. Se l’algoritmo rileva una probabilità elevata di contenuti adult, violence o racy, l’immagine viene scartata a prescindere dal punteggio di pertinenza. Automatizzare sì, ma con la cintura di sicurezza allacciata.
NB: Se ti basta quanto hai letto nella Fase 2, puoi passare alla Fase 3.
Per ogni immagine candidata, Google Cloud Vision non “indovina il prodotto”, ma restituisce un set di etichette (label) con un livello di confidenza. Questo output è perfetto per trasformare Vision in un filtro semantico: non cerco la verità assoluta, cerco indizi coerenti col contesto.
Ecco un esempio (ridotto) di risposta per una foto che Vision interpreta come gioielleria:
{
"responses": [
{
"labelAnnotations": [
{ "description": "Jewelry", "score": 0.92, "topicality": 0.92 },
{ "description": "Bracelet", "score": 0.88, "topicality": 0.88 },
{ "description": "Silver", "score": 0.84, "topicality": 0.84 },
{ "description": "Fashion accessory", "score": 0.79, "topicality": 0.79 }
],
"safeSearchAnnotation": {
"adult": "VERY_UNLIKELY",
"violence": "VERY_UNLIKELY",
"racy": "UNLIKELY"
}
}
]
}
Due dettagli importanti:
Il catalogo e gli attributi (colore/materiale/tipologia) erano in italiano: “bracciale”, “argento”, “ciondolo”. Vision mi rispondeva con “bracelet”, “silver”, “jewelry”. Qui entra in gioco DeepL: traduco in tempo reale (o con cache) un set di keyword “core” e confronto mele con mele.
In pratica genero un vocabolario atteso in inglese (derivato da nome prodotto + attributi) e poi uso le label Vision come evidenze.
Un match string-based puro è fragile. Quello che ha funzionato è un piccolo modello di scoring:
Ecco uno snippet (semplificato) che rende l’idea:
$expected = ['jewelry', 'bracelet', 'silver', 'necklace', 'ring']; // da prodotto/attributi via DeepL
$forbidden = ['vase', 'mythology', 'logo', 'software', 'planet']; // contesti “trappola”
$labels = collect($visionLabels) // es. da labelAnnotations
->sortByDesc(fn($l) => $l['topicality'] ?? $l['score'])
->take(8)
->map(fn($l) => strtolower($l['description']))
->values();
$score = 0.0;
// Reward/Penalty semplice ma efficace
foreach ($labels as $label) {
if (in_array($label, $expected, true)) {
$score += 0.18; // reward
}
if (in_array($label, $forbidden, true)) {
$score -= 0.45; // penalty forte: evita disastri tipo “Pandora = vaso”
}
}
// Bonus se Vision è molto “sicuro” della prima label
$top = $visionLabels[0] ?? null;
if ($top && (($top['score'] ?? 0) >= 0.90)) {
$score += 0.10;
}
// SafeSearch: cintura di sicurezza
if ($safeSearch['adult'] === 'LIKELY' || $safeSearch['adult'] === 'VERY_LIKELY') {
return ['ok' => false, 'score' => $score, 'reason' => 'SafeSearch: adult'];
}
return [
'ok' => $score >= 0.70,
'score' => round($score, 2),
'labels' => $labels->all(),
];
La cosa interessante non è tanto il valore delle costanti (che ho tarato per tentativi), ma l’idea: l’AI produce feature (label), e io costruisco sopra una policy deterministica che gestisce ambiguità semantiche. “Pandora” diventa un caso gestibile: gioiello vs mito vs altro.
Una volta validata l’immagine, non volevo solo “attaccarla al prodotto”. Volevo anche poter rispondere, mesi dopo, a domande del tipo:
Qui Spatie Media Library è perfetta: offre un’API fluida per associare file ai modelli Eloquent e, soprattutto, permette di salvare metadati strutturati tramite withCustomProperties() (JSON), recuperabili e ispezionabili in modo pulito.
Nel mio caso ho salvato sia l’evidenza (Vision) sia la provenienza (Search):
$variantModel->addMedia($tempFile)
->withCustomProperties([
'image_source' => 'google',
'google_search' => [
'query' => $query,
'url' => $imageUrl,
'rank' => $rank,
],
'validation' => [
'score' => $decision['score'],
'labels' => $decision['labels'],
'safe_search' => $safeSearch,
'approved_at' => now()->toIso8601String(),
],
])
->toMediaCollection('images');
Il vantaggio pratico è enorme: se domani devo “spiegare” un’immagine o rifare una parte del processo, ho tutto in chiaro nei metadati del media item (e posso persino costruire strumenti interni per filtrare/ricontrollare le immagini già importate).
In altre parole: non ho solo ripopolato un catalogo. Ho costruito un processo ripetibile, tracciabile, e difendibile.
Lanciando il comando products:fetch-variant-images, il terminale ha iniziato a raccontare una storia:
🔍 Processing SKU: 123-PND...⚠️ Query completa: 0 risultati.🔄 Fallback #2 (Solo Nome): Trovate 5 immagini.🔎 Validating image #1... Score: 0.15 (Rejected: Low relevance)🔎 Validating image #2... Score: 0.92 (Validated!)✅ Image attached.L’algoritmo ha “ragionato” come un umano: ha provato a cercare in modo specifico, poi ha allargato il campo, e infine ha usato gli occhi (digitali) per scartare i falsi positivi.
Questa è stata la parte più interessante del progetto: Google Vision non ha “risolto tutto”.
Ma ha permesso di costruire un sistema di cernita automatica:
Una pipeline ingegneristica completa.
L’AI nel web development non serve solo a generare codice. In questo caso, l’uso combinato di Logica di Fallback (per la ricerca) e Computer Vision (per la validazione) ha trasformato un disastro annunciato in un catalogo ripopolato automaticamente.
A volte, quando perdi i sorgenti, sei costretto a scrivere codice migliore di quello che avevi prima.