Pobieranie strony offline w PHP

W tym artykule zbudujemy prostego pająka, który będzie chodził po wybranej stronie i pobierał jej zawartość na dysk do odczytu offline. Skorzystamy przy tym z wiedzy zawartej w artykule o web scrappingu.

Plan działania

Zanim zaczniemy, ustalmy szczegółowo co chcemy zrobić:

  1. Pobrać zawartość wskazanej strony początkowej. Adres tej strony będzie również wzorcowym adresem, którym będziemy się sugerować, aby nie wyjść poza domenę.
  2. Sprawdzić linki występujące na stronie i dodać je do listy (o ile wcześniej nie wystąpiły) i o ile nie wskazują na zewnątrz domeny
  3. Pobrać kolejne strony z uwzględnianiem listy.

W ten sposób powinniśmy pobrać całą zawartość strony dostępną z poziomu użytkownika.

Pobieranie zawartości strony

Aby pobrać stronę skorzystamy z algorytmu napisanego wcześniej do wspomnianego artykułu. Zmodyfikujemy jedynie element związany z kompresją – dodamy sprawdzanie czy strona jest skompresowana.

function getWebsite ($url) {

  $opts = [
    'http'=> [
      'method'=>"GET",
      'header'=>"Accept-Encoding: gzip, deflate, br\r\n" .
                "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\n" .
                "Referer: https://eskim.pl\r\n" .
                "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.50\r\n"
      ]
  ];

  $context = stream_context_create ($opts);
  $compressed = file_get_contents ($url, false, $context);
  $webpage = gzdecode ($compressed);
  if ($webpage === false) return $compressed;

  return $webpage;
}

Funkcja gzdecode zwróci false, jeżeli dekompresja się nie uda. Wtedy zwracamy po prostu stronę.

Sprawdzanie domeny

Pierwszy pobierany adres będzie wspomnianym adresem wzorcowym. Napiszmy algorytm, który to zweryfikuje:

function isInternalURL ($url, $current_url) {

  if ( stripos ($current_url, $url) === 0 && stripos ($current_url, $url.'.') === false ) {
    return true;
  }
  return false;
}

Powyższa funkcja sprawdza czy przekazany w pierwszym parametrze link znajduje się na początku drugiego ciągu. Dodatkowo sprawdzamy czy nie kończy się kropką na wypadek takiej konstrukcji:

eskim.pl.example.com

Parsowanie strony

Pobieramy stronę i parsujemy zgodnie z powyższym artykułem o web scrappingu. Tym razem jednak interesują nas linki, czyli tagi a i atrybut href.

function getLinks ($page) {

  $links = [];

  $website = new DOMDocument();
  $website->loadHTML ($page); // zamień stronę na obiekt DOM

  $as = $website->getElementsByTagName('a'); // pobierz wszystkie tagi a

  foreach ($as as $a) {

	  if ($a->hasAttribute('href')) {
      $links[] = trim ($a->getAttribute('href') );
	  }
  }

  return array_unique ($links);
}

Następnie wchodzimy w pierwszą stronę z linka i robimy dokładnie to samo – pobieramy i zapisujemy linki (dbamy dodatkowo, aby się nie powtarzały)

Przekierowania

Czasami pod linkiem znajduje się przekierowanie na inną stronę. file_get_contents automatycznie obsługuje przekierowania. W naszym konkretnym przypadku nie do końca jest to wskazane (przekierowanie może być na zewnętrzną stronę). Aby wyłączyć tą funkcjonalność musimy zmodyfikować ustawienia pobierania. Dodajmy do http, follow_location i ustawmy je na false

'http' => [
  'follow_location' => false
]

Możemy również pobierać tylko strony lokalne używając przekierowań. Kod zapożyczony ze stack overflow:

function get_url_contents_and_final_url(&$url)
{
    do
    {
        $context = stream_context_create(
            array(
                "http" => array(
                    "follow_location" => false,
                ),
            )
        );

        $result = file_get_contents($url, false, $context);

        $pattern = "/^Location:\s*(.*)$/i";
        $location_headers = preg_grep($pattern, $http_response_header);

        if (!empty($location_headers) &&
            preg_match($pattern, array_values($location_headers)[0], $matches))
        {
            $url = $matches[1];
            $repeat = true;
        }
        else
        {
            $repeat = false;
        }
    }
    while ($repeat);

    return $result;
}

Powyższy kod sprawdza czy wystąpiło przekierowanie i jeżeli tak, pobiera stronę z przekierowania.. aż do skutku. Można tutaj dodać ograniczenie (np. maksymalnie 10 stron) oraz weryfikować przed pobraniem czy strona znajduje się w tej samej domenie.

Kod na GitHub – very-simple-spider udostępniony na licencji GPL 3.

Podsumowanie

  1. Pobieranie zawartości zostało opisane w artykule o web scrappingu.
  2. Sprawdzamy czy przeszukiwany link znajduje się w domenie
  3. Wyciągamy atrybuty href z tagów a za pomocą DOMDocument.
  4. Wyłączamy obsługę przekierować podczas pobierania danych przy użyciu file_get_contents, ustawiając opcję follow_location na false.