
Adatok weboldalról - 2. rész lapozás
Adat kinyerés Weboldal Node.js
2023. március 28.
Az előző részben egy weboldalról nyertünk ki adatokat. Ez a Adatok weboldalról - 1. rész címen található meg a korábbi bejegyzések között. Itt azonban csak az első találati oldalon szereplő cikkek címét gyűjtöttük ki, abban az esetben, ha szükségünk van a többi oldal tartalmára is, akkor meg kell oldani, hogy az összes oldalon végig menjünk.
Fontos, hogy ennek a lényege inkább az egyszeri adat összegyűjtés és nem pedig a rendszeres összes oldal lekérése. Illetve ez egyfajta előkészület is, amit követi majd a változás figyelés, ahol már csak az ujjak elemek hozzáadása a lényeg.
Előkészületek
Első lépésként szintén az oldalt kell nézünk, ahonnan az adatok szerezzük, hogy különböző beállításokkal miként változik az url-je.
Az alap url a következő volt: https://akjournals.com/search?access=all&pageSize=10&q1=agricultural&sort=datedescending
Ezen már látszik, is hogy egy oldalon 10 elem jelenik meg. Ezt a pageSize
paraméter állítja be. Nézzük is meg ha ezt váltjuk (feltéve, ha van az oldalon erre lehetőség), hogy akkor az hogyan változik. A maximális érték, amit betudunk állítani a formon az 50. Ha kézzel nagyobbra írjuk, akkor is 50-re fog visszaállni. Így ezt érdemes hozzá használnunk. Ebben az esetben a link a következő változat lesz:
https://akjournals.com/search?access=all&pageSize=50&q1=agricultural&sort=datedescending
Ezt követően, amit meg kell nézni, hogy mi történik, ha léptetünk. Láthatjuk, hogy az url kiegészül egy page
paraméterrel, ami jelzi nekünk, hogy hányadik oldalon tartunk.
https://akjournals.com/search?access=all&page=2&pageSize=50&q1=agricultural&sort=datedescending
Ezzel megvan és megtaláltuk, hogy milyen paramétert kell állítani a lapozáshoz.
Viszont nagyon fontos, hogy mielőtt erre megírjuk a lekérést, gondolnunk kell arra a arra is, hogy egy a lapozás nem végtelen. Le kell kezelni azt az opciót, hogy mi történik, ha az utolsó oldalra érünk. Ha ezt nem tesszük meg, akkor a scriptünk végtelen ciklusba lép és sose hagyja abba a lekérdezést, amíg le nem állítjuk. Erre két egyszerű opció van.
Az első eset, hogy megnézzük, mit ad az oldal, ha az utolsó utáni oldalra mennénk. Láthatjuk, hogy üres találti eredményt kapunk, tehát mondhatjuk azt, hogy abban az esetben, ha nincs cikk a keresési eredményben, akkor a végére értünk.
A másik opció lehet, ha keresünk az oldalon olyat részt, ami jelzi, hogy mennyi cikk van és abból a pontosan mennyi cikk van megjelenítve. Jelen esetben van ilyen:
You are looking at 2,901 - 2,888 of 2,888 items for :
Itt meg lehet tenni azt, hogy ezt az értéket nézzük, hogy elértük e az utolsót. Ebben az esetben ki kell minden alkalommal olvasni az értékét és ellenőrizni. Ez a mostani esetben a bonyolultabb megoldás, így mi az első esetet fogjuk alkalmazni, tehát a cikkek számát fogjuk nézni.
A kódolás
Az url sajátos lehetősége, hogy szabadon rendezhető. Ezt a tulajdonságot ki is fogjuk használni. Mégpedig úgy, hogy a page
paramétert a végére fogjuk tenni így egyszerűen a oldalszámot csak hozzá kell adni. Deklaráljuk az alap url-t, amihez csak hozzá szeretnénk adni az oldalszámot.
const atricleURL = 'https://akjournals.com/search?access=all&pageSize=50&q1=agricultural&sort=datedescending';
Ezt követően hozzunk létre egy változót, amiben tároljuk, hogy hány darab cikk volt az utolsó oldalon.
let articleCount = 50;
Írjuk meg a ciklust a függvényünkben, ami addig megy, amíg a cikkek száma pontosan 50. Mert ha 0 akkor túl futottunk az utolsó oldalon, viszont ha kisebb mint 50 akkor azt jelenti, hogy nincs több oldal, mert az utolsó oldalra már nem jutott 50 cikk. Tehát alapesetben a kisebb mint 50 fog érvényesülni, de fel kell készülni arra az esetre is, ha véletlen pont 50 cikk lenne az utolsó oldalon és így túl fogunk futni.
Ebben az esetben a getArticles
függvényt át kell írni egy async
függvényre és a https
lekérést egy Promise
-ba kell tenni. Erre azért van szükség, mert így van lehetőségünk megvárni a lekérés eredményét. Abban az esetben, ha ezt nem tennék meg, fent állna annak a lehetősége, hogy a végeredmények nem a megfelelő sorrendben kerül tárolásra. Az oka, hogy a kérések egymással párhuzamosan mennek, és ha bármelyik később indított kérésünk előbb fejeződik be (ami előfordulhat bármikor), úgy annak az értéke hamarabb kerül az eredmény tömbbe. Ezért megvárjuk minden egyes lekérés eredményét, mielőtt új kérést indítunk.
Megjegyzés: Olyan eset is létezik, hogy ha nem várunk akkor adott esetben pár másodpercen belül elküldhet a gépünk nagyon sok kérést a szerverre. Ilyenkor a szerver ezt érzékeli és egyszerűen letiltja az elérést. Ezért is érdeme ezt az opciót bevezetni, még ha lassabb is lesz egy picit.
async function getArticles(url) {
let page = 1;
while (articleCount === 50) {
console.info(`\n${page}. oldal letöltése folyamatban`);
await new Promise((resolve, reject) => {
https.get(https.get(`${url}&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: ${url}&page=${page}`);
dataClear(data);
resolve(true);
});
})
.on("error", (err) => {
console.error("Hiba a letöltés során!");
console.log(err.message);
resolve(false);
})
);
});
page++;
};
writeArticles(articles);
}
A kódot megvizsgálva láthatjuk, hogy nagyon sok változás nem történt. A függvényünk elé került egy async
szó, ami azért felelős, hogy ez a függvény aszinkron módban fusson, tehát lehetőséget biztosít arra, hogy megvárjuk a benne történő további ilyen típusú függvényeket.
Ezt követően bekerült egy konzolra íratás, hogy lássuk melyik oldalon dolgozik éppen a program.
Majd a következő rész, ami fontos. Az az await
, ami annyit jelent, hogy addig nem halad tovább a következő sorra a program, ami a mögötte szereplő függvény nem tért vissza értékkel. Ami mögé került az egy new Promise
ami egyszerűen egy aszinkron kód futását biztosító függvény és garantálja, hogy lesz kimenete. Ez három módon tud visszatérni. Ezek a módok a Teljesítve, Elutasítva, Folyamatban. A lényeg, hogy az await addig vár amíg ez folyamatban van. Láthatjuk, hogy van két átadott változója isresolve
és a reject
. Ez további két függvény, a resolve
tér vissza a teljesítve állapottal és az átadott értékkel, míg a reject
az elutasítva állapotban tér vissza szintén az általunk átadott értékkel.
Ahogy látjuk a https
kérésen belül, ha end
és sikeres a kérés, akkor resolve(true)
, így jelezzük, hogy teljesítve és vissza tér egy igaz értékkel. Az error
résznél pedig resolve(false)
-al elutasította állapotra állítjuk, ahol most egy hamis értékkel térünk vissza.
Illetve ami még fontos, hogy a writeArticles
függvényt, csak a ciklus után hívjuk, amikor már a változókban az összes kívánt adat. Addig nem írogatjuk az állományt.
Megjegyzés: Az írásnál az is lehetne opció, hogy minden oldal után append
-el csak hozzá fűzzük a legutóbb lekért adatokat.
Ami még változás, a korábbiakhoz képest, hogy a dataClear
függvényben minden alkalommal lekérjük az elemek számát, amire szükségünk van a ciklusban, hiszen ez alapján döntjük el, hogy kell e további oldalt lekérni.
function dataClear(data) {
const dom = new JSDOM(data);
const articleElements = dom.window.document.querySelectorAll(".type-search-result .title
.
.
.
articleCount = articleElements.length;
}
Ennek a futási ideje elég hosszú lesz, hiszen minden oldalt lekér, ami a cikk írásakor pontosan 59 kérést jelent. Itt a lekérés is egész sok időt vehet igénybe, és az adatok feldolgozása attól függő, hogy mennyi adatot szeretnénk kinyerni. A példában a cím kivétele nem nagy munka így az gyorsabban le is fut.
Itt nincs más dolgunk, mint tárolni az adatokat és meg is van a teljes lista a cikkekről.
Megjegyzés: Arra érdemes odafigyelni, hogy ha több oldalt kérünk le, akkor ne futtassuk túl gyakran a scriptet, ugyanis könnyen lehet, hogy ebben az esetben is kitilt az oldal, ahonnan az adatokat kérjük le. Erre lehet megoldás, hogy a két lekérés között szünetet tartunk a programban, hogy ne legyen túl gyakori a kérés, vagy ha nem olyan érzékeny a szerver akkor csak ritkán futtatjuk.
Végeredmény
const fs = require("fs");
const https = require("https");
const { JSDOM } = require("jsdom");
const atricleURL = 'https://akjournals.com/search?access=all&pageSize=50&q1=agricultural&sort=datedescending';
const articles = [];
let articleCount = 50;
async function getArticles(url) {
let page = 1;
while (articleCount === 50) {
console.info(`\n${page}. oldal letöltése folyamatban`);
await new Promise((resolve, reject) => {
https.get(https.get(`${url}&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: ${url}&page=${page}`);
dataClear(data);
resolve(true);
});
})
.on("error", (err) => {
console.error("Hiba a letöltés során!");
console.log(err.message);
resolve(false);
})
);
});
page++;
};
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");
articles.push({ title: titleValue, link: linkValue });
});
console.info("Adatok feldolgozva");
articleCount = articleElements.length;
}
function writeArticles(articles) {
const json = JSON.stringify(articles);
fs.writeFileSync('articles.json', json, 'utf8');
console.log(`A bejegyzések kiírása megtörtént a articles.json állományban.`)
}
console.info(`Adatok letöltése folyamatban...`)
getArticles(atricleURL);