Adatok weboldalról - 3. rész a változás mentése

Adatok weboldalról - 3. rész a változás mentése

Adat kinyerés Weboldal Node.js

2023. aprilis 10.

Miután a korábbi részekben sikeresen kiszedtük az összes információt az általunk kinézett oldalról, nincs más dolgunk, mint a változás lekérése. Ehhez csak annyit kell csinálni, hogy eltesszük a legutolsó N darab mentett adatnak egy fix és egyedi azonosítóját, ilyen lehet például az url.

Azért nem jó az, ha csak az utolsót tesszük el, mert előfordulhat olyan eset, hogy kikerült már egy cikk, de utólag módosítani kellet rajta. Ebből eredően megváltozhat akár még az elérés url-je is. Amikor ilyen történik, akkor a program sose találjuk mág meg azt a cikket, ami egyezik az utolsára mentettel és így újra leszedi az összeset.

Korábbi bejegyzések, előzmények:

Folytassuk akkor onnan, ahol abbahagytuk, ami annyit jelent, hogy egy változóban megvan az összes kinyert információ amire szükségünk volt. A legutóbbi példánkban ezt egy articles nevű változóban tároltuk, ami egy objektum. Ebben az objektumban biztosan van egy title és egy link kulcs és a hozzá tartozó érték.

Előkészület

Amikor a kódunk már túl nagy lesz, de még nem tördeljük szét érdemes minden kézzel beírt dolgot változóba tenni. Amikor már van bármilyen olyan értékünk ami egynél többnél fordul elő akkor meg pláne. Én az alábbi dolgokat találtam, amivel lehet egyszerűsíteni:

const pageArticleSize = 50;
const atricleURL = `https://akjournals.com/search?access=all&pageSize=${pageArticleSize}&q1=agricultural&sort=datedescending`;

const lastLinksFileName = "lastLinks.txt";
const newLinksFileName = "articles.json";

Így ezeket a későbbiek során, ha újra kellenek csak elég behivatkozni, illetve ha változtatni szeretnénk például, hogy hány cikk legyen egy oldalon elég csak egytlen helyen átírni.

Utolsó 10 azonosító mentése

Figyelembe véve, hogy a lekért adatok időrendben csökkenő sorrendben vannak, így nekünk az első elemek kellenek a tömbből.

const lastAtricle = articles.slice(0, 10);

Most már megvannak az utolsó cikkek is. Írjuk ki a link értékeket egy állományba, aminek a neve lehet bármi. A példában elnevezem lastLinks.txt-nek. Ehhez a map függvénnyel végig megyünk a tömbön és a benne lévő teljes objektumot csak a link értékére módosítjuk. Itt annyi plusz bekerül, hogy a linkek tartalmaznak különböző plusz paramétereket. Ami fontos, hogy ez nem igaz minden oldalra, tehát ha más oldalról gyűjtünk adatot akkor ez nem biztos, hogy így van. Mivel viszont itt így oldották meg ezért a linkeket meg kell tisztítani. Ehhez az url névű csomagot fogjuk használni, ami a szöveges url paramétert tényleges hivatkozássá alakítja. Így van lehetőségünk a pathname paraméterrel a tényleges linket kikérni a queryString nélkül. Tehát nem lesz a linkek mögött a ? és az utána jövő plusz paraméterek.

const url = require("url");

Majd ezeket összefűzzük egy \n (sortörés) karakterrel, hogy az összes link külön sorban szépen szeparáltan legyen meg.

Ehhez írjunk egy függvényt, hogy később csak meg kelljen hívni.

function writeLastArticlesLink(articles, numberOfLinks = 10) {
	const lastAtricle = articles.slice(0, numberOfLinks);
	fs.writeFileSync(
		lastLinksFileName,
		lastAtricle.map((article) => url.parse(article.link).pathname).join("\n"),
		"utf8"
	);
}

A függvényünk kapott egy második paramétert, ami megmondja, hogy hány linket szűrjön le. Ez alapértelmezetten 10, de a függvény hívása során szabadon változtatható.

Amikor ezzel megvagyok, akkor innentől kezdve már csak addig kell keresni és menteni a cikkeket, amíg az új cikk azonosítója nem szerepel ebben a listában. Ezzel a megoldással már nem kell újra végig menni az összes bejegyzésen.

Bővítés

