2025-05-16 18:52:21 +02:00
const axios = require ( 'axios' ) ;
const cheerio = require ( 'cheerio' ) ;
const Product = require ( '../models/product' ) ;
const ProductInfo = require ( '../models/productInfo' ) ;
const tools = require ( '../tools/general' ) ;
2025-05-19 17:33:58 +02:00
const shared _consts = require ( '../tools/shared_nodejs' ) ;
const fs = require ( 'fs' ) . promises ; // 👈 Usa il modulo promises
2025-05-16 10:26:55 +02:00
class AmazonBookScraper {
constructor ( ) {
this . baseUrl = 'https://www.amazon.it/dp/' ;
}
2025-05-16 18:52:21 +02:00
async fetchPageISBN10 ( isbn10 ) {
const url = ` ${ this . baseUrl } ${ isbn10 } ` ;
2025-05-16 10:26:55 +02:00
try {
const { data } = await axios . get ( url , {
headers : {
'User-Agent' :
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' +
'AppleWebKit/537.36 (KHTML, like Gecko) ' +
'Chrome/113.0.0.0 Safari/537.36' ,
// altri header se necessario
} ,
} ) ;
2025-05-19 17:33:58 +02:00
return { html : data , url } ;
2025-05-16 10:26:55 +02:00
} catch ( err ) {
2025-05-16 18:52:21 +02:00
console . error ( ` Errore fetching ISBN ${ isbn10 } : ` , err . message ) ;
return null ;
}
}
isbn13to10 ( isbn13 ) {
try {
if ( ! isbn13 . startsWith ( '978' ) || isbn13 . length !== 13 ) return null ;
const core = isbn13 . slice ( 3 , 12 ) ; // i 9 numeri centrali
let sum = 0 ;
for ( let i = 0 ; i < 9 ; i ++ ) {
sum += ( 10 - i ) * parseInt ( core [ i ] , 10 ) ;
}
let check = 11 - ( sum % 11 ) ;
if ( check === 10 ) check = 'X' ;
else if ( check === 11 ) check = '0' ;
else check = check . toString ( ) ;
return core + check ;
} catch ( err ) {
console . error ( 'Errore calcolando ISBN-10 da ISBN-13:' , err . message ) ;
2025-05-16 10:26:55 +02:00
return null ;
}
}
2025-05-16 18:52:21 +02:00
getTitleByProductInfo ( productInfo ) {
try {
return productInfo ? . name . trim ( ) ;
} catch ( e ) {
return '' ;
}
}
2025-05-19 17:33:58 +02:00
async extractData ( myproduct , html , url ) {
2025-05-16 10:26:55 +02:00
const $ = cheerio . load ( html ) ;
2025-05-16 18:52:21 +02:00
const productInfo = await ProductInfo . findOne ( { _id : myproduct . idProductInfo } ) . lean ( ) ;
const title _ondb = this . getTitleByProductInfo ( productInfo ) ;
2025-05-16 10:26:55 +02:00
// Titolo
let title = $ ( '#productTitle' ) . text ( ) . trim ( ) || null ;
2025-05-16 18:52:21 +02:00
// Sottotitolo (Amazon lo mette nel titolo)
2025-05-16 10:26:55 +02:00
2025-05-16 18:52:21 +02:00
let dettagli = $ ( '#productSubtitle' ) . text ( ) . trim ( ) || null ;
2025-05-16 10:26:55 +02:00
2025-05-16 18:52:21 +02:00
const ris = this . extractSubtitle ( title , title _ondb ) ;
2025-05-16 10:26:55 +02:00
2025-05-16 18:52:21 +02:00
let sottotitolo = title _ondb ? ris ? . subtitle : '' ;
2025-05-16 10:26:55 +02:00
2025-05-16 18:52:21 +02:00
let titoloOriginale = '' ;
2025-05-16 10:26:55 +02:00
2025-05-16 18:52:21 +02:00
if ( ris ? . titoloOriginale ) {
// E' presente il vero titolo del Libro !
titoloOriginale = ris . titoloOriginale ;
2025-05-16 10:26:55 +02:00
}
2025-05-16 18:52:21 +02:00
if ( sottotitolo && title _ondb ) {
// significa che il titolo è dentro al sottotitolo, quindi prendi per buono quello nostro
title = title _ondb ;
}
let numpagine = null ;
let misure = null ;
let edizione = null ;
let publisher = null ;
let data _pubblicazione = null ;
numpagine = this . extractNumeroDiPagine ( $ ) ;
const dim = this . extractDimensions ( $ ) ;
misure = this . convertDimensionsToMisureMacro ( dim ) ;
const data _pubb = this . extractDataPubblicazione ( $ ) ;
data _pubblicazione = this . parseItalianDate ( data _pubb ) ;
publisher = this . extractEditore ( $ ) ;
if ( data _pubb ) {
edizione = this . extractMonthYear ( data _pubb ) ;
}
return {
titolo : title ,
... ( titoloOriginale ? { titoloOriginale } : { } ) ,
2025-05-19 17:33:58 +02:00
... ( sottotitolo ? { sottotitolo } : { sottotitolo : '' } ) ,
... ( numpagine ? { numpagine } : { } ) ,
... ( misure ? { misure } : { } ) ,
... ( edizione ? { edizione } : { } ) ,
... ( data _pubblicazione ? { data _pubblicazione } : { } ) ,
... ( publisher ? { editore : publisher } : { } ) ,
url : ` <a href=" ${ url } " target="_blank">URL</a> ` ,
2025-05-16 18:52:21 +02:00
} ;
2025-05-16 10:26:55 +02:00
}
2025-05-16 18:52:21 +02:00
async scrapeISBN ( myproduct , isbn , options ) {
2025-05-19 17:33:58 +02:00
try {
const isbn10 = this . isbn13to10 ( isbn ) ;
const res = await this . fetchPageISBN10 ( isbn10 ) ;
if ( ! res ) {
await Product . findOneAndUpdate (
{ _id : myproduct . _id } ,
{ $set : { scraped : true , scraped _error : true } } ,
{ upsert : true , new : true , includeResultMetadata : true }
) . lean ( ) ;
return null ;
}
const html = res . html ;
if ( ! html ) return null ;
2025-05-16 10:26:55 +02:00
2025-05-19 17:33:58 +02:00
let updated = null ;
let risupdate = null ;
2025-05-16 18:52:21 +02:00
2025-05-19 17:33:58 +02:00
const data = await this . extractData ( myproduct , html , res . url ) ;
2025-05-16 18:52:21 +02:00
2025-05-19 17:33:58 +02:00
if ( ! options ? . update ) return data ;
2025-05-16 18:52:21 +02:00
2025-05-19 17:33:58 +02:00
let recModificato = { } ;
const arrvariazioni = myproduct . arrvariazioni || [ ] ;
let index = - 1 ;
if ( arrvariazioni . length === 1 ) {
index = 0 ;
} else {
index = arrvariazioni . findIndex ( ( v ) => v . versione === shared _consts . PRODUCTTYPE . NUOVO ) ;
if ( index < 0 ) index = 0 ;
}
const productInfo = { } ;
let aggiornaDataPubb = false ;
let aggiornaPages = false ;
let aggiornaMisure = false ;
let aggiornaEdizione = false ;
let aggiornaProductInfo = false ;
let aggiornaSottotitolo = false ;
if ( index !== - 1 ) {
const variante = arrvariazioni [ index ] ;
// Determina se aggiornare pagine
aggiornaPages = ( ! options . aggiornasoloSeVuoti || ! variante . pagine || variante . pagine === 0 ) && data . numpagine ;
if ( aggiornaPages ) {
variante . pagine = Number ( data . numpagine ) ;
recModificato [ 'pagine' ] = variante . pagine ;
}
// Determina se aggiornare misure
aggiornaMisure = ( ! options . aggiornasoloSeVuoti || ! variante . misure ) && data . misure ;
if ( aggiornaMisure ) {
variante . misure = data . misure ;
recModificato [ 'misure' ] = variante . misure ;
}
// Determina se aggiornare edizione
aggiornaEdizione = ( ! options . aggiornasoloSeVuoti || ! variante . edizione ) && data . edizione ;
if ( aggiornaEdizione ) {
variante . edizione = data . edizione ;
recModificato [ 'edizione' ] = variante . edizione ;
}
}
2025-05-16 18:52:21 +02:00
2025-05-19 17:33:58 +02:00
// Determina se aggiornare data pubblicazione
const currentDatePub = myproduct . idProductInfo . date _pub ;
aggiornaDataPubb =
( ! options . aggiornasoloSeVuoti || ! tools . isDateValid ( currentDatePub ) ) &&
tools . isDateValid ( data . data _pubblicazione ) ;
if ( aggiornaDataPubb && data . data _pubblicazione ) {
productInfo . date _pub = new Date ( data . data _pubblicazione ) ;
}
2025-05-16 18:52:21 +02:00
2025-05-19 17:33:58 +02:00
aggiornaSottotitolo = ( ! options . aggiornasoloSeVuoti || ! myproduct . idProductInfo . sottotitolo ) && data . sottotitolo ;
if ( aggiornaSottotitolo && data . sottotitolo ) {
productInfo . sottotitolo = data . sottotitolo ;
}
2025-05-16 18:52:21 +02:00
2025-05-19 17:33:58 +02:00
aggiornaSottotitolo = false ; // !! PER ORA LO DISATTIVO PERCHE' non esiste sempre il sottotitolo in un libro.
// Aggiorna arrvariazioni se pagine o misure sono cambiati
const aggiornadati = aggiornaPages || aggiornaMisure || aggiornaEdizione ;
aggiornaProductInfo = aggiornaDataPubb || aggiornaSottotitolo ;
if ( aggiornadati ) {
updated = await Product . findOneAndUpdate (
{ _id : myproduct . _id } ,
{ $set : { arrvariazioni , scraped : true , scraped _updated : true , scraped _date : new Date ( ) } } ,
{ upsert : true , new : true , includeResultMetadata : true }
) ;
} else if ( aggiornaProductInfo ) {
if ( ! tools . isObjectEmpty ( productInfo ) ) {
// Aggiorna il flag che ho modificato i dati
updated = await Product . findOneAndUpdate (
{ _id : myproduct . _id } ,
{ $set : { scraped : true , scraped _updated : true , scraped _date : new Date ( ) } } ,
{ upsert : true , new : true , includeResultMetadata : true }
) ;
}
}
if ( ! updated ) {
const upd = await Product . findOneAndUpdate (
{ _id : myproduct . _id } ,
{ $set : { scraped : true , scraped _date : new Date ( ) } } ,
{ upsert : true , new : true , returnDocument : 'after' }
) ;
console . log ( 'upd' , upd ) ;
}
2025-05-16 18:52:21 +02:00
2025-05-19 17:33:58 +02:00
if ( aggiornaProductInfo ) {
// Aggiorna productInfo se contiene dati
if ( ! tools . isObjectEmpty ( productInfo ) ) {
risupdate = await ProductInfo . findOneAndUpdate (
{ _id : myproduct . idProductInfo } ,
{ $set : productInfo } ,
{ new : true , upsert : true , includeResultMetadata : true }
) . lean ( ) ;
}
// console.log('risupdate', risupdate);
}
2025-05-16 18:52:21 +02:00
2025-05-19 17:33:58 +02:00
const concatenatedProduct = {
... recModificato ,
... productInfo ,
} ;
2025-05-16 18:52:21 +02:00
2025-05-19 17:33:58 +02:00
if ( updated ) {
console . log ( ' DATI AGGIORNATI:' , JSON . stringify ( concatenatedProduct ) ) ;
}
return { data , updated : this . isRecordAggiornato ( updated ) || this . isRecordAggiornato ( risupdate ) } ;
} catch ( error ) {
console . error ( 'Errore in scrapeISBN:' , error ? . message ) ;
return { data : null , updated : false , error : 'Errore in scrapeISBN:' + error ? . message } ;
2025-05-16 18:52:21 +02:00
}
2025-05-19 17:33:58 +02:00
}
2025-05-16 18:52:21 +02:00
2025-05-19 17:33:58 +02:00
isRecordAggiornato ( updatedDoc ) {
try {
if ( updatedDoc ) {
const wasUpserted = updatedDoc . lastErrorObject . upserted !== undefined ;
return updatedDoc . lastErrorObject . n === 1 && ! wasUpserted ;
} else {
return false ;
}
} catch ( e ) {
console . log ( 'error isRecordAggiornato' , e ) ;
return false ;
2025-05-16 18:52:21 +02:00
}
2025-05-19 17:33:58 +02:00
}
2025-05-16 18:52:21 +02:00
2025-05-19 17:33:58 +02:00
numeroValido ( num ) {
return ! isNaN ( num ) && num !== null && num !== '' && num > 0 ;
}
2025-05-16 18:52:21 +02:00
2025-05-19 17:33:58 +02:00
datiMancanti ( product ) {
let datimancanti = false ;
if ( product . arrvariazioni ? . length > 0 ) {
const arrvar = product . arrvariazioni [ 0 ] ;
if ( ! this . numeroValido ( arrvar . pagine ) ) {
datimancanti = true ;
}
if ( ! arrvar . misure ) {
datimancanti = true ;
}
if ( ! arrvar . edizione ) {
datimancanti = true ;
}
2025-05-16 18:52:21 +02:00
}
2025-05-19 17:33:58 +02:00
if ( product . idProductInfo ) {
if ( ! tools . isDateValid ( product . idProductInfo . date _pub ) ) datimancanti = true ;
2025-05-16 18:52:21 +02:00
}
2025-05-19 17:33:58 +02:00
return datimancanti ;
}
getRemainingTimeToTheEnd ( dataorainizio , index , numrecord ) {
// calcola il tempo stimato rimanente (ore e minuti), tenendo conto che sono arrivato a index, e devo raggiongere "numrecord", e sono partito alla data "dataorainizio"
const differenza = ( ( new Date ( ) . getTime ( ) - dataorainizio . getTime ( ) ) / ( index + 1 ) ) * ( numrecord - index ) ;
// Se la differenza è negativa, restituisce 0
if ( differenza <= 0 ) {
return 'COMPLETATO !' ;
}
// Calcola ore, minuti, secondi rimanenti
const ore = Math . floor ( differenza / ( 1000 * 60 * 60 ) ) ;
const minuti = Math . floor ( ( differenza % ( 1000 * 60 * 60 ) ) / ( 1000 * 60 ) ) ;
// Restituisci il tempo rimanente in formato ore:minuti:secondi
return ` Stimato: ${ ore } ore e ${ minuti } min ` ;
}
includiNelControlloIlRecProduct ( product ) {
return product . idProductInfo && [ 1 , 4 , 34 , 45 , 46 ] . includes ( product . idProductInfo . idStatoProdotto ) ;
2025-05-16 10:26:55 +02:00
}
2025-05-19 17:33:58 +02:00
async scrapeMultiple ( products , options ) {
2025-05-16 10:26:55 +02:00
const results = [ ] ;
2025-05-19 17:33:58 +02:00
let quanti = 0 ;
let mylog = '' ;
console . log ( ` scrapeMultiple INIZIATO... ` ) ;
let dataorainizio = new Date ( ) ;
for ( let i = 0 ; i < 100 && i < products . length ; i ++ ) {
const product = products [ i ] ;
let isbn = product . isbn ;
if ( this . includiNelControlloIlRecProduct ( product ) ) {
if ( this . datiMancanti ( product ) ) {
// console.log(`${quanti} / ${products.length} - Scraping: ${product.idProductInfo.name}`);
const data = await this . scrapeISBN ( product , isbn , options ) ;
if ( data ? . updated ) {
results . push ( { isbn , ... data } ) ;
quanti ++ ;
}
if ( i % 1 === 0 ) {
const percentuale = ( ( quanti / products . length ) * 100 ) . toFixed ( 2 ) ;
console . log (
` Scraping: ${ product . isbn } - ${ product . idProductInfo . name } - ${ quanti } su ${ i + 1 } / ${
products . length
} - [ $ { percentuale } % ] - $ { this . getRemainingTimeToTheEnd ( dataorainizio , i , products . length ) } `
) ;
}
// Per evitare blocchi, metti una pausa (es. 2 secondi)
await new Promise ( ( r ) => setTimeout ( r , 3000 ) ) ;
}
}
2025-05-16 10:26:55 +02:00
}
2025-05-19 17:33:58 +02:00
mylog += ` RECORD AGGIORNATI: ${ results . length - 1 } su ${ quanti } ` ;
2025-05-16 10:26:55 +02:00
return results ;
}
2025-05-16 18:52:21 +02:00
generateHtmlTableFromObject ( obj ) {
if ( ! obj || typeof obj !== 'object' ) return '' ;
2025-05-16 10:26:55 +02:00
2025-05-16 18:52:21 +02:00
let html = '<table border="1" cellpadding="5" cellspacing="0" style="border-collapse: collapse;">' ;
html += '<thead><tr><th>Chiave</th><th>Valore</th></tr></thead><tbody>' ;
for ( const [ key , value ] of Object . entries ( obj ) ) {
// Se il valore è un oggetto o array, lo converto in JSON stringa
const displayValue = value && typeof value === 'object' ? JSON . stringify ( value ) : String ( value ) ;
html += ` <tr><td> ${ key } </td><td> ${ displayValue } </td></tr> ` ;
}
html += '</tbody></table>' ;
return html ;
}
2025-05-19 17:33:58 +02:00
static async ScraperAzzeraFlagProducts ( idapp , options ) {
// aggiorna tutti i record di Product (con idapp) scraped: false
await Product . updateMany ( { idapp , scraped : true } , { $set : { scraped : false } } ) ;
await Product . updateMany ( { idapp , scraped _updated : true } , { $set : { scraped _updated : false } } ) ;
}
static async removeDuplicateVariations ( idapp , options ) {
let mylog = 'removeDuplicateVariations...\n' ;
// Fase 1: Troviamo i documenti che hanno almeno due elementi in arrvariazioni,
// uno con `versione` e uno senza.
const result = await Product . aggregate ( [
{ $match : { idapp } } , // Seleziona il prodotto in base a idapp
{
$unwind : '$arrvariazioni' , // Esplodi l'array `arrvariazioni` in documenti separati
} ,
{
$group : {
_id : '$_id' , // Gruppo per _id del prodotto
arrvariazioni : { $push : '$arrvariazioni' } , // Ricostruisci l'array arrvariazioni
// Trova se c'è almeno un elemento con `versione` e uno senza
hasVersione : {
$sum : { $cond : [ { $ifNull : [ '$arrvariazioni.versione' , false ] } , 1 , 0 ] } ,
} ,
} ,
} ,
{
$match : {
hasVersione : { $gt : 0 } , // Se c'è almeno un record con `versione`
} ,
} ,
] ) ;
// Ora possiamo rimuovere i duplicati
for ( let doc of result ) {
// Filtra gli oggetti dentro `arrvariazioni` per mantenere quelli con versione
const arrvariazioniWithVersione = doc . arrvariazioni . filter ( ( item ) => item . versione ) ;
// Rimuovi gli elementi che non hanno versione ma hanno gli stessi altri campi
const cleanedArr = arrvariazioniWithVersione . filter (
( item , index , self ) =>
index ===
self . findIndex (
( t ) =>
t . active === item . active &&
t . status === item . status &&
t . price === item . price &&
t . sale _price === item . sale _price &&
t . quantita === item . quantita &&
t . pagine === item . pagine &&
t . misure === item . misure &&
t . edizione === item . edizione &&
t . ristampa === item . ristampa &&
t . formato === item . formato &&
t . tipologia === item . tipologia &&
t . idTipologia === item . idTipologia &&
t . idTipoFormato === item . idTipoFormato &&
t . preOrderDate === item . preOrderDate &&
t . addtocart _link === item . addtocart _link &&
t . eta === item . eta
)
) ;
if ( doc . arrvariazioni . length - cleanedArr . length > 0 ) {
const logtemp = ` Elaborato ${ doc . _id } con ${ arrvariazioniWithVersione . length } elementi \n ` ;
logtemp += ` Rimossi ${ doc . arrvariazioni . length - cleanedArr . length } duplicati \n ` ;
console . log ( logtemp ) ;
mylog += logtemp ;
// Aggiorna il documento eliminando i duplicati
await Product . updateOne ( { _id : doc . _id } , { $set : { arrvariazioni : cleanedArr } } ) ;
}
}
return { mylog } ;
}
static async queryArrVariazioni ( idapp ) {
const result = await Product . aggregate ( [
{ $match : { idapp , 'arrvariazioni.0' : { $exists : true } , 'arrvariazioni.1' : { $exists : true } } } ,
{ $project : { arrvariazioni : 1 , _id : 0 } } ,
] ) ;
console . log ( 'result' , result ) ;
}
static async ScraperGeneraCSV ( idapp , options , res ) {
// Dichiara le intestazioni del CSV
const headers = [ 'isbn' , 'titolo' , 'pagine' , 'misure' , 'edizione' , 'date_pub' /*'sottotitolo'*/ ] ;
try {
// Trova i prodotti e popula 'idProductInfo'
const products = await Product . find ( { idapp , scraped _updated : true } )
. populate ( { path : 'idProductInfo' , select : 'date_pub name sottotitolo' } )
. lean ( ) ;
// Funzione per "appiattire" i dati
const flattenData = ( data ) => {
return data . map ( ( item ) => {
const flattened = { ... item } ;
// Se arrvariazioni esiste, prendi solo il primo elemento o elabora come richiesto
if ( item . arrvariazioni && item . arrvariazioni . length > 0 ) {
const variation = item . arrvariazioni [ 0 ] ; // Usa il primo elemento o modifica se necessario
flattened . pagine = variation . pagine || '' ; // Usa '' se pagine non esiste
flattened . misure = variation . misure || '' ;
flattened . edizione = variation . edizione || '' ;
flattened . ristampa = variation . ristampa || '' ;
}
// Assicurati che 'idProductInfo' esista prima di usarlo
flattened . date _pub = item . idProductInfo ? item . idProductInfo . date _pub : '' ;
flattened . titolo = item . idProductInfo ? item . idProductInfo . name : '' ;
// flattened.sottotitolo = item.idProductInfo ? item.idProductInfo.sottotitolo : '';
return flattened ;
} ) ;
} ;
// Appiattisci i dati
const records = flattenData ( products ) ;
// Prepara le righe del CSV
const rows = records . map ( ( item ) => {
// Se 'date_pub' è valido, convertilo in formato data, altrimenti metti una stringa vuota
const formattedDate = item . date _pub
? item . date _pub . toLocaleDateString ( 'it-IT' , { year : 'numeric' , month : '2-digit' , day : '2-digit' } )
: '' ;
return [
item . isbn || '' , // Assicurati che ISBN sia sempre una stringa, anche vuota
item . titolo || '' ,
//item.sottotitolo || '',
item . pagine || '' , // Gestisci il caso in cui 'pagine' non esiste
item . misure || '' ,
item . edizione || '' ,
formattedDate , // La data formattata
] ;
} ) ;
// Aggiungi l'intestazione al CSV
rows . unshift ( headers ) ;
// Unisci tutte le righe con il delimitatore "|"
const csvData = rows . map ( ( row ) => row . join ( '|' ) ) . join ( '\n' ) ;
// Scrivi il file CSV in modo asincrono
// Ritorna la stringa CSV come oggetto per eventuali elaborazioni future
return { data : csvData } ;
} catch ( e ) {
console . error ( 'Error in ScraperGeneraCSV:' , e ) ;
return { error : e . message } ;
}
}
static async ScraperDataAmazon ( idapp , options ) {
2025-05-16 18:52:21 +02:00
const scraper = new AmazonBookScraper ( ) ;
const isbn = options . isbn ;
try {
const myproduct = await Product . getProductByIsbn ( isbn ) ;
2025-05-19 17:33:58 +02:00
const ris = await scraper . scrapeISBN ( myproduct , isbn , options ) ;
2025-05-16 18:52:21 +02:00
2025-05-19 17:33:58 +02:00
console . log ( ris ? . data ) ;
return res . status ( 200 ) . send ( { code : server _constants . RIS _CODE _OK , data : ris ? . data , html } ) ;
2025-05-16 18:52:21 +02:00
} catch ( e ) {
console . error ( e ) ;
return res . status ( 400 ) . send ( { code : server _constants . RIS _CODE _ERR , msg : '' } ) ;
}
}
2025-05-19 17:33:58 +02:00
static async ScraperMultipleDataAmazon ( idapp , options ) {
2025-05-16 18:52:21 +02:00
const scraper = new AmazonBookScraper ( ) ;
try {
2025-05-19 17:33:58 +02:00
// Prendi solo quelli che non sono ancora stati scraped !
const products = await Product . aggregate ( [
// Filtro di base sui campi idapp, isbn, scraped, e scraped_error
{
$match : {
idapp ,
isbn : { $exists : true , $ne : '' } ,
scraped : { $ne : true } , // Escludi direttamente i record con scraped = true
$or : [ { deleted : { $exists : false } } , { deleted : { $exists : true , $eq : false } } ] ,
$or : [ { scraped _error : { $exists : false } } , { scraped _error : { $exists : true , $eq : false } } ] ,
} ,
} ,
// Popoliamo il campo idProductInfo
{
$lookup : {
from : 'productinfos' , // Nome della collezione per 'idProductInfo'
localField : 'idProductInfo' , // Campo del documento corrente (Product)
foreignField : '_id' , // Campo di riferimento in ProductInfo
as : 'idProductInfo' , // Campo in cui verranno inseriti i dati popolati
} ,
} ,
// De-strutturiamo l'array idProductInfo, se è un array
{
$unwind : {
path : '$idProductInfo' ,
preserveNullAndEmptyArrays : true , // Mantieni i documenti anche se idProductInfo è null o vuoto
} ,
} ,
{
$match : {
'idProductInfo.idStatoProdotto' : { $in : [ 1 , 4 , 34 , 45 , 46 ] } , // Condizione su idStatoProdotto
} ,
} ,
// Proiettiamo solo i campi necessari
{
$project : {
scraped : 1 ,
scraped _updated : 1 ,
isbn : 1 ,
title : 1 ,
sottotitolo : 1 ,
arrvariazioni : 1 ,
'idProductInfo._id' : 1 ,
'idProductInfo.date_pub' : 1 ,
'idProductInfo.name' : 1 ,
'idProductInfo.sottotitolo' : 1 ,
'idProductInfo.idStatoProdotto' : 1 ,
'idProductInfo.link_macro' : 1 ,
'idProductInfo.imagefile' : 1 ,
} ,
} ,
// A questo punto, puoi aggiungere altre operazioni di aggregazione se necessario (e.g., ordinamento)
] ) ;
// console.log(products);
const books = await scraper . scrapeMultiple ( products , options ) ;
2025-05-16 18:52:21 +02:00
console . log ( books ) ;
} catch ( e ) {
console . error ( e ) ;
return res . status ( 400 ) . send ( { code : server _constants . RIS _CODE _ERR , msg : '' } ) ;
}
}
extractDataPubblicazione ( $ ) {
// Seleziona il div con id specifico per la data di pubblicazione
const publicationDate = $ ( '#rpi-attribute-book_details-publication_date .rpi-attribute-value span' ) . text ( ) . trim ( ) ;
// Se non trova la data, ritorna null
return publicationDate || null ;
}
extractNumeroDiPagine ( $ ) {
// Seleziona il div con id specifico per pagine
const pagesText = $ ( '#rpi-attribute-book_details-fiona_pages .rpi-attribute-value span' ) . text ( ) . trim ( ) ;
// pagesText dovrebbe essere tipo "184 pagine"
if ( ! pagesText ) return null ;
// Estrai solo il numero (facoltativo)
const match = pagesText . match ( /(\d+)/ ) ;
return match ? match [ 1 ] : pagesText ; // ritorna solo il numero o il testo intero
}
extractDimensions ( $ ) {
// Seleziona il div con id specifico per dimensioni
const dimText = $ ( '#rpi-attribute-book_details-dimensions .rpi-attribute-value span' ) . text ( ) . trim ( ) ;
// Se non trova niente ritorna null
return dimText || null ;
}
convertDimensionsToMisureMacro ( dimString ) {
if ( ! dimString ) return null ;
// Estrai tutti i numeri (compresi decimali)
const numbers = dimString . match ( /[\d.]+/g ) ;
if ( ! numbers || numbers . length < 2 ) return null ;
// Converti in numeri float e ordina decrescente
const sortedNums = numbers
. map ( ( num ) => parseFloat ( num ) )
. filter ( ( n ) => ! isNaN ( n ) )
. sort ( ( a , b ) => b - a ) ;
if ( sortedNums . length < 2 ) return null ;
// Prendi i due più grandi
const [ first , second ] = sortedNums ;
return ` cm ${ first } x ${ second } ` ;
}
parseItalianDate ( dateStr ) {
if ( ! dateStr ) return null ;
// Mappa mesi in italiano a numeri (0-based per Date)
const months = {
gennaio : 0 ,
febbraio : 1 ,
marzo : 2 ,
aprile : 3 ,
maggio : 4 ,
giugno : 5 ,
luglio : 6 ,
agosto : 7 ,
settembre : 8 ,
ottobre : 9 ,
novembre : 10 ,
dicembre : 11 ,
} ;
// Divido la stringa (es. "14 maggio 2025")
const parts = dateStr . toLowerCase ( ) . split ( ' ' ) ;
if ( parts . length !== 3 ) return null ;
const day = parseInt ( parts [ 0 ] , 10 ) ;
const month = months [ parts [ 1 ] ] ;
const year = parseInt ( parts [ 2 ] , 10 ) ;
if ( isNaN ( day ) || month === undefined || isNaN ( year ) ) return null ;
return new Date ( year , month , day ) ;
}
extractSubtitle ( fullTitle , baseTitle ) {
if ( ! fullTitle || ! baseTitle ) return null ;
let mybaseTitle = '' ;
let numCharRemoved = 0 ;
let titoloOriginale = '' ;
// Se il fullTitle non contiene il baseTitle, ritorna null
const coniniziali = fullTitle . trim ( ) . toLowerCase ( ) . startsWith ( baseTitle . trim ( ) . toLowerCase ( ) ) ;
if ( coniniziali ) {
mybaseTitle = baseTitle ;
}
let senzainiziali = false ;
if ( ! coniniziali ) {
// torna la posizione in cui l'ho trovato
const posizione = fullTitle . toLowerCase ( ) . indexOf ( baseTitle . trim ( ) . toLowerCase ( ) ) ;
if ( posizione < 0 || posizione > 3 ) {
return null ;
}
// torna il nome del titolo, compreso degli articoli, partendo da zero, fino a posizione + baseTitle
titoloOriginale = fullTitle . substring ( 0 , posizione + baseTitle . length ) ;
numCharRemoved = posizione ;
senzainiziali = true ;
}
if ( ! coniniziali && ! senzainiziali ) {
return null ;
}
// Rimuovi il baseTitle dall'inizio
let remainder = fullTitle . slice ( baseTitle . length + numCharRemoved ) . trim ( ) ;
// Se la rimanenza inizia con ":" o "-" o ".", rimuovila
if ( remainder . startsWith ( ':' ) || remainder . startsWith ( '-' ) || remainder . startsWith ( '.' ) ) {
remainder = remainder . slice ( 1 ) . trim ( ) ;
}
// Se resta una stringa non vuota, è il sottotitolo
return { subtitle : remainder . length > 0 ? remainder : null , titoloOriginale } ;
}
extractMonthYear ( dateStr ) {
if ( ! dateStr ) return null ;
// Divide la stringa in parole
const parts = dateStr . trim ( ) . split ( ' ' ) ;
// Se ha almeno 3 parti (giorno, mese, anno)
if ( parts . length >= 3 ) {
// Restituisce mese + anno
return parts
. slice ( 1 )
. map ( ( part , idx ) => ( idx === 0 ? part [ 0 ] . toUpperCase ( ) + part . slice ( 1 ) : part ) )
. join ( ' ' ) ;
}
return null ;
2025-05-16 10:26:55 +02:00
}
2025-05-16 18:52:21 +02:00
extractEditore ( $ ) {
// Seleziona il testo dentro il div id rpi-attribute-book_details-publisher
const publisher = $ ( '#rpi-attribute-book_details-publisher .rpi-attribute-value span' ) . text ( ) . trim ( ) ;
2025-05-16 10:26:55 +02:00
2025-05-16 18:52:21 +02:00
return publisher || null ;
2025-05-16 10:26:55 +02:00
}
}
2025-05-16 18:52:21 +02:00
module . exports = AmazonBookScraper ;