Parser HTML: Python vs PHP

Każdy kto miał styczność z pisaniem parsera plików HTML w php wie, że nie jest to nic przyjemnego, i podczas pisania trzeba używać bardzo różnych sztuczek, które komplikują cały kod i tylko dobre komentarze pozwolą nam na łatwe utrzymanie procedury. W Pythonie, nie ma najmniejszego problemu z napisaniem takiej aplikacji – ten język posiada wbudowane klasy do obsługi takich zadań. Wiedząc o tych różnicach (php vs python) postanowiłem wykonać test, aby jednoznacznie stwierdzić, że python jest lepszy :).

Edit: 31 sierpień 2008

Polecam do przeczytania artykuł dotyczący tego tematu: http://blog.zeromski.com.pl/2008/08/31/phpquery-parser-html-z-api-jquery/.

Założenia

Plik do parsowania: file.html (0,8kb)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<html>
    <head>
    <title>Tytuł</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />          
    <meta name="Author" content="Żeromski Mateusz"/>
    <meta name="Keywords" content="Żeromski Mateusz Matex portfolio" />
     <meta name="Description" content="Portfolio: Żeromski Mateusz Matex"/>
    </head>
    <body>
   

    <div class="element">
    <p class="title">Element Title</p>
    <p class="description">Opis elementy</p>
    <a href="http://localhost/78678678345" class="more"> Link</a>
    </div>

    <div class="element">
    <p class="title">Element Title</p>
    <p class="description">Opis elementy</p>
    <a href="http://localhost/78678678345" class="more"> Link</a>
    </div>

    </body>
</html>

Parsowanie PHP

Prace zacząłem od znalezienia jakiejś gotowej klasy – wykorzystałem klasę HtmlParser ( link ). Jak miałem już “gotowca”, to napisałem taki kod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?
$start = microtime(true);
$data = file_get_contents('file.html');
include('htmlparser/htmlparser.inc');
$parser = new HtmlParser($data);
$elements = array();
while($parser -> parse()){
    if($parser->iNodeAttributes['class'] == "title" && strlen(trim($parser -> iNodeValue)) > 0)
        $element['title'] = trim($parser -> iNodeValue);

    if($parser->iNodeAttributes['class'] == "description" && strlen(trim($parser -> iNodeValue)) > 0)  
       
        $element['desc'] = trim($parser -> iNodeValue);
   
    if(isset($parser->iNodeAttributes['href']) && strlen(trim($parser -> iNodeValue)) > 0){
       
        $element['link'] = trim($parser -> iNodeValue);
        $element['url'] = trim($parser -> iNodeAttributes['href']);
    }
   
    if(count($element) == 4){
       
        $elements[] = $element;
        $element = array();
    }  
}
foreach($elements as $element){
   
    echo  "Title: \t".$element['title']."\n";
    echo "Description: \t".$element['desc']."\n";
    echo  "Link: \t". $element['link']."\n";
    echo  "Url: \t".$element['url']."\n";
    echo  '-----'."\n";
}

$end = microtime(true);
echo ($end -$start);
?>

Rozmiar pliku: 1,1kb.
Średni czas wykonywania: 0,013 +/- 0,001

Parsowanie Python

Tutaj już miałem mniej problemów z znalezieniem klasy, która mi pomoże w parsowaniu – dlatego, że python ma to wbudowane. Lecz skorzystałem z małej “nakładki” (coś na przykładzie Snoopy -> Curl): Beautiful Soup ( link ). Kod jaki napisałem to:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import time
start = time.time()
from BeautifulSoup import BeautifulSoup

f = open("file.html")
page = ''
try:
    for line in f:
        page = page+line
finally:
    f.close()

soup = BeautifulSoup(page)

elements = soup.findAll('div', {'class' : 'element'});
result = []
for element in elements:
    _block = {}
    _block['title'] = str(element.find('p', {'class' : 'title'}).contents);
    _block['desc'] = str(element.find('p', {'class' : 'description'}).contents);
    link = element.find('a')
    _block['link'] = str(link.contents)
    _block['url'] = str(link['href'])
    result.append(_block)
   
for element in result:
    print "Title: "+element['title']
    print "Description: "+element['desc']
    print "Link "+ element['link']
    print "Url "+element['url'];
    print '-----'  


finish = time.time()

delta = float(finish) - float(start);
print delta

Rozmiar pliku: 0,8 kb
Czas wykonywania: 0,032 +/ 0,001

Podsumowanie testu

Jak widać, więcej kodu napisałem w php, i właśnie w nim procedura wykonywała się trzy razy szybciej – to mnie zdziwilo.

Postanowiłem więc zwiększać wielkość pliku do parsowania i patrzyłem jak zmienia się czas wykonywania procedur, prezentuje to poniższa tableka.

rozmiar pliku (kb) php (s) python (s)
0,8 0,013 0,32
26 0,39 0,47
100 1,56 1,8
198 3,17 3,75
593 9,4 11,1

Jak widać python jest jednak wolniejszy – tutaj nie mam wątpliwości. Lecz warunki były cieplarniane, plik HTML był idealny – lecz to ma się nijak do rzeczywistości, bo nie możemy zakładać, że strona którą parsujemy będzie zawsze taka sama (no chyba, że używamy własnej).

Pomiędzy elementy dodałem więc część kodu onet.pl, tutaj wyszła różnica pomiędzy pythonem a php. O ile ten pierwszy działał tak jak wcześniej – czyli dobrze, php wypluwał błędne dane. Dzieję się tak dlatego, że python po kodzie html porusza się jak po pliku xml, natomiast php parsuje wszystko linia po ninijce (w moim teście – wydaje mi się że można napisać skrypt tak, aby plik html traktował jak xml, ale ja to ominąłem).

