Wprowadzenie do skrobania stron internetowych za pomocą Cheerio

Photo of author

By maciekx

Pobieranie danych ze stron internetowych, znane jako web scraping, to technika umożliwiająca ekstrakcję informacji z konkretnych witryn. Strony internetowe wykorzystują język HTML do strukturyzowania swojej zawartości. Gdy kod HTML jest logiczny i semantyczny, staje się on łatwym celem do odnalezienia pożądanych danych.

Zazwyczaj narzędzia do web scrapingu używane są do zbierania, monitorowania danych oraz śledzenia ich zmian w czasie.

Koncepcje jQuery przydatne przed użyciem Cheerio

jQuery to popularna biblioteka JavaScript, która upraszcza manipulację Document Object Model (DOM), obsługę zdarzeń, tworzenie animacji i wiele więcej. Cheerio to biblioteka do web scrapingu, która inspiruje się jQuery. Oferuje zbliżoną składnię i interfejs API, co ułatwia analizę dokumentów HTML i XML.

Zanim zaczniesz używać Cheerio, warto poznać sposoby, w jakie jQuery wybiera elementy HTML. Na szczęście jQuery wspiera większość selektorów CSS3, co upraszcza proces odnajdywania elementów w DOM. Przykładowo:

 $("#container");

Powyższy fragment kodu za pomocą jQuery wybiera elementy z identyfikatorem „kontener”. Analogiczny kod w czystym JavaScript byłby znacznie bardziej skomplikowany:

 document.querySelectorAll("#container");

Porównując te dwa przykłady, widać, że kod jQuery jest bardziej czytelny. To właśnie jest zaletą tej biblioteki.

jQuery oferuje także szereg przydatnych metod, takich jak `text()`, `html()`, umożliwiających manipulację elementami HTML. Do nawigacji po DOM służą m.in. metody `parent()`, `siblings()`, `prev()` i `next()`.

Metoda `each()` jQuery jest szczególnie przydatna w wielu projektach wykorzystujących Cheerio. Pozwala na iterację po obiektach i tablicach. Jej składnia wygląda następująco:

 $(<element>).each(<array or object>, callback)

W powyższym przykładzie funkcja `callback` jest wywoływana dla każdej iteracji tablicy lub obiektu.

Ładowanie HTML przy użyciu Cheerio

Aby rozpocząć analizę danych HTML lub XML za pomocą Cheerio, można użyć metody `cheerio.load()`. Spójrzmy na przykład:

 const $ = cheerio.load('<html><body><h1>Witaj świecie!</h1></body></html>');
console.log($('h1').text())

Powyższy kod wykorzystuje metodę jQuery `text()` do pobrania tekstu z elementu `h1`. Pełna składnia metody `load()` wygląda tak:

 load(content, options, mode)

Parametr `content` odnosi się do danych HTML lub XML przekazywanych do metody `load()`. Parametr `options` to opcjonalny obiekt, który może zmieniać zachowanie metody. Domyślnie metoda `load()` tworzy elementy `html`, `head` i `body`, jeśli ich brakuje. Aby wyłączyć to zachowanie, ustaw tryb na `false`.

Pobieranie wiadomości z Hacker News przy użyciu Cheerio

Kod użyty w tym projekcie jest dostępny w repozytorium GitHub i jest udostępniony na licencji MIT.

Połączmy teraz nabytą wiedzę i stwórzmy prosty web scraper. Hacker News to popularna strona internetowa dla przedsiębiorców i innowatorów. Jest to także idealna strona do ćwiczenia umiejętności web scrapingu, ponieważ ładuje się szybko, ma prosty interfejs i nie wyświetla reklam.

Upewnij się, że masz zainstalowane Node.js i menedżer pakietów npm. Utwórz pusty folder, a następnie plik `package.json` i wklej do niego następujący kod JSON:

 {
  "name": "web-scraper",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "nodemon index.js"
  },
  "author": "",
  "license": "MIT",
  "dependencies": {
    "cheerio": "^1.0.0-rc.12",
    "express": "^4.18.2"
  },
  "devDependencies": {
    "nodemon": "^3.0.1"
  }
}

Po zapisaniu pliku, otwórz terminal i wpisz:

 npm i

Spowoduje to instalację niezbędnych zależności potrzebnych do zbudowania naszego scrapera. Pakiet Cheerio jest potrzebny do analizy HTML, ExpressJS do stworzenia serwera, oraz Nodemon (jako zależność deweloperska), która będzie automatycznie restartować serwer po zmianach w kodzie.

Konfiguracja i tworzenie wymaganych funkcji

Utwórz plik `index.js` i zadeklaruj w nim zmienną `PORT`. Ustaw wartość `PORT` na 5500 (lub inny wybrany numer), a następnie zaimportuj pakiety Cheerio i Express.

 const PORT = 5500;
const cheerio = require("cheerio");
const express = require("express");
const app = express();

Następnie zdefiniuj trzy zmienne: `url`, `html` i `readyPage`. Ustaw `url` na adres strony Hacker News.

 const url="https://news.ycombinator.com";
let html;
let finishedPage;

