- Per generare la sinossi è possibile estrarre con 1 click la descrizione sia da Amazon che da GruppoMacro.

- corretto piccolo bug sul catalogo.
This commit is contained in:
Surya Paolo
2025-05-20 12:21:51 +02:00
parent a3c7b92c0c
commit 10097f4238
10 changed files with 469 additions and 62 deletions

View File

@@ -0,0 +1,99 @@
const mongoose = require('mongoose').set('debug', false);
const Schema = mongoose.Schema;
const tools = require('../tools/general');
mongoose.Promise = global.Promise;
mongoose.level = 'F';
// Resolving error Unknown modifier: $pushAll
mongoose.plugin((schema) => {
schema.options.usePushEach = true;
});
const MyScrapingBookSchema = new Schema({
isbn: {
type: String,
index: true,
},
isbn10: {
type: String,
},
fonte: {
type: String,
},
titolo: {
type: String,
},
titoloOriginale: {
type: String,
},
sottotitolo: {
type: String,
},
autore: {
type: String,
},
pagine: {
type: String,
},
misure: {
type: String,
},
edizione: {
type: String,
},
editore: {
type: String,
},
date_pub: {
type: Date,
},
url: {
type: String,
},
stelline: {
type: Number,
},
prezzo: {
type: String,
},
date_extraction: {
type: Date,
},
descrizione_lunga: {
type: String,
},
});
MyScrapingBookSchema.statics.getFieldsForSearch = function () {
return [{ field: 'titolo', type: tools.FieldType.string }];
};
MyScrapingBookSchema.statics.executeQueryTable = function (idapp, params, user) {
params.fieldsearch = this.getFieldsForSearch();
return tools.executeQueryTable(this, idapp, params, user);
};
MyScrapingBookSchema.statics.findAllIdApp = async function (idapp) {
const MyScrapingBook = this;
const myfind = { idapp };
try {
return await MyScrapingBook.find(myfind).sort({ titolo: 1 }).lean();
} catch (err) {
console.error('Errore in MyScrapingBook:', err, model);
return null;
}
};
const MyScrapingBook = mongoose.model('MyScrapingBook', MyScrapingBookSchema);
MyScrapingBook.createIndexes()
.then(() => {})
.catch((err) => {
throw err;
});
module.exports = { MyScrapingBook };

View File

