• Strona główna
  • Curriculum Vitae
  • O mnie
  • Przykład: Gramatyka w PHP
  • Przykład: Kompresja CSS
  • Przykład: Kompresja JavaScript
  • Przykład: Skracanie linków
  • Przykład: Wykrywanie serwera HTTP
  • Przykład: Własna bramka SMS
  • Mapa strony
  • Kontakt
Niebieski Pomarańczowy Zielony Różowy Fioletowy

Pobieranie adresów URL z innej strony

Opublikowane 12 września 2010. Autor: Kamil Brenk. Wizyt: 4 427.

Kategorie: PHP
Tematyka: benchmark php, optymalizacja serwisów, PHP, PHP PEAR, praktyczne skrypty, programowane ciekawostki, programowanie strukturalne, wydajność serwisów internetowych, Wyrażenia regularne, XML

wrz 12

Czasem zachodzi konieczność, że potrzebujemy pobrać wszystkie adresy URL z zewnętrznej strony. Piszemy crawler, który przechodzi na wybrany przez nas adres, pobiera wszystkie adresy z elementów A, przechodzi na pobrane adresy i tak dalej, do skutku.

We wpisie tym przyjrzymy się dwóm metodom pobierania odnośników z zewnętrznej strony (czy jakiegokolwiek zbitka kodu HTML). Poznamy sposób wydajny i estetyczny, czyli krótko o DOMDocument vs regex.

Problem pobrania wszystkich adresów z innej strony WWW przewija się bardzo często na różnych forach programistycznych. Najczęściej pytanie dotyczy właściwego ułożenia wyrażenia regularnego, które to niekoniecznie jest proste w tym przypadku.

Na początek należy jednak pobrać kod HTML zewnętrznej strony, do której odnośników chcemy się dostać:

1
2
3
4
5
6
7
8
9
10
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_FAILONERROR, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)');
$output = curl_exec($ch);
curl_close($ch);

Aby kod ten zadziałał niezbędna jest instalacja modułu cURL, która zazwyczaj już jest wykonana. Jeśli natomiast nie dysponujemy tym rozszerzeniem i nie możemy doinstalować (lub po prostu nie chcemy) to wpis Jak pobierać zewnętrzne zasoby? może okazać Ci się przydatny.

Pobieranie odnośników z kodu HTML z wykorzystaniem wyrażeń regularnych

Aby prawidłowo obsłużyć pobieranie wszystkich odnośników z wskazanego kodu HTML musimy się niemało natrudzić. Powstałe w ten sposób wyrażenie musi obsługiwać wiele sytuacji i wyjątków.

Na potrzeby niniejszego wpisu stworzyłem proste wyrażenie, niezbyt optymalne, nie wychwytujące zapewne wszystkich adresów URL, jednak musi wystarczyć.