Czasy parsowania po zmianie file.html na niepoprawny wyglądały tak

rozmiar pliku: 82kb
PHP: 0,8s
Python: 0,4s

Różnica dwukrotna na korzyść pythona, i dodatkowo skrypt PHP nie działał poprawnie.

Wniosek jest jeden – jeżeli mamy pisać parser – warto przy tej okazji skorzystać z pythona, w przyszłości unikniemy wielu stresowych sytuacji (np przy zmiana parsowanego serwisu). Ponadto procedura napisana w pythonie jest czytelniejsza i łatwiejsza do analizowania – ma to związek z samą składnią języka, ale o tym kiedy indziej.

 

Tagi: , ,

Komentarze: 13 do “Parser HTML: Python vs PHP”

  1. 1 ranza

    Fajne.
    Nie wiem jak to wygląda w php, ale w pythonie do mierzenia czasu powinieneś użyć timeit()

  2. 2 Mateusz Żeromski

    Nie sądze aby to poprawiło jakkolwiek działanie procedury, ale swoją droga dość ciekawe to timeit() – dzięki za info.

  3. 3 Alek

    Na PHP nie znam sie, ale w Pythonie napisal bym to tak:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    from __future__ import with_statement
    import time
    start = time.time()
    from BeautifulSoup import BeautifulSoup

    with open('file.html') as f:
        page = f.read()

    for element in BeautifulSoup(page).findAll('div'):
        element_link = element.find('a')
        print "Title: %s\nDescription: %s\nLink: %s\nUrl: %s\n----\n" \
            % (element.find('p', {'class' : 'title'}).contents,
               element.find('p', {'class' : 'description'}).contents,
               element_link.contents, element_link['href'])

    print time.time() - start
  4. 4 Mateusz Żeromski

    Owszem masz rację, ale ja celowo robilem dwie pętle po to aby kody php’a i pythona były podobne, i czytelniejsze, Twój kod jest dobry, pewnie troszke jest szybszy niż mój ale też troszke bardziej skomplikowany a mi zależało aby również osoby nie znający dobrze pythona zrozumiały moje wypociny :).
    Każdą procedurę można zapisac na milion sposobów, gdybyś użył metody timeit() jak napisał ranza wtedy by było widać przewagę pythona nad php, a tak moim zdaniem, po prostu skompresowałeś mój kod, przez co, moze byc on bardziej nieczytelny, pzdr i dzieki :)

  5. 5 Alek

    Czytelnosc to sprawa wzgledna. Ja sadze, ze moj kod jest bardziej czytelny dla tych, ktorzy znaja Pythona. Nie o to jednak chodzi.

    Do napisania mojej wersji sklonilo mnie to, ze w tekscie porownujesz i wielkosc kodu i szybkosc wykonania. Wiec jesli chodzi o wielkosc kodu to pokazalem Ci, ze w Pythonie mozna pisac bardzo zwiezly i czytelny kod. To jedna z wazniejszych cech Pythona.

    pozdrawiam

  6. 6 Mateusz Żeromski

    Masz rację – tak sobie myślę że taki kod w php nie jest możliwy w tym przypadku. A z tym kodem to wiem, a moze byc tak porównać omry z php i sqlalchemy – tutaj będzie widać najlepiej potęgę pythona – może mi sie uda przed połową lipca, zobaczymy. Dzieki i pzdr.

  7. 7 Kuba

    Fajny test, najpierw stwierdziłeś że Python musi wygrać, a potem zmieniałeś środowisko testowe dopuki faktycznie nie wygrał. Gratuluję ;)

  8. 8 marcin

    Hmm, na twoim miejscu trochę rzetelniej podszedłbym do tego typu testów. Przede wszystkim główny narzut czasowy pochodzi od bibliotek / nakładek, niemniej w żaden sposób nie podajesz czy to są najpopularniejsze, najlepsze i w ogóle dlaczego te. Rzetelniej byłoby napisać skrypty parsujące z palca.

    Druga sprawa – patrz komentarz Kuby. W ostatnim teście różnica wynosiła 0,4 sekundy! To żadna różnica (szczególnie patrząc na poprzednie rezultaty). Zawsze wykonuje się serię testów i na końcu podaje się średni czas.

    Ostatnia rzecz – cytując: “wydaje mi się że można napisać skrypt tak, aby plik html traktował jak xml, ale ja to ominąłem”, a potem zarzucasz PHP, że wypluwa błędne dane. Bez komentarza.

    Ewidetnie próbowałeś udowodnić konkretną tezę. Trochę więcej obiektywności następnym razem.

  9. 9 Egz

    I dlaczego nie DOM (SimpleXML od biedy)? Sądzę, że głupim expatem wykonam te działania szybciej niż Twój kod i błędnych wyników nie będzie. Ewidetna stronniczość LUB brak odpowiedniej wiedzy do przeprowadzania takich testów.

  10. 10 uk

    ku

  11. 11 on

    nowłacha!

  12. 12 Chłopiec HTMLowo-Windowsowy

    Świetny przykład na to, że znajomość języka programowania nie oznacza umiejętności programowania.

    Niezależnie od tego co chciałeś pokazać, to HTMLa w ten sposób po prostu się nie parsuje!

  13. 13 pats

    @Chłopiec HTMLowo-Windowsowy: a możesz powiedzieć dlaczego?

Napisz komentarz