
Adatok weboldalról - 1. rész
Adat kinyerés Weboldal Node.js
2023. március 23.
Ebben a posztban egy megadott weboldalon található adatok kinyerésének módját mutatom be. Mivel minden weboldal más felépítésű ezért mindenhol egy kicsit más a megoldás, de a folyamat ugyanaz. Most ennek a folyamatnak a menetét fogom ismertetni, hogy később ez alapján szinte bármilyen oldalról lehessen adatot gyűjteni. Amennyiben még ez újdonság, akkor ajánlom figyelmedbe egy korábbi bejegyzésem, ami a Weboldal felépítésének vizsgálatával foglalkozik.
Ami itt nagyon fontos, az a jogi oldal. Csak olyan oldalról gyűjtsünk adatot, ami a felhasználói feltételekben erre engedélyt ad illetve, ha az adott oldaltól van külön engedélyünk. A legtöbb oldal nem járul hozzá, hogy az ott szereplő adatokat máshol megjelenítsük vagy adatbázisba tegyük be. Ezen túl pedig arra is oda kell figyelni, hogy az olyan nyilvános adatoknál, amik gyűjthetőek a felhasználás során a forrás megjelölés nagyon fontos.
A példánk
Mondjuk, hogy egy nagy agrár érdekeltséggel rendelkező cégnél dolgozunk, ahol nagyon fontos minden újítás azonnali bevezetése. Így havonta többször is több adatbázist vagy forrást kell ellenőrizni, hogy került e fel új tartalom. A példánkban az Akadémiai Kiadó Folyóiratcsomag adatbázisában fogjuk keresni az olyan publikációkat, amik agrár témával vannak kapcsolatban. Az adatbázis és a kereső az alábbi oldalon érhető el: https://akjournals.com/
Az oldal jobb felső sarkában található a Search
menüpont, amire kattintás után elkezdhetjük beírni a keresendő kifejezést. Jelen esetben én az agricultural
szóra fogok keresni, hogy az agrár találatokat listázza nekem.
A keresés eredménye
Láthatjuk, hogy az eredmények mellet még van további szűrési lehetőségünk. Ezentúl ami fontos, hogy jelen esetben a rendezés (sort by) az a releváns tartalmak szerint legyen, ami a jelen esetben nem jó. A lenyíló menüből válasszuk ki, hogy a legújabb-tól a legrégebbi-ig terjedjen ( Date - Recent to old ). Amit nem mindenki vesz észre pedig nagyon fontos, hogy kaptunk egy egyedi linket, amit a címsorból kimásolva minden betöltés után ugyanezt a találati listát kapjuk, természetesen kiegészítve a legfrissebb tartalommal. Így mindig ugyanez a szűrés és annak a sorbarendezése, tehát minden amit beállítottunk vissza fog töltődni. Ez azért is rendkívül fontos számunkra, mert ezt a linket fogjuk használni és mindig ezt az oldalt fogjuk betölteni a programunkban.
Nálam ez a link a következő: https://akjournals.com/search?access=all&pageSize=10&q1=agricultural&sort=datedescending
Megjegyzés a linkhez: A linket egy picit tanulmányozva meg is találjuk az általunk beállított dolgokat. A q1 paraméter amire rá keresztünk, jelen esetben az agricultural és után van a sort, hogy a dátum szerint legyen csökkenő. Hasonló módon fog bekerülni minden, amire szűrünk.
Visszatérve a feladatra az első és legegyszerűbb megoldás, ha azt mondjuk, hogy csak az érdekel van e újabb találat a legutóbbi megtekintés óta. Ebben az esetben nem tároljuk le az összes találatot csak a legutolsót, amihez viszonyítani fogunk. Nézzük ezt hogy is kell.
Megfelelő elem kiválasztása
Nézzük meg hogyan is épül fel az oldal és hogyan tudjuk kinyerni a legelső címet, vagyis az utolsó publikáció címét. Nyissuk meg a fejlesztői eszközöket a böngészőben. Amennyiben ez ismeretlen lenne erről korábban készült már egy részletes bejegyzés Weboldal felépítésének vizsgálata címmel, ahol az is bemutatásra került, hogy milyen módon lehet például a címeket kinyerni.
Tehát ha egy kiadvány címet megvizsgálunk a kijelölő eszközzel akkor az alábbit kell látnunk.
A forrás vizsgálata a böngésző eszközeivel
Itt látjuk, hogy ez egy hivatkozás (a tag
) ami rendelkezik egy c-Button--link
osztállyal, bár ez még sokat nem árul el, mert elég általános megnevezésnek tűnik. Azt is látjuk, hogy ennek van egy közvetlen h1
-es szülőeleme aminek az osztálya már beszédesebb, mert a title
is benne van. Innen már feltételezhetjük, hogy minden cím rendelkezik ezzel, így tudunk rá hivatkozni. Viszont érdemes tovább szűrni, hogy biztos csak a megfelelő elem legyen meg. Tovább nézve látjuk, hogy sajnos sokat kellene menni fel a hierarchiában ahhoz, hogy találjunk egy biztos pontot. Amennyiben kitartóan nézegetjük akkor meg is találjuk ahogy az osztály type-search-result
, ami elég erős megnevezésnek tűnik.
Így amit jelen esetben tudunk használni az a következő: .type-search-result .title a
Akkor teszteljük is le. Most elég lesz számunkra a sima querySelector, mert csak egyetlen elemet szeretnénk az elsőt.
document.querySelector(".type-search-result .title a");
Lefuttatva látjuk is, hogy ténylegesen az első címet találja meg.
Megjegyzés: Abban az esetben, ha tényleg biztosra szeretnénk menni, akkor láthatjuk, hogy 10 találat van az oldalon. Így tudjuk, hogy mennyi találatnak kell lenni a mi feltételünkre is, abban az esetben, ha a fenti hivatkozást az összes elemre futtatjuk le. Majd az eredményének ki kérjük a találati darabszámát és ezt a querySelectorAll
-al tudjuk és annak a darabszámát pedig a length
változóval kapjuk meg:
document.querySelectorAll(".type-search-result .title a").length;
Visszatérve az eredeti feladatra. Kérjük ki ennek az a
linknek a hivatkozás paraméterét. Azért a hivatkozást, mert ez biztosan egyedi, nincsenek benne speciális karakterek és jól lehet vele dolgozni. Ehhez a getAttribute()
függvényt fogjuk használni. Ez a függvény egy paramétert vár, hogy az adott elem melyik értékével térjen vissza. Jelen esetben a href
paramétert szeretnénk, mert a hivatkozásnál az tartalmazza a tényleges linket.
document.querySelector(".type-search-result .title a").getAttribute("href");
Ezt lefuttatva meg is kapjuk a linket, amivel tudunk dolgozni. Most hogy megvan az első lépés és tudjuk melyik adatra van szükség, most következik az automatizálás.
Oldal (be/le)töltése és feldolgozása
Első dolgunk a korábban megadott url-t programról betölteni és megnézni, hogy a tartalma tényleg egyezik-e az általunk megnyitott oldal forrásával. Ugyanis azt fontos megemlíteni, hogy a manapság a frontend keretrendszerek terjedésével, a forrás már nem minden esetben tartalmazza a tényleges tartalmat. Erről lesz egy külön cikk is.
A jobb átláthatóság és a könnyebb módosíthatóság érdekében változóban tároljuk az url paramétert, így bárhol és bármikor hivatkozhatunk rá. Illetve változás esetén csak egy helyen kell átírni.
const url = "https://akjournals.com/search?access=all&pageSize=10&q1=agricultural&sort=datedescending";
A tartalom lekéréséhez a https
nevű csomagot fogom használni:
const https = require("https");
Ennek van egy get
függvénye, ami egy url paramétert vár és visszatér egy válasz üzenettel.
https.get(url, (res) => {
let data = "";
res.on("data", (chunk) => {
data += chunk;
});
res.on("end", () => {
console.info(`Adatok sikeresen letöltve az alábbi oldalról: ${url}`);
dataClear(data);
});
})
.on("error", (err) => {
console.error("Hiba a letöltés során!");
console.log(err.message);
process.exit(0);
});
Tehát nézzük mit is látunk. A get
függvény lekéri egy oldalt és visszatér a válasszal (response
), amit res
néven tovább is adunk egy függvénynek. Ez a response lehet több csomagra is szétvégva (chunk
) ezért meg kell várni az összes beérkezését és összefűzni. Ezt látjuk on('data')
-n belül. Amikor megérkezett az utolsó csomag is és kész az összefűzése, akkor meghívódik a res('end')
, ahol már rendelkezésre áll az összes tartalom, a data
változóan. Itt meghívok egy külső függvényt, amivel majd kinyerem a számomra szükséges adatokat.
Azonban nézzük még meg a függvény alját, az on('error')
részt. Értelemszerűen ez akkor hívódik meg ha bármi hiba történt a betöltés során. Nem csinál semmi mást csak a konzolra ki írja a hiba szövegét.
Mielőtt tovább megyünk ebből csináljunk egy függvényt, amit a későbbiek során bármikor meghívhatunk, tetszőleges url paraméterrel.
function getArticles(pageUrl) {
https.get(pageUrl, (res) => {
let data = "";
res.on("data", (chunk) => {
data += chunk;
});
res.on("end", () => {
console.info(`Adatok sikeresen letöltve az alábbi oldalról: ${pageUrl}`);
dataParser(data);
});
})
.on("error", (err) => {
console.error("Hiba a letöltés során!");
console.log(err.message);
process.exit(0);
});
}
Majd hívjuk is meg, hogy legyen adatok. Azonban ez most nem fog lefutni, mert az oldal letöltése után meghív egy dataParser
függvényt. Ezt előbb létre kell hozni. Ezen belül fogjuk feldolgozni a kapott értéket.
function dataParser(data) {
}
getArticles(url);
Adat feldolgozás
Akkor nézzük az adatfeldolgozást, amit már a korábban deklarált dataParser
függvényben fogunk végezni. Először is írassuk ki a tartalmat, amit kapunk. Láthatjuk, hogy pontosan ugyanaz a tartalma, mint az oldalon lévő forrásnak. Így tudjuk, hogy benne van minden olyan érték amire szükségünk van. A következő, amit észre kell venni, hogy szöveges állományként van meg. Így viszont nem tudjuk a már korábban összegyűjtött kis scripteket használni az adat kinyerésre.
Szerencsére erre nagyon egyszerű megoldás van, ugyanis nincs más teendőnk, mint a szöveget html kóddá alakítani. Pontosan erre a feladatra van egy kész csomag, amit csak használnunk kell, ez pedig nem más, mint a html-dom-parser
Telepítést követően csak be kell húzni a csomagot a programunk legelején.
const { JSDOM } = require("jsdom");
Majd nincs más teendő, mint a szöveges állományunkat átadjuk neki, hogy legyen egy DOM objektumok, amiben már tudunk keresni.
const dom = new JSDOM(data);
Ezzel készen állunk, hogy ki keressünk, az első cikket és megnézzük, hogy tényleg az e a tartalma, amit az oldalon is látunk.
const fistArticle = dom.window.document.querySelector(
".type-search-result .title a"
);
console.log(fistArticle.textContent);
Amennyiben lefuttatjuk a programot, ami nálam index.js
néven van. Láthatjuk, hogy az első cikke hivatkozásának szövegét ki is írja.
$ node index.js
Ezzel az oldal be is van töltve és készen áll a feldolgozása. Nyerjük ki az adatokat és helyezzük egy JSON
állományba.
Nincs más dolgunk, mint ahogy fentebb tettünk és megkeresni a címeket majd végig menni rajtuk. Először is hozzunk létre egy változót a függvény elött, amiben tároljuk a kinyert szükséges értékeket.
const articles = [];
Majd akkor a megfelelő hivatkozások menjünk végig és nyerjük ki a tartalmát és tároljuk el.
document.querySelectorAll(".type-search-result .title a").forEach((elem) => {
const titleValue = elem.innerHTML;
const linkValue = elem.getAttribute("href");
articles.push({ title: titleValue, link: linkValue });
});
Akkor nézzük meg, hogy is néz ki a teljes feldolgozó függvényünk.
function dataParser(data) {
const dom = new JSDOM(data);
dom.window.document.querySelectorAll(".type-search-result .title a").forEach((elem) => {
const titleValue = elem.innerHTML;
const linkValue = elem.getAttribute("href");
articles.push({ title: titleValue, link: linkValue });
});
console.info("Adatok feldolgozva");
writeArticles(articles);
}
Ha ezzel megvagyunk a konzolban könnyen tudjuk ellenőrizni az eredményt.
console.log(articles);
Az utolsó lépés, hogy le mentjük az állományt JSON
formátumban. Ehhez sima írás műveletet használunk és ezen túl a JSON.stringify()
függvényt, ami a tömbből JSON
-t készít. Majd ezt csak kiírjuk. Ezt is egy függvénybe tesszük bele, amit a feldolgozás végén meghívunk.
function writeArticles(articles) {
const json = JSON.stringify(articles);
fs.writeFileSync('articles.json', json, 'utf8');
console.info(`A bejegyzések kiírása megtörtént a articles.json állományban.`);
}
Ezzel meg is lennénk a fő feladattal. A programot futtatva mindig rendelkezésre áll számunkra az legutóbbi 10 bejegyzés címe és így mindig a legfrissebb lesz. Nem kell az oldalon újra beállítani a dolgokat és nem is kell minden alkalommal kézzel betölteni azt.
A későbbiek folyamán lesz leírás, ennek a folyamatnak a rendszeres automatikus futtatására és így a változás figyelésre is.
A végeredmény
const fs = require("fs");
const https = require("https");
const { JSDOM } = require("jsdom");
const url = "https://akjournals.com/search?access=all&pageSize=10&q1=agricultural&sort=datedescending";
const articles = [];
function getArticles(url) {
https.get(url, (res) => {
let data = "";
res.on("data", (chunk) => {
data += chunk;
});
res.on("end", () => {
console.info(`Adatok sikeresen letöltve az alábbi oldalról: ${url}`);
dataParser(data);
});
})
.on("error", (err) => {
console.error("Hiba a letöltés során!");
console.log(err.message);
process.exit(0);
});
}
function dataParser(data) {
const dom = new JSDOM(data);
dom.window.document.querySelectorAll(".type-search-result .title a").forEach((elem) => {
const titleValue = elem.innerHTML;
const linkValue = elem.getAttribute("href");
articles.push({ title: titleValue, link: linkValue });
});
console.info("Adatok feldolgozva");
writeArticles(articles);
}
function writeArticles(articles) {
const json = JSON.stringify(articles);
fs.writeFileSync('articles.json', json, 'utf8');
console.info(`A bejegyzések kiírása megtörtént a articles.json állományban.`);
}
console.info(`Adatok letöltése folyamatban...`)
getArticles(url);