Teraz zdefiniuj funkcję `getHeader()`, która będzie zwracać kod HTML, który przeglądarka ma wyświetlić.

 function getHeader(){
    return `
        <div style="display:flex; flex-direction:column; align-items:center;">
        <h1 style="text-transform:capitalize">Scraper News</h1>
        <div style="display:flex; gap:10px; align-items:center;">
        <a href="https://www.makeuseof.com/" id="news" onClick='showLoading()'>Home</a>
        <a href="https://wilku.top/best" id="best" onClick='showLoading()'>Best</a>
        <a href="https://wilku.top/newest" id="newest" onClick='showLoading()'>Newest</a>
        <a href="https://wilku.top/ask" id="ask" onClick='showLoading()'>Ask</a>
        <a href="https://wilku.top/jobs" id="jobs" onClick='showLoading()'>Jobs</a>
        </div>
        <p class="loading" style="display:none;">Loading...</p>
        </div>
`}

Stwórz kolejną funkcję `getScript()`, która będzie zwracała kod JavaScript potrzebny do uruchomienia w przeglądarce. Funkcja ta przyjmuje argument określający typ strony.

 function getScript(type){
    return `
    <script>
    document.title = "${type.substring(1)}"

    window.addEventListener("DOMContentLoaded", (e) => {
      let navLinks = [...document.querySelectorAll("a")];
      let current = document.querySelector("#${type.substring(1)}");
      document.body.style = "margin:0 auto; max-width:600px;";
      navLinks.forEach(x => x.style = "color:black; text-decoration:none;");
      current.style.textDecoration = "underline";
      current.style.color = "black";
      current.style.padding = "3px";
      current.style.pointerEvents = "none";
    })

    function showLoading(e){
      document.querySelector(".loading").style.display = "block";
      document.querySelector(".loading").style.textAlign = "center";
    }
    </script>`
}

Na koniec, utwórz asynchroniczną funkcję `fetchAndRenderPage()`. Jej zadaniem będzie pobranie danych ze strony Hacker News, przetworzenie ich za pomocą Cheerio i wysłanie sformatowanego kodu HTML do klienta.

 async function fetchAndRenderPage(type, res) {
    const response = await fetch(`${url}${type}`)
    html = await response.text();
}

Hacker News oferuje różne sekcje postów. „News” to strona główna, posty z pytaniami od użytkowników są oznaczone jako „ask”, najpopularniejsze posty to „best”, najnowsze posty to „newest”, a oferty pracy to „jobs”.

Funkcja `fetchAndRenderPage()` pobiera listę postów z Hacker News na podstawie argumentu określającego typ. Jeśli pobieranie się powiedzie, zmienna `html` zostaje przypisana do tekstu odpowiedzi.

Następnie dodaj następujące linie do funkcji:

 res.set('Content-Type', 'text/html');
res.write(getHeader());

const $ = cheerio.load(html);
const articles = [];
let i = 1;

W powyższym kodzie metoda `set()` ustawia nagłówek HTTP, a metoda `write()` wysyła fragment treści. Funkcja `load()` przyjmuje `html` jako argument.

Kolejne linie służą do wybrania odpowiednich elementów potomnych ze wszystkich elementów z klasą „titleline”.

 $('.titleline').children('a').each(function(){
    let title = $(this).text();
    articles.push(`<h4>${i}. ${title}</h4>`);
    i++;
})

W tym kodzie każda iteracja pobiera tekst z docelowego elementu HTML i zapisuje go w zmiennej `title`.

Następnie dodaj odpowiedź z funkcji `getScript()` do tablicy `articles`. Stwórz zmienną `readyPage`, która będzie przechowywać kompletny kod HTML gotowy do wysłania do przeglądarki. Na końcu użyj metody `write()` do wysłania fragmentu `finishPage`, i zakończ proces odpowiedzi metodą `end()`.

 articles.push(getScript(type))
finishedPage = articles.reduce((c, n) => c + n);
res.write(finishedPage);
res.end();

Definiowanie tras dla żądań GET

Bezpośrednio pod funkcją `fetchAndRenderPage()` użyj metody `express get()` do zdefiniowania odpowiednich tras dla różnych typów postów. Następnie użyj metody `listen` aby nasłuchiwać połączeń z określonym portem w sieci lokalnej.

 app.get("https://www.makeuseof.com/", (req, res) => {
    fetchAndRenderPage('/news', res);
})

app.get("https://wilku.top/best", (req, res) => {
    fetchAndRenderPage("https://wilku.top/best", res);
})

app.get("https://wilku.top/newest", (req, res) => {
    fetchAndRenderPage("https://wilku.top/newest", res);
})

app.get("https://wilku.top/ask", (req, res) => {
    fetchAndRenderPage("https://wilku.top/ask", res);
})

app.get("https://wilku.top/jobs", (req, res) => {
    fetchAndRenderPage("https://wilku.top/jobs", res);
})

app.listen(PORT)

W powyższym kodzie każda metoda `get` ma funkcję wywołania zwrotnego, która wywołuje funkcję `fetchAndRenderPage`, przekazując odpowiedni typ i obiekt `res`.

Po otwarciu terminala i wpisaniu `npm run start`, serwer powinien się uruchomić. Możesz odwiedzić adres `localhost:5500` w przeglądarce, aby zobaczyć wyniki.

Gratulacje! Udało Ci się pobrać tytuły z Hacker News bez korzystania z zewnętrznego API.

Dalsze możliwości web scrapingu

Na podstawie danych pobranych z Hacker News można tworzyć różnego rodzaju wizualizacje, takie jak wykresy, diagramy, czy chmury słów, aby w przystępny sposób przedstawić wnioski i trendy.

Możesz także skrobać profile użytkowników, aby analizować ich reputację na platformie, na podstawie takich czynników jak liczba otrzymanych pozytywnych głosów, komentarzy i innych.


newsblog.pl