const axios = require('axios'); const cheerio = require('cheerio'); const Product = require('../models/product'); const ProductInfo = require('../models/productInfo'); const tools = require('../tools/general'); class AmazonBookScraper { constructor() { this.baseUrl = 'https://www.amazon.it/dp/'; } async fetchPageISBN10(isbn10) { const url = `${this.baseUrl}${isbn10}`; 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 }, }); return data; } catch (err) { 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); return null; } } getTitleByProductInfo(productInfo) { try { return productInfo?.name.trim(); } catch (e) { return ''; } } async extractData(myproduct, html) { const $ = cheerio.load(html); const productInfo = await ProductInfo.findOne({ _id: myproduct.idProductInfo }).lean(); const title_ondb = this.getTitleByProductInfo(productInfo); // Titolo let title = $('#productTitle').text().trim() || null; // Sottotitolo (Amazon lo mette nel titolo) let dettagli = $('#productSubtitle').text().trim() || null; const ris = this.extractSubtitle(title, title_ondb); let sottotitolo = title_ondb ? ris?.subtitle : ''; let titoloOriginale = ''; if (ris?.titoloOriginale) { // E' presente il vero titolo del Libro ! titoloOriginale = ris.titoloOriginale; } 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 } : {}), ...(sottotitolo ? { sottotitolo } : {sottotitolo: ''}), numpagine, misure, edizione, data_pubblicazione, editore: publisher, }; } async scrapeISBN(myproduct, isbn, options) { const isbn10 = this.isbn13to10(isbn); const html = await this.fetchPageISBN10(isbn10); if (!html) return null; const data = await this.extractData(myproduct, html); if (!options?.update) return data; 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; if (index !== -1) { const variante = arrvariazioni[index]; // Determina se aggiornare pagine aggiornaPages = !options.aggiornasoloSeVuoti || !variante.pagine || variante.pagine === 0; if (aggiornaPages) variante.pagine = Number(data.numpagine); // Determina se aggiornare misure aggiornaMisure = !options.aggiornasoloSeVuoti || !variante.misure; if (aggiornaMisure) variante.misure = data.misure; // Determina se aggiornare edizione aggiornaEdizione = !options.aggiornasoloSeVuoti || !variante.edizione; if (aggiornaEdizione) variante.edizione = data.edizione; } // Determina se aggiornare data pubblicazione const currentDatePub = productInfo.date_pub; aggiornaDataPubb = !options.aggiornasoloSeVuoti || !tools.isDateValid(currentDatePub); if (aggiornaDataPubb && data.data_pubblicazione) { productInfo.date_pub = new Date(data.data_pubblicazione); } // Aggiorna arrvariazioni se pagine o misure sono cambiati const aggiornadati = aggiornaPages || aggiornaMisure || aggiornaEdizione; if (aggiornadati) { await Product.findOneAndUpdate( { _id: myproduct._id }, { $set: { arrvariazioni, scraped: true, scraped_date: new Date() } }, { upsert: true, new: true, includeResultMetadata: true } ); } // Aggiorna productInfo se contiene dati if (!tools.isObjectEmpty(productInfo)) { const risupdate = await ProductInfo.findOneAndUpdate( { _id: myproduct.idProductInfo }, { $set: productInfo, scraped: true, scraped_date: new Date() }, { new: true, upsert: true, returnOriginal: false } ).lean(); console.log('risupdate', risupdate) ; } return data; } async scrapeMultiple(isbnList, options) { const results = []; for (const isbn of isbnList) { console.log(`Scraping ISBN: ${isbn}`); const myproduct = null; /// myproduct... const data = await this.scrapeISBN(myproduct, isbn, options); results.push({ isbn, ...data }); // Per evitare blocchi, metti una pausa (es. 2 secondi) await new Promise((r) => setTimeout(r, 2000)); } return results; } generateHtmlTableFromObject(obj) { if (!obj || typeof obj !== 'object') return ''; let html = '
| Chiave | Valore |
|---|---|
| ${key} | ${displayValue} |