1
/<a\shref=["\']?([^"]+)["\']?/i

Teraz już tylko wystarczy z użyciem preg_match_all przejść przed kod HTML i zastosować powyższe wyrażenie. Wygląda to następująco:

1
2
3
4
5
$pattern = '/<a\shref=["\']?([^"]+)["\']?/i';
preg_match_all($pattern, $text, $matches);

// nasze url'e
var_dump ($matches[1]);
Pobieranie odnośników z kodu HTML z wykorzystaniem klasy DOMDocument

Drugim, dużo przyjemniejszym i wygodniejszym sposobem jest wykorzystanie wbudowanej w PHP klasy DOMDocument.

Wspomniana klasa wymaga uruchomienie na serwerze rozszerzenia DOM (który jest domyślnie aktywowany) oraz libxml. Rozszerzenia te są jednak dostępne na większości hostingów (nie mówię tutaj o darmowych hostingach), więc nie ma żadnych problemów.

Przykładowy kod może wyglądać następująco:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Create a new DOM Document to hold our webpage structure
$dom = new DOMDocument();
 
// Load the url's contents into the DOM
@$dom->loadHTML($text);
 
// Empty array to hold all links to return
$links = array();
 
// Loop through each <a> tag in the dom and add it to the link array
foreach ($dom->getElementsByTagName('a') as $link) {
    $links[] = $link->getAttribute('href');
}

Zamiast metody loadHTML można również wykorzystać loadHTMLFile, podając bezpośredni adres URL, co jeszcze bardziej ułatwia nam zadanie i wyklucza wykorzystanie cURL.

Poza tym warto zauważyć, że „wygłuszamy” błędy przy pomocy znaku @ dla metody loadHTML. Zrobiłem to celowo, aby usunąć niechciane informacje o nieprawidłowo sformatowanym dokumencie XML, złych deklaracjach, etc.

Poza tym, każdemu kto zna język JavaScript powyższa składnia będzie bardzo znajoma. Bowiem w JavaScript istnieją dokładnie takie same metody do obsługi drzewa DOM (z powyższego listingu: getElementsByTagName, getAttribute).

Wyrażenia regularne vs DOM Document

Przeprowadziłem krótki benchmark celem sprawdzenia która metoda jest szybsza. Kod testujący działanie tych metod (zresztą już wcześniej wykorzystywany i testowany):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Benchmark($function, $iterations=1000, $args=null) {
    set_time_limit(0);

    if (is_callable($function) === true) {
        $result = microtime(true);

        for ($i = 1; $i <= $iterations; $i++) {
            call_user_func_array($function, $args);
        }

        return round(microtime(true) - $result, 4);
    }

    return false;
}

Pełen kod benchmarku: http://pastebin.com/n4YFuTmM

A oto uzyskane wyniki:

Metoda / iteracje 1 10 100
DOM Document 3.8939 38.4944 399.2282
Wyrażenia regularne 0.0383 0.1597 1.6267

Po takich wynikach jakie prezentuję powyżej, nie zwiększałem już ilości wywoływanych funkcji podczas testu. Wyraźnie bowiem widać, jak wielka różnica w wydajności poszczególnych rozwiązań istnieje.

Niewątpliwie przoduje tutaj wyrażenie regularne, które mimo iż nie jest napisane w najlepszy sposób, jest wielokrotnie szybsze (ponad 100 razy). Jest to spory argument ZA!

Podsumowanie

Po powyższym teście może zdziwić, ale mimo wszystko polecam używanie klasy DOMDocument do operacji na drzewie DOM. Dlaczego? Ano ważniejszy niż czas wykonania kodu po stronie serwera jest tylko nasz czas i nerwy na przygotowanie danego kodu.

Zamiast więc pisać bardzo kłopotliwy i skomplikowany kod, w którym ciężko doszukać się błędów i który jest trudno skalowalny, lepiej ten czas wykorzystać na optymalizację kodu w innych miejscach aplikacji.

Co jeśli zamiast linków chciałbyś pobrać wszystkie obrazy z danego kodu HTML? A jeśli chciałbyś pobrać dodatkowo tekst alternatywny z tego obrazka? Nasze wyrażenie regularne mogłoby się rozrastać w nieskończoność powodując coraz większe zaciemnienie kodu. Podczas gdy analizując drzewo DOM dokumentu zrobilibyśmy to w kilkanaście sekund :-)

Kolejnym argumentem przemawiającym za stosowaniem rozszerzenia DOM jest możliwość zintegrowania naszego drzewka z DOMXPath.

Z takimi narzędziami żaden problem nie będzie Ci przeszkodą, a pisanie własnych pajączków sieciowych przyjemnością!

