Bonsoir,
Je fais un retour ici car je viens de tenter quelque chose d'approchant - avec toutefois une logique légèrement différente - et j'ai repensé à ce topic de Lionel. Alors moi je n'avais pas d'API (j'ai bien fait une demande mais personne au bout du fil) donc j'ai fait du sraping :
1. un premier utilisateur appelle une page de mon site qui déclenche un sraping permettant de requêter toutes les données nécessaires que je formate dans la foulée.
2. J'enregistre le tout dans un JSON, ainsi, TOUS les utilisateurs suivants bénéficierons des données directement disponibles dans le JSON.
3. Donc, si pas de JSON ou si JSON datant d'avant minuit du jour en cours, alors déclenchement du scraping, sinon recours aux données JSON.
Donc, la première différence est là : pas de tâche CRON, c'est le premier utilisateur de la journée qui lance le script. Ensuite, le JSON évite de devoir structurer une table à destination de données qui n'appartiennent pas à mon site : celles-ci peuvent évoluer au cours du temps, le fichier se doit de pouvoir être modulaire si besoin ; surtout pas une structure rigide comme une table SQL.
J'utilise uniquement Node.js, le script :
import { JSDOM } from 'jsdom'
import fs from 'fs/promises'
import path from 'path'
import { fileURLToPath } from 'url'
const jsonFilePath = path.join(path.dirname(fileURLToPath(import.meta.url)), '../data/eventsData.json')
function formatISODate(dateISO) {
const date = new Date(dateISO)
const formattedDate = new Intl.DateTimeFormat('fr-FR', {
weekday: 'long',
day: 'numeric',
month: 'long',
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
}).format(date)
return formattedDate.charAt(0).toUpperCase() + formattedDate.slice(1)
}
function formatEventDate(date) {
return date
.replace(/ -/, ',')
.replace(/(09:00)/, '<span style="color:var(--color5)">$1</span>')
.replace(/ -/, ',')
.replace(/(10:00)/, '<span style="color:var(--color2)">$1</span>')
}
function formatLocation(location) {
return location
.toLowerCase()
.split(' ')
.map(word => `${word.charAt(0).toUpperCase()}${word.slice(1)}`)
.join('-')
}
function processEvents(events) {
return events.map(event => ({
formattedDate: formatEventDate(formatISODate(event.dateISO)),
formattedLocation: formatLocation(event.location),
}))
}
async function scrapeWebsite(url) {
try {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`Erreur HTTP : ${response.status}`)
}
const html = await response.text()
const dom = new JSDOM(html)
const document = dom.window.document
const rows = document.querySelectorAll('article')
const events = []
rows.forEach(row => {
const dateISO = row.querySelector('meta[itemprop="startDate"]').getAttribute('content')
const location = row.querySelector('h4 [itemprop="addressLocality"]')?.textContent.trim()
if (dateISO && location) {
events.push({ dateISO, location })
}
})
return events
} catch (error) {
console.error('Erreur lors du scraping de', url, ':', error.message)
return []
}
}
async function scrapeMultiplePages() {
const urls = ['https://messes.info/horaires/CODE1', 'https://messes.info/horaires/CODE2']
const results = await Promise.all(urls.map(url => scrapeWebsite(url)))
const aggregatedEvents = results.flat()
const sortedEvents = aggregatedEvents.sort((a, b) => new Date(a.dateISO) - new Date(b.dateISO))
return processEvents(sortedEvents)
}
async function readJsonData() {
try {
const data = await fs.readFile(jsonFilePath, 'utf8')
return JSON.parse(data)
} catch (error) {
return null
}
}
async function writeJsonData(data) {
const jsonData = JSON.stringify(data, null, 2)
await fs.writeFile(jsonFilePath, jsonData, 'utf8')
}
async function getStoredEvents() {
const data = await readJsonData()
if (data) {
const lastUpdated = new Date(data.lastUpdated)
const now = new Date()
const isOutdated = now.setHours(0, 0, 0, 0) > lastUpdated.setHours(0, 0, 0, 0)
if (!isOutdated) {
return data.events
}
}
return null
}
async function getPlayground(req, res) {
let data = {}
data.url = req.url
data.name = 'Playground <span>. Page de test</span>'
data.title = 'Playground, page de test'
data.description = 'Page de test.'
let events = await getStoredEvents()
if (!events) {
events = await scrapeMultiplePages()
await writeJsonData({ lastUpdated: new Date(), events })
}
data.content = events
return res.view('playground', { data })
}
export { getPlayground }
Tout ça pour obtenir les horaires des messes dans mon secteur car je suis à cheval sur 3 paroisses et que j'en avais marre de devoir consulter tous les sites...
Modifié par Olivier C (07 Sep 2024 - 06:39)