@@ -494,7 +494,7 @@ module.exports.findAllIdApp = async function (idapp, code, id, all, isbn) {
myfind = { ...myfind, code };
}
if (isbn) {
myfind = { ...myfind, sbn };
myfind = { ...myfind, isbn };
}
if (id) {
myqueryadd = {

View File

@@ -57,7 +57,10 @@ class CronMod {
// } else if (mydata.dbop === 'rigeneraTutto') {
// await ListaIngresso.Esegui_CronTab(idapp, mydata);
} else if (mydata.dbop === "ScraperMultipleDataAmazon") {
mystr = await AmazonBookScraper.ScraperMultipleDataAmazon(idapp, mydata.options);
mystr = await AmazonBookScraper.ScraperMultipleDataAmazon(idapp, {update: true, aggiornasoloSeVuoti: true, forzaricarica: false});
ris = { mystr };
} else if (mydata.dbop === "ScraperMultipleDataDBStored") {
mystr = await AmazonBookScraper.ScraperMultipleDataDBStored(idapp, {update: true, aggiornasoloSeVuoti: true, forzaricarica: false, dbstored: true});
ris = { mystr };
} else if (mydata.dbop === "ScraperGeneraCSV") {
mystr = await AmazonBookScraper.ScraperGeneraCSV(idapp, mydata.options, res);

View File

@@ -9,32 +9,76 @@ const shared_consts = require('../tools/shared_nodejs');
const fs = require('fs').promises; // 👈 Usa il modulo promises
const { MyScrapingBook } = require('../models/myscrapingbook');
class AmazonBookScraper {
constructor() {
this.baseUrl = 'https://www.amazon.it/dp/';
}
async fetchPageISBN10(isbn10) {
getUserAgentRandom() {
// Lista di User-Agent comuni per i vari browser
const userAgents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/91.0.864.59 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:80.0) Gecko/20100101 Firefox/80.0',
'Mozilla/5.0 (Windows NT 6.1; Trident/7.0; AS; rv:11.0) like Gecko',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36',
'Mozilla/5.0 (Windows NT 6.1; rv:56.0) Gecko/20100101 Firefox/56.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36',
];
// Seleziona un User-Agent casuale dalla lista
const randomIndex = Math.floor(Math.random() * userAgents.length);
return userAgents[randomIndex];
}
async fetchPageISBN10(isbn10, isbn) {
if (!isbn10) return false;
const url = `${this.baseUrl}${isbn10}`;
const retryLimit = 2; // Numero massimo di tentativi
const timeout = 7000; // Timeout di 5 secondi per la richiesta
const delay = 10000; // Ritardo tra i tentativi (10 secondi)
for (let attempt = 1; attempt <= retryLimit; attempt++) {
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
'User-Agent': this.getUserAgentRandom(),
// Aggiungi altri header se necessario
},
timeout, // Timeout di 5 secondi per la richiesta
});
return { html: data, url };
} catch (err) {
console.error(`Errore fetching ISBN ${isbn10}:`, err.message);
console.error(`Errore fetching isbn10 ${isbn10} (ISBN:${isbn}) (tentativo ${attempt}):`, err.message);
if (attempt < retryLimit) {
console.log(`Riprovo tra ${delay / 1000} secondi...`);
await new Promise((resolve) => setTimeout(resolve, delay)); // Ritardo prima di riprovare
} else {
console.error(
`Impossibile recuperare la pagina per ISBN10 ${isbn10} (ISBN:${isbn}) dopo ${retryLimit} tentativi.`
);
await Product.findOneAndUpdate(
{ isbn: isbn },
{ $set: { scraped: true, scraped_error: true } },
{ upsert: true, new: true, includeResultMetadata: true }
).lean();
return null;
}
}
}
}
isbn13to10(isbn13) {
try {
if (!isbn13.startsWith('978') || isbn13.length !== 13) return null;
if (!(isbn13.startsWith('978') || isbn13.startsWith('979')) || isbn13.length !== 13) return null;
const core = isbn13.slice(3, 12); // i 9 numeri centrali
let sum = 0;
@@ -60,7 +104,7 @@ class AmazonBookScraper {
}
}
async extractData(myproduct, html, url) {
async extractData(myproduct, html, url, isbn10) {
const $ = cheerio.load(html);
const productInfo = await ProductInfo.findOne({ _id: myproduct.idProductInfo }).lean();
@@ -94,11 +138,19 @@ class AmazonBookScraper {
let edizione = null;
let publisher = null;
let data_pubblicazione = null;
let descrizione_lunga = null;
let prezzo = null;
let stelline = null;
let autore = null;
numpagine = this.extractNumeroDiPagine($);
const dim = this.extractDimensions($);
misure = this.convertDimensionsToMisureMacro(dim);
const data_pubb = this.extractDataPubblicazione($);
descrizione_lunga = this.extractDescrizioneLunga($);
autore = this.extractAutore($);
stelline = this.extractStelline($);
prezzo = this.extractPrezzo($);
data_pubblicazione = this.parseItalianDate(data_pubb);
publisher = this.extractEditore($);
@@ -107,23 +159,105 @@ class AmazonBookScraper {
edizione = this.extractMonthYear(data_pubb);
}
return {
const fonte = 'AMAZON';
const data = {
isbn: myproduct.isbn,
isbn10,
titolo: title,
fonte,
...(titoloOriginale ? { titoloOriginale } : {}),
...(sottotitolo ? { sottotitolo } : { sottotitolo: '' }),
...(autore ? { autore } : { autore: '' }),
...(stelline ? { stelline } : {}),
...(numpagine ? { numpagine } : {}),
...(misure ? { misure } : {}),
...(edizione ? { edizione } : {}),
...(data_pubblicazione ? { data_pubblicazione } : {}),
...(descrizione_lunga ? { descrizione_lunga } : {}),
...(publisher ? { editore: publisher } : {}),
url: `<a href="${url}" target="_blank">URL</a>`,
url: `<a href="${url}" target="_blank">VAI AL SITO ${fonte}</a>`,
...(data_pubblicazione ? { data_pubblicazione } : {}),
...(prezzo ? { prezzo } : {}),
};
// Aggiungilo al record
await this.addOrUpdateMyScrapingBook(data);
return data;
}
async findRecordMyScrapingBookByIsbn(isbn) {
const record = await MyScrapingBook.findOne({ isbn }).lean();
return record;
}
async addOrUpdateMyScrapingBook(data) {
try {
const {
isbn,
isbn10,
titolo,
titoloOriginale,
fonte,
url,
sottotitolo,
autore,
numpagine,
misure,
edizione,
editore,
data_pubblicazione,
stelline,
prezzo,
descrizione_lunga,
} = data;
// Cerca un record esistente per ISBN
let record = await MyScrapingBook.findOne({ isbn }).lean();
// Se il record esiste, aggiorna i campi
await MyScrapingBook.updateOne(
{ isbn },
{
$set: {
isbn10,
titolo,
fonte,
titoloOriginale,
sottotitolo,
autore,
pagine: numpagine,
misure,
edizione,
editore,
date_pub: data_pubblicazione,
url,
stelline,
prezzo,
descrizione_lunga,
date_extraction: new Date(),
},
},
{ upsert: true }
);
} catch (err) {
console.error('Errore gestendo MyScrapingBook:', err);
}
}
async scrapeISBN(myproduct, isbn, options) {
try {
const datastored = await this.findRecordMyScrapingBookByIsbn(isbn);
let data = null;
let updated = null;
let risupdate = null;
// prima cerca sulla tabella che ho scaricato
if (options.forzaricarica || !datastored) {
const isbn10 = this.isbn13to10(isbn);
const res = await this.fetchPageISBN10(isbn10);
const res = await this.fetchPageISBN10(isbn10, isbn);
if (!res) {
await Product.findOneAndUpdate(
{ _id: myproduct._id },
@@ -136,10 +270,10 @@ class AmazonBookScraper {
const html = res.html;
if (!html) return null;
let updated = null;
let risupdate = null;
const data = await this.extractData(myproduct, html, res.url);
data = await this.extractData(myproduct, html, res.url, isbn10);
} else {
data = { ...datastored };
}
if (!options?.update) return data;
@@ -233,7 +367,7 @@ class AmazonBookScraper {
{ $set: { scraped: true, scraped_date: new Date() } },
{ upsert: true, new: true, returnDocument: 'after' }
);
console.log('upd', upd);
// console.log('upd', upd);
}
if (aggiornaProductInfo) {
@@ -293,13 +427,15 @@ class AmazonBookScraper {
if (!arrvar.misure) {
datimancanti = true;
}
if (!arrvar.edizione) {
/*if (!arrvar.edizione) {
datimancanti = true;
}
}*/
}
if (product.idProductInfo) {
if (!tools.isDateValid(product.idProductInfo.date_pub)) datimancanti = true;
if (!tools.isDateValid(product.idProductInfo.date_pub)) {
datimancanti = true;
}
}
return datimancanti;
@@ -333,7 +469,7 @@ class AmazonBookScraper {
console.log(`scrapeMultiple INIZIATO...`);
let dataorainizio = new Date();
for (let i = 0; i < 100 && i < products.length; i++) {
for (let i = 0; i < products.length; i++) {
const product = products[i];
let isbn = product.isbn;
@@ -348,7 +484,7 @@ class AmazonBookScraper {
}
if (i % 1 === 0) {
const percentuale = ((quanti / products.length) * 100).toFixed(2);
const percentuale = ((i / products.length) * 100).toFixed(2);
console.log(
`Scraping: ${product.isbn} - ${product.idProductInfo.name} - ${quanti} su ${i + 1} / ${
products.length
@@ -357,10 +493,11 @@ class AmazonBookScraper {
}
// Per evitare blocchi, metti una pausa (es. 2 secondi)
await new Promise((r) => setTimeout(r, 3000));
await new Promise((r) => setTimeout(r, 2000));
}
}
}
mylog += `RECORD AGGIORNATI: ${results.length - 1} su ${quanti}`;
return results;
}
@@ -389,6 +526,12 @@ class AmazonBookScraper {
await Product.updateMany({ idapp, scraped_updated: true }, { $set: { scraped_updated: false } });
}
static async ScraperAzzeraFlagErrori(idapp, options) {
// aggiorna tutti i record di Product (con idapp) scraped: false
await Product.updateMany({ idapp, scraped_error: true }, { $set: { scraped: false } });
}
static async removeDuplicateVariations(idapp, options) {
let mylog = 'removeDuplicateVariations...\n';
@@ -478,7 +621,6 @@ class AmazonBookScraper {
.populate({ path: 'idProductInfo', select: 'date_pub name sottotitolo' })
.lean();
// Funzione per "appiattire" i dati
const flattenData = (data) => {
return data.map((item) => {
@@ -565,8 +707,9 @@ class AmazonBookScraper {
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 } }],
/*$or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }],
*/
},
},
// Popoliamo il campo idProductInfo
@@ -622,14 +765,105 @@ class AmazonBookScraper {
}
extractDataPubblicazione($) {
try {
// 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;
} catch (error) {
console.error('Error extracting publication date:', error);
return null;
}
}
extractDescrizioneLunga($) {
try {
// Cerca l'elemento che contiene la descrizione lunga
const descriptionElement = $('#bookDescription_feature_div .a-expander-content span');
// Estrai il testo e rimuovi eventuali spazi extra o tag HTML
const description = descriptionElement.text().trim();
// Se la descrizione è vuota, restituisci null
return description || null;
} catch (error) {
console.error('Error extracting long description:', error);
return null;
}
}
extractAutore($) {
try {
// Seleziona tutti gli elementi che contengono gli autori
const authorsElements = $('#bylineInfo .author a');
// Estrai il testo per ogni autore e rimuovi spazi extra
const authors = authorsElements.map((i, el) => $(el).text().trim()).get();
// Se ci sono autori, restituiscili come stringa separata da virgole
return authors.length > 0 ? authors.join(', ') : null;
} catch (error) {
console.error('Error extracting authors:', error);
return null;
}
}
extractStelline($) {
try {
// Cerca l'elemento che contiene la descrizione lunga
// Seleziona l'elemento che contiene il voto medio (5,0 su 5 stelle)
const ratingText = $('#acrPopover').attr('title');
if (ratingText) {
// Estrai il numero di stelle dal testo "5,0 su 5 stelle"
const stars = ratingText.split(' su ')[0].replace(',', '.'); // Cambia la virgola in punto
return stars; // Restituisce il numero di stelle
}
} catch (error) {
console.error('Error extracting stars:', error);
return null;
}
}
extractPrezzo($) {
try {
// Seleziona l'elemento che contiene il prezzo
const priceElement = $(
'.a-box-group .a-section #desktop_qualifiedBuyBox #corePriceDisplay_desktop_feature_div .a-price .a-price-whole'
);
// Estrai il prezzo principale e la parte decimale
const wholePrice = priceElement.text().trim();
const decimalPrice = $(
'.a-box-group .a-section #desktop_qualifiedBuyBox #corePriceDisplay_desktop_feature_div .a-price .a-price-fraction'
)
.text()
.trim();
// Estrai il simbolo della valuta
const currencySymbol = $(
'.a-box-group .a-section #desktop_qualifiedBuyBox #corePriceDisplay_desktop_feature_div .a-price .a-price-symbol'
)
.text()
.trim();
// Se il prezzo è stato trovato, formattalo come prezzo completo
if (wholePrice && decimalPrice && currencySymbol) {
const fullPrice = `${wholePrice}${decimalPrice} ${currencySymbol}`;
return fullPrice;
}
// Se non trova il prezzo, restituisce null
return null;
} catch (error) {
console.error('Error extracting price:', error);
return null;
}
}
extractNumeroDiPagine($) {
try {
// Seleziona il div con id specifico per pagine
const pagesText = $('#rpi-attribute-book_details-fiona_pages .rpi-attribute-value span').text().trim();
@@ -639,14 +873,23 @@ class AmazonBookScraper {
// Estrai solo il numero (facoltativo)
const match = pagesText.match(/(\d+)/);
return match ? match[1] : pagesText; // ritorna solo il numero o il testo intero
} catch (error) {
console.error('Error extracting number of pages:', error);
return null;
}
}
extractDimensions($) {
try {
// 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;
} catch (error) {
console.error('Error extracting dimensions:', error);
return null;
}
}
convertDimensionsToMisureMacro(dimString) {

View File

@@ -27,7 +27,7 @@ const Gasordine = require('../models/gasordine');
const { User } = require('../models/user');
const AmazonBookScraper = require('../modules/scraping');
const AmazonBookScraper = require('../modules/Scraping');
const { Catalog } = require('../models/catalog');
const { RaccoltaCataloghi } = require('../models/raccoltacataloghi');

View File

@@ -22,7 +22,7 @@ router.post('/', auth_default, async function (req, res, next) {
let ismanager = await tools.isManagerByReq(req);
let catalogs = await Catalog.findAllIdApp(idapp, '', undefined, ismanager);
let catalogs = await Catalog.findAllIdApp(idapp);
let orders = null;
if (catalogs) return res.send({ code: server_constants.RIS_CODE_OK, catalogs, orders });

View File

@@ -0,0 +1,57 @@
const express = require('express');
const router = express.Router();
const tools = require('../tools/general');
var server_constants = require('../tools/server_constants');
const { User } = require('../models/user');
var { authenticate, auth_default } = require('../middleware/authenticate');
const _ = require('lodash');
const { MyScrapingBook } = require('../models/myscrapingbook');
const Product = require('../models/product');
const AmazonBookScraper = require('../modules/Scraping');
//GET /products
router.post('/', auth_default, async function (req, res, next) {
const idapp = req.body.idapp;
const isbn = req.body.isbn;
const forzacaricamento = req.body.forzacaricamento;
try {
let myscraping = null;
if (isbn) {
myscraping = await MyScrapingBook.findOne({ isbn }).lean();
if (!myscraping && forzacaricamento) {
const scraper = new AmazonBookScraper();
const options = {
update: false,
forzaricarica: false,
};
const myproduct = await Product.getProductByIsbn(idapp, isbn);
if (myproduct && myproduct.length > 0) {
myscraping = await scraper.scrapeISBN(myproduct[0], isbn, options);
// console.log(myscraping);
}
}
}
if (myscraping) {
return res.send({ code: server_constants.RIS_CODE_OK, myscraping });
} else {
return res.send({ code: server_constants.RIS_CODE_OK, myscraping: null });
}
} catch (e) {
console.error(e);
return res.status(400).send({ code: server_constants.RIS_CODE_ERR, msg: e.message });
}
});
module.exports = router;

View File

@@ -128,6 +128,7 @@ connectToDatabase(connectionUrl, options)
const site_router = require('./router/site_router');
const admin_router = require('./router/admin_router');
const products_router = require('./router/products_router');
const myscraping_router = require('./router/myscraping_router');
const catalogs_router = require('./router/catalogs_router');
const cart_router = require('./router/cart_router');
const orders_router = require('./router/orders_router');
@@ -238,6 +239,7 @@ connectToDatabase(connectionUrl, options)
app.use('/site', site_router);
app.use('/admin', admin_router);
app.use('/products', products_router);
app.use('/myscraping', myscraping_router);
app.use('/catalogs', catalogs_router);
app.use('/cart', cart_router);
app.use('/orders', orders_router);

View File

@@ -48,6 +48,7 @@ const Pickup = require('../models/pickup');
const { Newstosent } = require('../models/newstosent');
const { MyPage } = require('../models/mypage');
const { MyElem } = require('../models/myelem');
const { MyScrapingBook } = require('../models/myscrapingbook');
const { Cron } = require('../models/cron');
const { MyScheda } = require('../models/myscheda');
const { MyBot } = require('../models/bot');
@@ -200,6 +201,8 @@ module.exports = {
mytable = MyPage;
else if (tablename === 'myelems')
mytable = MyElem;
else if (tablename === 'myscrapingbooks')
mytable = MyScrapingBook;
else if (tablename === 'crons')
mytable = Cron;
else if (tablename === 'myschedas')

View File

@@ -1 +1 @@
1.2.47
1.2.48