Komentarze (9)

  1. eRIZ 12 września 2010

    Po powyższym teście może zdziwić, ale mimo wszystko polecam używanie klasy DOMDocument do operacji na drzewie DOM. Dlaczego? Ano ważniejszy niż czas wykonania kodu po stronie serwera jest tylko nasz czas i nerwy na przygotowanie danego kodu.

    A chyba ktoś tu zapomniał o phpQuery, o znacznie większych możliwościach. ;)

  2. Kamil Brenk 12 września 2010

    nie słyszałem o tej bibliotece, ale jak widzę to faktycznie jeszcze bardziej umila pracę :-)

    wcześniej porównałem klasę DOMDocument do pracy na drzewie DOM w JavaScript (przy czym nie trzeba obsługiwać różnych przeglądarek z osobna) – to teraz phpQuery ułatwia pracę niczym jQuery w JavaScriptowym DOM-ie.

    dzięki za zajawkę eRIZ :)

  3. Uirapuru 13 września 2010

    A spóźniłem się, bo też chciałem polecić phpQuery :) Ale fajny post, dzięki! :)

  4. ryszard kapała 27 października 2010

    Właśnie szukałem takiego czegoś do mojej strony. phpQuery też próbowałem. Dziękuje bardzo!

  5. Michal Wachowski 8 grudnia 2010

    Ostatni popełniłem takie coś… bez phpQuery.
    Działa, niestety. :)

  6. webit 4 stycznia 2011

    Ten kod nie zadziala dla <a title=”cos” href=”cos.html”> co tez jest poprawnym zapisem ;)

  7. Kamil Brenk 4 stycznia 2011

    Ano nie zadziała :-) Niemniej łatwo poprawić ten kod.

    Zresztą powyżej napisałem:

    Na potrzeby niniejszego wpisu stworzyłem proste wyrażenie, niezbyt optymalne, nie wychwytujące zapewne wszystkich adresów URL, jednak musi wystarczyć.

    Jako przewagę DOM Document nad wyrażeniami regularnymi podałem łatwość napisania odpowiednich regułek pobierających. W przypadku wyrażeń regularnych musimy myśleć o setkach różnych możliwościach zapisu HTML, podczas gdy skacząc po drzewie DOM niczym takim nie musimy się martwić.

  8. martvin 18 marca 2011

    do parsowania i wyciagania dowolnych elemetow ze strony polecam JS_Extractor. Bardzo stary ale skuteczny skrypcik. Uzywalem tez jego wczesniejszej wersji „TableExtractor”- jesli trzeba wyciagnac dane tabelaryczne to jest on nie dozastapienia.

  9. Kamil Brenk 18 marca 2011

    Faktycznie, świetne narzędzie! :-)



Kamil Brenk Blog

PHP, JavaScript, SQL, HTML

  • Informacje o blogu

    Kamil Brenk

    Blog o tworzeniu aplikacji na potrzeby sieci Web.

    Praktyczne przykłady, porady i sztuczki. PHP, SQL, AJAX, JavaScript, HTML i pochodne.

    Kanał RSS

    • Najnowsze
    • Komentarze
    • Popularne
    • Liczniki w CSS
    • Wyprzedaż książek o programowaniu!
    • Niestandardowy placeholder
    • JavaScript w modułach
    • Co dalej z blogiem?
    • Interaktywna mapa w HTML i CSS
    • Olsztyn: Jak wyseparować zawartość zassaną przez file_get_content?
    • ERMLAB: Od czegoś trzeba zacząć :) Wiele osób właśnie stawia na...
    • david: co nalezy wkleić na stronę aby plik ze stylami był ladowany...
    • krynicz: Nie jestem pewien czy dobrze to rozumiem: wpisujemy OG w...
    • yaro: Jak zmienić re_write znak "_" na "-"?
    • Piotr: stworzyłem prostą stronkę w PHP, czy jest możliwość aby...
    • MichalR: Super sprawa... bardzo przydatne.. dzieki i pozdrawiam..
    • Niestandardowe czcionki na stronie
    • Sposoby wczytywania JavaScript
    • Gramatyka w PHP, część 1
    • Umowa i zaliczka dla freelancera
    • Wysyłanie wiadomości SMS w PHP
    • Projekt aplikacji po stronie klienta
    • Własny mechanizm Feed
  • Szukajka
    Wpisz co chcesz wyszukać na stronie…
  • Kategorie
    • Apache
    • Freelancer
    • Front-end Development
    • HTML5 & CSS3
    • Inne
    • JavaScript
    • Książki
    • PHP
    • Po godzinach
    • Pozycjonowanie
    • Protokół HTTP
    • SQL
    • Wyrażenia regularne
  • Moje serwisy
    • Testy zawodowe
    • Miłość, uczucia i seks
  • Czytane blogi
    • Wojciech Sznapka
    • Wojciech Soczyński
    • Michał Wachowski
    • Tomasz Kowalczyk
    • Filip Górczyński
  • Strona główna
  • Curriculum Vitae
  • O mnie
  • Przykład: Gramatyka w PHP
  • Przykład: Kompresja CSS
  • Przykład: Kompresja JavaScript
  • Przykład: Skracanie linków
  • Przykład: Wykrywanie serwera HTTP
  • Przykład: Własna bramka SMS
  • Mapa strony
  • Kontakt

Kamil Brenk © 2010. All rights reserved.

Designed by FTL Wordpress Themes brought to you by Smashing Magazine.

Do góry ∧