- estrazione dei dati del libro sul sito di Amazon.
- possibilità di visualizzare i dati estratti e di aggiornare i dati, sia solo se vuoti, che sovrascrivere tutti i dati.
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
"bcryptjs": "^3.0.2",
|
"bcryptjs": "^3.0.2",
|
||||||
"bluebird": "^3.7.2",
|
"bluebird": "^3.7.2",
|
||||||
"body-parser": "^1.20.3",
|
"body-parser": "^1.20.3",
|
||||||
|
"cheerio": "^1.0.0",
|
||||||
"cookie-parser": "^1.4.7",
|
"cookie-parser": "^1.4.7",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"country-codes-list": "^2.0.0",
|
"country-codes-list": "^2.0.0",
|
||||||
|
|||||||
@@ -238,6 +238,12 @@ const productSchema = new Schema({
|
|||||||
date_updated: {
|
date_updated: {
|
||||||
type: Date,
|
type: Date,
|
||||||
},
|
},
|
||||||
|
scraped: {
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
scraped_date: {
|
||||||
|
type: Date,
|
||||||
|
},
|
||||||
validaprod: {
|
validaprod: {
|
||||||
esito: {
|
esito: {
|
||||||
type: Number,
|
type: Number,
|
||||||
@@ -422,6 +428,9 @@ module.exports.executeQueryPickup = async function (idapp, params) {
|
|||||||
return [...risexact, ...ris];
|
return [...risexact, ...ris];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports.getProductByIsbn = function (idapp, isbn) {
|
||||||
|
return Product.findAllIdApp(idapp, null, null, null, isbn);
|
||||||
|
};
|
||||||
module.exports.getProductByCode = function (idapp, code) {
|
module.exports.getProductByCode = function (idapp, code) {
|
||||||
return Product.findAllIdApp(idapp, code);
|
return Product.findAllIdApp(idapp, code);
|
||||||
};
|
};
|
||||||
@@ -454,7 +463,7 @@ module.exports.isLowQuantityInStockById = async function (id) {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.findAllIdApp = async function (idapp, code, id, all) {
|
module.exports.findAllIdApp = async function (idapp, code, id, all, isbn) {
|
||||||
let myfind = {};
|
let myfind = {};
|
||||||
let myqueryadd = {};
|
let myqueryadd = {};
|
||||||
let query = [];
|
let query = [];
|
||||||
@@ -478,6 +487,9 @@ module.exports.findAllIdApp = async function (idapp, code, id, all) {
|
|||||||
if (code) {
|
if (code) {
|
||||||
myfind = { ...myfind, code };
|
myfind = { ...myfind, code };
|
||||||
}
|
}
|
||||||
|
if (isbn) {
|
||||||
|
myfind = { ...myfind, sbn };
|
||||||
|
}
|
||||||
if (id) {
|
if (id) {
|
||||||
myqueryadd = {
|
myqueryadd = {
|
||||||
$addFields: {
|
$addFields: {
|
||||||
|
|||||||
@@ -183,6 +183,13 @@ const productInfoSchema = new Schema({
|
|||||||
sottotitolo: String,
|
sottotitolo: String,
|
||||||
link_macro: String,
|
link_macro: String,
|
||||||
|
|
||||||
|
scraped: {
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
scraped_date: {
|
||||||
|
type: Date,
|
||||||
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var productInfo = module.exports = mongoose.model('ProductInfo', productInfoSchema);
|
var productInfo = module.exports = mongoose.model('ProductInfo', productInfoSchema);
|
||||||
|
|||||||
@@ -587,7 +587,7 @@ class Macro {
|
|||||||
setta = false,
|
setta = false,
|
||||||
importa = true;
|
importa = true;
|
||||||
|
|
||||||
let product = { ...productInput };
|
let product = { ...productInput, deleted: false };
|
||||||
|
|
||||||
|
|
||||||
if (options.inputdaGM)
|
if (options.inputdaGM)
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
import axios from 'axios';
|
const axios = require('axios');
|
||||||
import cheerio from 'cheerio';
|
const cheerio = require('cheerio');
|
||||||
|
|
||||||
|
const Product = require('../models/product');
|
||||||
|
const ProductInfo = require('../models/productInfo');
|
||||||
|
|
||||||
|
const tools = require('../tools/general');
|
||||||
|
|
||||||
class AmazonBookScraper {
|
class AmazonBookScraper {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.baseUrl = 'https://www.amazon.it/dp/';
|
this.baseUrl = 'https://www.amazon.it/dp/';
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchPage(isbn) {
|
async fetchPageISBN10(isbn10) {
|
||||||
const url = `${this.baseUrl}${isbn}`;
|
const url = `${this.baseUrl}${isbn10}`;
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.get(url, {
|
const { data } = await axios.get(url, {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -20,96 +25,220 @@ class AmazonBookScraper {
|
|||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Errore fetching ISBN ${isbn}:`, err.message);
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extractData(html) {
|
getTitleByProductInfo(productInfo) {
|
||||||
|
try {
|
||||||
|
return productInfo?.name.trim();
|
||||||
|
} catch (e) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async extractData(myproduct, html) {
|
||||||
const $ = cheerio.load(html);
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
|
const productInfo = await ProductInfo.findOne({ _id: myproduct.idProductInfo }).lean();
|
||||||
|
|
||||||
|
const title_ondb = this.getTitleByProductInfo(productInfo);
|
||||||
|
|
||||||
// Titolo
|
// Titolo
|
||||||
let title = $('#productTitle').text().trim() || null;
|
let title = $('#productTitle').text().trim() || null;
|
||||||
|
// Sottotitolo (Amazon lo mette nel titolo)
|
||||||
|
|
||||||
// Sottotitolo (Amazon spesso lo mette in #productSubtitle o nel titolo, proveremo)
|
let dettagli = $('#productSubtitle').text().trim() || null;
|
||||||
let subtitle = $('#productSubtitle').text().trim() || null;
|
|
||||||
|
|
||||||
// Numero pagine, formato, edizione
|
const ris = this.extractSubtitle(title, title_ondb);
|
||||||
// Questi dati spesso sono nella tabella dettagli prodotto con id #detailBullets_feature_div o #productDetailsTable
|
|
||||||
// Proviamo a estrarre da #detailBullets_feature_div
|
|
||||||
|
|
||||||
let pages = null;
|
let sottotitolo = title_ondb ? ris?.subtitle : '';
|
||||||
let format = null;
|
|
||||||
let edition = null;
|
|
||||||
|
|
||||||
$('#detailBullets_feature_div li').each((i, el) => {
|
let titoloOriginale = '';
|
||||||
const label = $(el).find('span.a-text-bold').text().trim().toLowerCase();
|
|
||||||
const value = $(el).find('span').last().text().trim();
|
|
||||||
|
|
||||||
if (label.includes('pagine') || label.includes('pagine stampate')) {
|
if (ris?.titoloOriginale) {
|
||||||
pages = value;
|
// E' presente il vero titolo del Libro !
|
||||||
} else if (label.includes('formato')) {
|
titoloOriginale = ris.titoloOriginale;
|
||||||
format = value;
|
|
||||||
} else if (label.includes('edizione')) {
|
|
||||||
edition = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// fallback su #productDetailsTable (altro possibile layout)
|
|
||||||
if (!pages || !format || !edition) {
|
|
||||||
$('#productDetailsTable .content tr').each((i, el) => {
|
|
||||||
const label = $(el).find('th').text().trim().toLowerCase();
|
|
||||||
const value = $(el).find('td').text().trim();
|
|
||||||
|
|
||||||
if (!pages && (label.includes('pagine') || label.includes('pagine stampate'))) {
|
|
||||||
pages = value;
|
|
||||||
} else if (!format && label.includes('formato')) {
|
|
||||||
format = value;
|
|
||||||
} else if (!edition && label.includes('edizione')) {
|
|
||||||
edition = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { title, subtitle, pages, format, edition };
|
if (sottotitolo && title_ondb) {
|
||||||
|
// significa che il titolo è dentro al sottotitolo, quindi prendi per buono quello nostro
|
||||||
|
title = title_ondb;
|
||||||
}
|
}
|
||||||
|
|
||||||
async scrapeISBN(isbn) {
|
let numpagine = null;
|
||||||
const html = await this.fetchPage(isbn);
|
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;
|
if (!html) return null;
|
||||||
|
|
||||||
const data = this.extractData(html);
|
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;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async scrapeMultiple(isbnList) {
|
async scrapeMultiple(isbnList, options) {
|
||||||
const results = [];
|
const results = [];
|
||||||
for (const isbn of isbnList) {
|
for (const isbn of isbnList) {
|
||||||
console.log(`Scraping ISBN: ${isbn}`);
|
console.log(`Scraping ISBN: ${isbn}`);
|
||||||
const data = await this.scrapeISBN(isbn);
|
const myproduct = null;
|
||||||
|
/// myproduct...
|
||||||
|
const data = await this.scrapeISBN(myproduct, isbn, options);
|
||||||
results.push({ isbn, ...data });
|
results.push({ isbn, ...data });
|
||||||
// Per evitare blocchi, metti una pausa (es. 2 secondi)
|
// Per evitare blocchi, metti una pausa (es. 2 secondi)
|
||||||
await new Promise((r) => setTimeout(r, 2000));
|
await new Promise((r) => setTimeout(r, 2000));
|
||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generateHtmlTableFromObject(obj) {
|
||||||
|
if (!obj || typeof obj !== 'object') return '';
|
||||||
|
|
||||||
|
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>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function ScraperDataAmazon(idapp, options) {
|
html += '</tbody></table>';
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
async ScraperDataAmazon(idapp, options) {
|
||||||
const scraper = new AmazonBookScraper();
|
const scraper = new AmazonBookScraper();
|
||||||
const isbn = options.isbn;
|
const isbn = options.isbn;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await scraper.scrapeISBN(isbn);
|
const myproduct = await Product.getProductByIsbn(isbn);
|
||||||
|
const data = await scraper.scrapeISBN(myproduct, isbn, options);
|
||||||
|
|
||||||
|
// let html = this.generateHtmlTableFromObject(data);
|
||||||
|
|
||||||
console.log(data);
|
console.log(data);
|
||||||
return data;
|
return res.status(200).send({ code: server_constants.RIS_CODE_OK, data, html });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
return res.status(400).send({ code: server_constants.RIS_CODE_ERR, msg: '' });
|
return res.status(400).send({ code: server_constants.RIS_CODE_ERR, msg: '' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function ScraperMultipleDataAmazon(idapp, options) {
|
async ScraperMultipleDataAmazon(idapp, options) {
|
||||||
const scraper = new AmazonBookScraper();
|
const scraper = new AmazonBookScraper();
|
||||||
const isbnList = ['8850224248']; // metti i tuoi ISBN qui
|
const isbnList = ['8850224248']; // metti i tuoi ISBN qui
|
||||||
|
|
||||||
@@ -122,4 +251,154 @@ export async function ScraperMultipleDataAmazon(idapp, options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AmazonBookScraper;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
return publisher || null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = AmazonBookScraper;
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const Gasordine = require('../models/gasordine');
|
|||||||
|
|
||||||
const { User } = require('../models/user');
|
const { User } = require('../models/user');
|
||||||
|
|
||||||
const AmazonBookScraper = require('../modules/Scraping');
|
const AmazonBookScraper = require('../modules/scraping');
|
||||||
|
|
||||||
const { Catalog } = require('../models/catalog');
|
const { Catalog } = require('../models/catalog');
|
||||||
const { RaccoltaCataloghi } = require('../models/raccoltacataloghi');
|
const { RaccoltaCataloghi } = require('../models/raccoltacataloghi');
|
||||||
@@ -2363,13 +2363,24 @@ router.post('/cloudflare', authenticate, async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.post('/scraper', authenticate, async (req, res) => {
|
router.post('/scraper', authenticate, async (req, res) => {
|
||||||
|
idapp = req.body.idapp;
|
||||||
|
idProduct = req.body.product_id;
|
||||||
|
options = req.body.options;
|
||||||
|
|
||||||
const scraper = new AmazonBookScraper();
|
const scraper = new AmazonBookScraper();
|
||||||
const isbn = req.data.options.isbn;
|
|
||||||
|
const product = await Product.getProductById(idProduct);
|
||||||
|
let isbn = '';
|
||||||
|
if (product) {
|
||||||
|
isbn = product.isbn;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await scraper.scrapeISBN(isbn);
|
let data = null;
|
||||||
|
if (isbn) {
|
||||||
|
data = await scraper.scrapeISBN(product, isbn, options);
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
}
|
||||||
return res.send(data);
|
return res.send(data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|||||||
@@ -6299,5 +6299,15 @@ module.exports = {
|
|||||||
return filename;
|
return filename;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isDateValid(mydate) {
|
||||||
|
try {
|
||||||
|
return (
|
||||||
|
mydate instanceof Date && isFinite(mydate.getTime()) && mydate.toISOString().split('T')[0] !== '1970-01-01'
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
61
yarn.lock
61
yarn.lock
@@ -2102,6 +2102,23 @@ cheerio@^0.22.0:
|
|||||||
lodash.reject "^4.4.0"
|
lodash.reject "^4.4.0"
|
||||||
lodash.some "^4.4.0"
|
lodash.some "^4.4.0"
|
||||||
|
|
||||||
|
cheerio@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0.tgz#1ede4895a82f26e8af71009f961a9b8cb60d6a81"
|
||||||
|
integrity sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==
|
||||||
|
dependencies:
|
||||||
|
cheerio-select "^2.1.0"
|
||||||
|
dom-serializer "^2.0.0"
|
||||||
|
domhandler "^5.0.3"
|
||||||
|
domutils "^3.1.0"
|
||||||
|
encoding-sniffer "^0.2.0"
|
||||||
|
htmlparser2 "^9.1.0"
|
||||||
|
parse5 "^7.1.2"
|
||||||
|
parse5-htmlparser2-tree-adapter "^7.0.0"
|
||||||
|
parse5-parser-stream "^7.1.2"
|
||||||
|
undici "^6.19.5"
|
||||||
|
whatwg-mimetype "^4.0.0"
|
||||||
|
|
||||||
chokidar@^3.5.1, chokidar@^3.5.2, chokidar@^3.5.3:
|
chokidar@^3.5.1, chokidar@^3.5.2, chokidar@^3.5.3:
|
||||||
version "3.6.0"
|
version "3.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
|
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
|
||||||
@@ -2874,7 +2891,7 @@ domutils@^2.4.2:
|
|||||||
domelementtype "^2.2.0"
|
domelementtype "^2.2.0"
|
||||||
domhandler "^4.2.0"
|
domhandler "^4.2.0"
|
||||||
|
|
||||||
domutils@^3.0.1:
|
domutils@^3.0.1, domutils@^3.1.0:
|
||||||
version "3.2.2"
|
version "3.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.2.2.tgz#edbfe2b668b0c1d97c24baf0f1062b132221bc78"
|
resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.2.2.tgz#edbfe2b668b0c1d97c24baf0f1062b132221bc78"
|
||||||
integrity sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==
|
integrity sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==
|
||||||
@@ -3011,6 +3028,14 @@ encoding-japanese@2.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/encoding-japanese/-/encoding-japanese-2.2.0.tgz#0ef2d2351250547f432a2dd155453555c16deb59"
|
resolved "https://registry.yarnpkg.com/encoding-japanese/-/encoding-japanese-2.2.0.tgz#0ef2d2351250547f432a2dd155453555c16deb59"
|
||||||
integrity sha512-EuJWwlHPZ1LbADuKTClvHtwbaFn4rOD+dRAbWysqEOXRc2Uui0hJInNJrsdH0c+OhJA4nrCBdSkW4DD5YxAo6A==
|
integrity sha512-EuJWwlHPZ1LbADuKTClvHtwbaFn4rOD+dRAbWysqEOXRc2Uui0hJInNJrsdH0c+OhJA4nrCBdSkW4DD5YxAo6A==
|
||||||
|
|
||||||
|
encoding-sniffer@^0.2.0:
|
||||||
|
version "0.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz#799569d66d443babe82af18c9f403498365ef1d5"
|
||||||
|
integrity sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==
|
||||||
|
dependencies:
|
||||||
|
iconv-lite "^0.6.3"
|
||||||
|
whatwg-encoding "^3.1.1"
|
||||||
|
|
||||||
end-of-stream@^1.1.0, end-of-stream@^1.4.4:
|
end-of-stream@^1.1.0, end-of-stream@^1.4.4:
|
||||||
version "1.4.4"
|
version "1.4.4"
|
||||||
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
|
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
|
||||||
@@ -3064,6 +3089,11 @@ entities@^4.2.0, entities@^4.4.0, entities@^4.5.0:
|
|||||||
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
|
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
|
||||||
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
|
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
|
||||||
|
|
||||||
|
entities@^6.0.0:
|
||||||
|
version "6.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/entities/-/entities-6.0.0.tgz#09c9e29cb79b0a6459a9b9db9efb418ac5bb8e51"
|
||||||
|
integrity sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==
|
||||||
|
|
||||||
error-ex@^1.3.1:
|
error-ex@^1.3.1:
|
||||||
version "1.3.2"
|
version "1.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
|
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
|
||||||
@@ -4175,6 +4205,16 @@ htmlparser2@^8.0.0, htmlparser2@^8.0.1, htmlparser2@^8.0.2:
|
|||||||
domutils "^3.0.1"
|
domutils "^3.0.1"
|
||||||
entities "^4.4.0"
|
entities "^4.4.0"
|
||||||
|
|
||||||
|
htmlparser2@^9.1.0:
|
||||||
|
version "9.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-9.1.0.tgz#cdb498d8a75a51f739b61d3f718136c369bc8c23"
|
||||||
|
integrity sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==
|
||||||
|
dependencies:
|
||||||
|
domelementtype "^2.3.0"
|
||||||
|
domhandler "^5.0.3"
|
||||||
|
domutils "^3.1.0"
|
||||||
|
entities "^4.5.0"
|
||||||
|
|
||||||
http-errors@2.0.0:
|
http-errors@2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
|
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
|
||||||
@@ -6662,6 +6702,13 @@ parse5-htmlparser2-tree-adapter@^7.0.0:
|
|||||||
domhandler "^5.0.3"
|
domhandler "^5.0.3"
|
||||||
parse5 "^7.0.0"
|
parse5 "^7.0.0"
|
||||||
|
|
||||||
|
parse5-parser-stream@^7.1.2:
|
||||||
|
version "7.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz#d7c20eadc37968d272e2c02660fff92dd27e60e1"
|
||||||
|
integrity sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==
|
||||||
|
dependencies:
|
||||||
|
parse5 "^7.0.0"
|
||||||
|
|
||||||
parse5@^7.0.0, parse5@^7.2.1:
|
parse5@^7.0.0, parse5@^7.2.1:
|
||||||
version "7.2.1"
|
version "7.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.2.1.tgz#8928f55915e6125f430cc44309765bf17556a33a"
|
resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.2.1.tgz#8928f55915e6125f430cc44309765bf17556a33a"
|
||||||
@@ -6669,6 +6716,13 @@ parse5@^7.0.0, parse5@^7.2.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
entities "^4.5.0"
|
entities "^4.5.0"
|
||||||
|
|
||||||
|
parse5@^7.1.2:
|
||||||
|
version "7.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.3.0.tgz#d7e224fa72399c7a175099f45fc2ad024b05ec05"
|
||||||
|
integrity sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==
|
||||||
|
dependencies:
|
||||||
|
entities "^6.0.0"
|
||||||
|
|
||||||
parseley@^0.12.0:
|
parseley@^0.12.0:
|
||||||
version "0.12.1"
|
version "0.12.1"
|
||||||
resolved "https://registry.yarnpkg.com/parseley/-/parseley-0.12.1.tgz#4afd561d50215ebe259e3e7a853e62f600683aef"
|
resolved "https://registry.yarnpkg.com/parseley/-/parseley-0.12.1.tgz#4afd561d50215ebe259e3e7a853e62f600683aef"
|
||||||
@@ -8632,6 +8686,11 @@ undici-types@~6.20.0:
|
|||||||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433"
|
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433"
|
||||||
integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==
|
integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==
|
||||||
|
|
||||||
|
undici@^6.19.5:
|
||||||
|
version "6.21.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/undici/-/undici-6.21.3.tgz#185752ad92c3d0efe7a7d1f6854a50f83b552d7a"
|
||||||
|
integrity sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==
|
||||||
|
|
||||||
unicode-emoji-modifier-base@^1.0.0:
|
unicode-emoji-modifier-base@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz#dbbd5b54ba30f287e2a8d5a249da6c0cef369459"
|
resolved "https://registry.yarnpkg.com/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz#dbbd5b54ba30f287e2a8d5a249da6c0cef369459"
|
||||||
|
|||||||
Reference in New Issue
Block a user