A következő teendőnk, az listánk bővítése az újabb cikkekkel. Itt ugyanúgy be kell olvasni a cikkeket, addig amíg el nem fogy az összes vagy nincs link egyezés az utolsó általunk mentett állapotokkal. Az új adatokat pedig vagy összegyűjtjük és egybe tesszük be a már korábban mentett adatok közé vagy egyesével szúrjuk be, mint a folytatás. A példában most ugyanúgy összegyűjtjük, mint korábban és egyesítjük a két gyűjteményt.

Azért ezt a módszert választottam, mert korábban csökkenő sorrendben tettük el az értékeket. Tehát a mentett állományunk elején mindig a legfrissebb van. Így amikor összegyűjtjük az újakat és azok is csökkenő sorrendben lesznek, akkor az állomány elejére kell helyeznünk. Így marad meg a folytonosság. Pont ezért nem tudjuk csak úgy az állományunk végére beszúrni az új bejegyzéseket, mert akkor dátumilag összevissza lennének.

Az utolsók beolvasása

Az első teendő beolvasni a legutóbbi hivatkozások listáját, amit mentettünk. Ezen túl hozzunk létre egy változót, amivel tudjuk ellenőrizni, hogy megvolt e már az utolsó.

let isLast = false;
if (fs.existsSync(lastLinksFileName)) {
	lastLinks = fs.readFileSync(lastLinksFileName, "utf8").split("\n");
}

Fontos, hogy le kell ellenőrizni az olvasás elött, hogy létezik e már az adott állomány. Erre azért van szükség mert, ha elsőnek futtattjuk a kódot, akkor még nem létezik, mert még sose mentettük le az utolsó cikkek linkjeit. Amennyiben ezt nem tesszük meg és tényleg nem létezik, akkor hibát kapunk és leáll a programunk.

Így máris rendelkezésünkre áll egy olyan azonosító lista, amiben tudunk keresni. A következő teendőnk, hogy meg is nézzük ebben a listánkban szerepel e az aktuális link vagy sem. Ezt oda kell beépíteni, ahol a cikkeket feldolgozzuk és a tömbbe rakjuk, ami nem más mint a dataClear függvény.

const pathName = url.parse(linkValue).pathname;
if (!isLast && !lastLinks.includes(pathName)) {
	articles.push({ title: titleValue, link: linkValue });
} else {
	isLast = true;
	return;
}

Megjegyzés: Itt is el kell távolítani a linkek felesleges részét, különben nem lesz egyezés soha.

A push bekerült egy feltételbe, hogy csak akkor teheti be az új cikket a tömbbe, ha még nem vagyunk túl az utolsón és az aktuális linkje sincs benne a korábbiakban. Ellenkező esetben, pedig beállítjuk az isLast változót igaz értékre. Innentől kezdve a futás során már nem tesz bele több értéket.

Még ki kell egészítsük a while függvényt is ezzel a plusz feltétellel.

