import puppeteer from "puppeteer"; import express from "express"; import "dotenv/config"; const app = express(); const PORT = process.env.PORT || 3000; // Symbol configuration const symbols = { BPIDEST2040: { url: "https://www.bancobpi.pt/particulares/poupar-investir/ppr/bpi-destino-ppr-2040", name: "BPI Destino PPR 2040", }, BPIDEST2050: { url: "https://www.bancobpi.pt/particulares/poupar-investir/ppr/bpi-destino-ppr-2050", name: "BPI Destino PPR 2050", }, }; // Function to scrape price for a given symbol async function getLatestPrice(symbol) { const config = symbols[symbol]; if (!config) { throw new Error(`Invalid symbol: ${symbol}`); } // Launch the browser const browser = await puppeteer.launch({ args: ["--no-sandbox"], timeout: 10000, }); try { // Open a new tab const page = await browser.newPage(); // Visit the page and wait until network connections are completed await page.goto(config.url, { waitUntil: "networkidle2" }); // Interact with the DOM to retrieve the price and date const [price, date] = await page.evaluate(() => { const spanTags = document.getElementsByTagName("span"); const priceSearchText = "ÚLTIMA COTAÇÃO:"; const dateSearchText = "DATA COTAÇÃO:"; let priceElementFound; let dateElementFound; for (let i = 0; i < spanTags.length; i++) { if (spanTags[i].textContent.trim() == priceSearchText) { priceElementFound = spanTags[i]; if (priceElementFound && dateElementFound) break; } if (spanTags[i].textContent.trim() == dateSearchText) { dateElementFound = spanTags[i]; if (priceElementFound && dateElementFound) break; } } if (!priceElementFound || !dateElementFound) { throw new Error("Could not find price or date elements"); } return [ priceElementFound.nextSibling.innerHTML, dateElementFound.nextSibling.innerHTML, ]; }); const marketPrice = parseFloat( price.replace("€", "").trim().replace(",", "."), ); return { symbol, name: config.name, price: marketPrice, date: date.trim(), timestamp: new Date().toISOString(), }; } finally { // Always close the browser await browser.close(); } } // API endpoint to get latest price for a symbol app.get("/price/:symbol", async (req, res) => { const { symbol } = req.params; try { console.log(`Fetching price for symbol: ${symbol}`); const priceData = await getLatestPrice(symbol); console.log(`Success: ${symbol} - €${priceData.price}`); res.json(priceData); } catch (error) { console.error(`Error fetching price for ${symbol}:`, error.message); res.status(400).json({ error: error.message, symbol, }); } }); // Health check endpoint app.get("/health", (req, res) => { res.json({ status: "ok", availableSymbols: Object.keys(symbols) }); }); // Root endpoint with usage instructions app.get("/", (req, res) => { res.json({ message: "BPI Stock Price Scraper API", endpoints: { health: "/health", price: "/price/:symbol", }, availableSymbols: Object.keys(symbols), example: `/price/BPIDEST2040`, }); }); // Start the server app.listen(PORT, () => { console.log(`Server running on http://localhost:${PORT}`); console.log(`Available symbols: ${Object.keys(symbols).join(", ")}`); console.log(`Example: http://localhost:${PORT}/price/BPIDEST2040`); });