Compare commits
2 Commits
| 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
|
# 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