async function getArticles(url) {

	let page = 1;
	while (articleCount === pageArticleSize && isLast === false) {

Megjegyzés: Ide is bekerült a korábban megadott pageArticleSize változónk, ami megmondja, hogy hány cikk van egy oldalon. Így biztos nem lesz gond, ha módosítjuk.

Visszatérve a kódra, ami addig fut, amíg nem értük el az utolsót. Utána egyszerűen leáll és befejezi a cikkek letöltését és feldolgozását.

Adatok mentése

Utolsó feladatunk az adatok összefűzése. Szükségünk van a korábbi összes adatra is ezért azokat be kell olvasni. A kiíró függvényt módosítani kell ennek megfelelően. Illetve érdemes, ide betenni az utolsó linkek frissítését is, aminek szintén írtunk már egy függvényt (writeLastArticlesLink).

function writeArticles(articles) {
	if (articles.length > 0) {
		let oldArticles = [];
		if (fs.existsSync(newLinksFileName)) {
			oldArticles = JSON.parse(fs.readFileSync(newLinksFileName, "utf8"));
		}
		const allArticles = [...articles, ...oldArticles];
		const json = JSON.stringify(allArticles);
		fs.writeFileSync(newLinksFileName, json, "utf8");
		writeLastArticlesLink(allArticles);
		console.info(
			`${articles.length}db bejegyzés kiírása megtörtént az ${newLinksFileName} állományban.`
		);
	} else {
		console.info("Nem került be új bejegyzés a legutóbbi futtatás óta.");
	}
}

Nézzük mi is történik. Először is, megnézzük, hogy hány új cikket olvastunk be. Abban az esetben, ha egyet sem, akkor erről küldünk visszajelzést.

Azonban, ha vannak új cikkek, akkor beolvassuk a régi értékeket (ha létezik ilyen állomány) és vissza alakítjuk egy tömbbé. Majd az új tömböt és a régit összeolvasszuk és együtt írjuk ki.

Majd megívjuk az új függvényt, ami kiírja az utolsó értékeket a megadott állományba. Ezzel kész is vagyunk.

Összegzés

Pár apró módosítással egyszerűen csökkentettük a futás időt és a felesleges lekérések számát. Ehhez nem volt másra szükség csak a folyamat átgondolására. Azon túl, hogy nagyon sok a mindennapok során elvégzett munkát lehet automatizálni, nagyon fontos arra is oda figyelni, hogy azt is optimálisan tegyük. Természetesen még ezen is lehet további finomhangolás elvégezni, ami kevesebb memória felhasználást és gyorsabb futást igényel, de ez még nem az a szintű példa, ahol erre ki kellene térni.

Viszont az elmúlt három részben remélem sikerült úgy összefoglalnom ezt a folyamatot, hogy bármilyen hasonló szintű feladatra elvégezhető legyen és bízom benne, hogy ezzel tudtam segíteni bárkinek a mindennapok kényelmesebbé tételében, illetve a munkája megkönnyítésében.

Végeredmény

const fs = require("fs");
const https = require("https");
const { JSDOM } = require("jsdom");

const url = require("url");

const pageArticleSize = 50;
const atricleURL = `https://akjournals.com/search?access=all&pageSize=${pageArticleSize}&q1=agricultural&sort=datedescending`;

const articles = [];
let articleCount = 50;

let lastLinks = [];
let isLast = false;
const lastLinksFileName = "lastLinks.txt";
const newLinksFileName = "articles.json";

if (fs.existsSync(lastLinksFileName)) {
	lastLinks = fs.readFileSync(lastLinksFileName, "utf8").split("\n");
}

function writeLastArticlesLink(articles, numberOfLinks = 10) {
	const lastAtricle = articles.slice(0, numberOfLinks);
	fs.writeFileSync(
		lastLinksFileName,
		lastAtricle.map((article) => url.parse(article.link).pathname).join("\n"),
		"utf8"
	);
}

async function getArticles(articleLink) {
	let page = 1;
	while (articleCount === pageArticleSize && isLast === false) {
		console.info(`\n${page}. oldal letöltése folyamatban`);
		await new Promise((resolve, reject) => {
			https.get(
				https
					.get(`${articleLink}&page=${page}`, (res) => {
						let data = "";
						res.on("data", (chunk) => {
							data += chunk;
						});
						res.on("end", () => {
							console.info(
								`Adatok sikeresen letöltve a(z) ${page}. oldalról: ${articleLink}&page=${page}`
							);
							page++;
							dataClear(data);
							resolve(true);
						});
					})
					.on("error", (err) => {
						console.error("Hiba a letöltés során!");
						console.error(err.message);
						resolve(false);
					})
			);
		});
		isLast = true;
	}
	writeArticles(articles);
}

function dataClear(data) {
	const dom = new JSDOM(data);
	const articleElements = dom.window.document.querySelectorAll(
		".type-search-result .title a"
	);
	articleElements.forEach((elem) => {
		const titleValue = elem.innerHTML;
		const linkValue = elem.getAttribute("href");

		//Remove junk query string paramters from the validataion
		const pathName = url.parse(linkValue).pathname;
		if (!isLast && !lastLinks.includes(pathName)) {
			articles.push({ title: titleValue, link: linkValue });
		} else {
			isLast = true;
			return;
		}
	});
	console.info("Adatok feldolgozva\n");
	articleCount = articleElements.length;
}

function writeArticles(articles) {
	if (articles.length > 0) {
		let oldArticles = [];
		if (fs.existsSync(newLinksFileName)) {
			oldArticles = JSON.parse(fs.readFileSync(newLinksFileName, "utf8"));
		}
		const allArticles = [...articles, ...oldArticles];
		const json = JSON.stringify(allArticles);
		fs.writeFileSync(newLinksFileName, json, "utf8");
		writeLastArticlesLink(allArticles);
		console.info(
			`${articles.length}db bejegyzés kiírása megtörtént az ${newLinksFileName} állományban.`
		);
	} else {
		console.info("Nem került be új bejegyzés a legutóbbi futattás óta.");
	}
}

console.info(`Adatok letöltése folyamatban...`);
getArticles(atricleURL);