This commit is contained in:
@@ -1,103 +1,128 @@
|
||||
import puppeteer from "puppeteer";
|
||||
import process from "node:process";
|
||||
import axios from "axios";
|
||||
import express from "express";
|
||||
import "dotenv/config";
|
||||
|
||||
const contas = {
|
||||
bpi2040: {
|
||||
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",
|
||||
ghostfolioName: "GF_BPI_Destino_PPR_2040",
|
||||
name: "BPI Destino PPR 2040",
|
||||
},
|
||||
bpi2050: {
|
||||
BPIDEST2050: {
|
||||
url: "https://www.bancobpi.pt/particulares/poupar-investir/ppr/bpi-destino-ppr-2050",
|
||||
ghostfolioName: "GF_BPI_Destino_PPR_2050",
|
||||
name: "BPI Destino PPR 2050",
|
||||
},
|
||||
};
|
||||
|
||||
async function parseLogRocketBlogHome(conta) {
|
||||
// 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,
|
||||
});
|
||||
|
||||
// Open a new tab
|
||||
const page = await browser.newPage();
|
||||
try {
|
||||
// Open a new tab
|
||||
const page = await browser.newPage();
|
||||
|
||||
// Visit the page and wait until network connections are completed
|
||||
await page.goto(conta.url, { waitUntil: "networkidle2" });
|
||||
// Visit the page and wait until network connections are completed
|
||||
await page.goto(config.url, { waitUntil: "networkidle2" });
|
||||
|
||||
// Interact with the DOM to retrieve the titles
|
||||
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;
|
||||
// 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];
|
||||
console.log(`Found ${priceSearchText}.`);
|
||||
if (priceElementFound && dateElementFound) break;
|
||||
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 (spanTags[i].textContent.trim() == dateSearchText) {
|
||||
dateElementFound = spanTags[i];
|
||||
console.log(`Found ${dateSearchText}.`);
|
||||
if (priceElementFound && dateElementFound) break;
|
||||
|
||||
if (!priceElementFound || !dateElementFound) {
|
||||
throw new Error("Could not find price or date elements");
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
priceElementFound.nextSibling.innerHTML,
|
||||
dateElementFound.nextSibling.innerHTML,
|
||||
];
|
||||
});
|
||||
return [
|
||||
priceElementFound.nextSibling.innerHTML,
|
||||
dateElementFound.nextSibling.innerHTML,
|
||||
];
|
||||
});
|
||||
|
||||
// Don't forget to close the browser instance to clean up the memory
|
||||
await browser.close();
|
||||
const marketPrice = parseFloat(
|
||||
price.replace("€", "").trim().replace(",", "."),
|
||||
);
|
||||
|
||||
const marketPrice = parseFloat(
|
||||
price.replace("€", "").trim().replace(",", "."),
|
||||
);
|
||||
|
||||
// Print the results
|
||||
console.log(decodeURIComponent(conta.ghostfolioName));
|
||||
console.log(`Current price: € ${marketPrice}`);
|
||||
console.log(`Date: ${date}`);
|
||||
|
||||
const bearerTokenResponse = await axios.post(
|
||||
`${process.env.GHOSTFOLIO_HOST}/api/v1/auth/anonymous`,
|
||||
{
|
||||
accessToken: process.env.GHOSTFOLIO_SECURITY_TOKEN,
|
||||
},
|
||||
);
|
||||
|
||||
const response = await axios
|
||||
.post(
|
||||
`${process.env.GHOSTFOLIO_HOST}/api/v1/market-data/MANUAL/${conta.ghostfolioName}`,
|
||||
{
|
||||
marketData: [
|
||||
{
|
||||
date,
|
||||
marketPrice,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${bearerTokenResponse.data.authToken}`,
|
||||
},
|
||||
},
|
||||
)
|
||||
.catch(console.error);
|
||||
|
||||
console.log(response.status);
|
||||
return {
|
||||
symbol,
|
||||
name: config.name,
|
||||
price: marketPrice,
|
||||
date: date.trim(),
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
} finally {
|
||||
// Always close the browser
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
parseLogRocketBlogHome(contas.bpi2040),
|
||||
parseLogRocketBlogHome(contas.bpi2050),
|
||||
]);
|
||||
// API endpoint to get latest price for a symbol
|
||||
app.get("/price/:symbol", async (req, res) => {
|
||||
const { symbol } = req.params;
|
||||
|
||||
process.exit(0);
|
||||
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`);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user