Compare commits
2 Commits
c499da44e9
...
fdf746c067
| Author | SHA1 | Date | |
|---|---|---|---|
| fdf746c067 | |||
| 81b898a8f1 |
@@ -0,0 +1,2 @@
|
||||
GHOSTFOLIO_SECURITY_TOKEN=
|
||||
GHOSTFOLIO_HOST=
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
FROM ghcr.io/puppeteer/puppeteer:22
|
||||
|
||||
USER root
|
||||
|
||||
# Add user so we don't need --no-sandbox.
|
||||
RUN mkdir -p /home/pptruser/Downloads /app \
|
||||
&& chown -R pptruser:pptruser /home/pptruser \
|
||||
&& chown -R pptruser:pptruser /app
|
||||
|
||||
# Run everything after as non-privileged user.
|
||||
USER pptruser
|
||||
|
||||
# Install Puppeteer under /node_modules so it's available system-wide
|
||||
COPY package.json /app/
|
||||
COPY yarn.lock /app/
|
||||
RUN cd /app/ && yarn
|
||||
COPY .env /app/
|
||||
COPY index.mjs /app/
|
||||
|
||||
ARG GHOSTFOLIO_SECURITY_TOKEN
|
||||
ARG GHOSTFOLIO_HOST
|
||||
ENV GHOSTFOLIO_SECURITY_TOKEN=$GHOSTFOLIO_SECURITY_TOKEN
|
||||
ENV GHOSTFOLIO_HOST=$GHOSTFOLIO_HOST
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/node", "/app/index.mjs"]
|
||||
@@ -1,3 +1,21 @@
|
||||
# bpi-stock-price-scraper
|
||||
|
||||
Extrair preço de PPRs para introduzir no Ghostfolio
|
||||
Extrair preço de PPRs para introduzir no Ghostfolio
|
||||
|
||||
# Utilização
|
||||
|
||||
Criar ficheiro `.env` a partir do `.env.example`
|
||||
|
||||
## Docker
|
||||
|
||||
```
|
||||
docker compose build
|
||||
docker compose up
|
||||
```
|
||||
|
||||
## Node
|
||||
|
||||
```
|
||||
yarn
|
||||
node index.mjs
|
||||
```
|
||||
@@ -0,0 +1,9 @@
|
||||
services:
|
||||
scraper:
|
||||
image: bpi-stock-price-scraper
|
||||
environment:
|
||||
- GHOSTFOLIO_SECURITY_TOKEN=${GHOSTFOLIO_SECURITY_TOKEN} # here it is!
|
||||
- GHOSTFOLIO_HOST=${GHOSTFOLIO_HOST} # here it is!
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
@@ -0,0 +1,8 @@
|
||||
import globals from "globals";
|
||||
import pluginJs from "@eslint/js";
|
||||
|
||||
|
||||
export default [
|
||||
{languageOptions: { globals: globals.browser }},
|
||||
pluginJs.configs.recommended,
|
||||
];
|
||||
@@ -0,0 +1,100 @@
|
||||
import puppeteer from "puppeteer";
|
||||
import process from "node:process";
|
||||
import axios from "axios";
|
||||
import "dotenv/config";
|
||||
|
||||
const contas = {
|
||||
bpi2040: {
|
||||
url: "https://www.bancobpi.pt/particulares/poupar-investir/ppr/bpi-destino-ppr-2040",
|
||||
ghostfolioName: "BPI%20Destino%20PPR%202040",
|
||||
},
|
||||
bpi2050: {
|
||||
url: "https://www.bancobpi.pt/particulares/poupar-investir/ppr/bpi-destino-ppr-2050",
|
||||
ghostfolioName: "BPI%20Destino%20PPR%202050",
|
||||
},
|
||||
};
|
||||
|
||||
async function parseLogRocketBlogHome(conta) {
|
||||
// Launch the browser
|
||||
const browser = await puppeteer.launch();
|
||||
|
||||
// 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" });
|
||||
|
||||
// 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;
|
||||
|
||||
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;
|
||||
}
|
||||
if (spanTags[i].textContent.trim() == dateSearchText) {
|
||||
dateElementFound = spanTags[i];
|
||||
console.log(`Found ${dateSearchText}.`);
|
||||
if (priceElementFound && dateElementFound) break;
|
||||
}
|
||||
}
|
||||
|
||||
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(",", ".")
|
||||
);
|
||||
|
||||
// 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/admin/market-data/MANUAL/${conta.ghostfolioName}`,
|
||||
{
|
||||
marketData: [
|
||||
{
|
||||
date,
|
||||
marketPrice,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${bearerTokenResponse.data.authToken}`,
|
||||
},
|
||||
}
|
||||
)
|
||||
.catch(console.error);
|
||||
|
||||
console.log(response.status);
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
parseLogRocketBlogHome(contas.bpi2040),
|
||||
parseLogRocketBlogHome(contas.bpi2050),
|
||||
]);
|
||||
|
||||
process.exit(0);
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "bpi-stock-price-scraper",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"repository": "ssh://git@10.0.2.28:222/lino-authelia/bpi-stock-price-scraper.git",
|
||||
"author": "Lino Silva <me@lino.cooking>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.7.7",
|
||||
"dotenv": "^16.4.5",
|
||||
"puppeteer": "^23.5.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.12.0",
|
||||
"eslint": "^9.12.0",
|
||||
"globals": "^15.11.0"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user