Wszystkie wpisy, których autorem jest bart

Web scraping czyli „skrobanie WWW”

Web scraping, czyli „skrobanie WWW”, lub bardziej formalnie ekstrakcja danych ze stron WWW, to programowa technika ekstrakcji informacji ze stron WWW. Takie oprogramowanie zazwyczaj symuluje eksplorację stron WWW jaką wykonuje człowiek przeglądając i analizując strony webowe.

Web scraping jest ściśle związany z indeksowaniem WWW, które polega na indeksowaniu informacji zawartej na stronach WWW za pomocą bota lub web crawlera. Technika ta została zaadoptowana w większości silników wyszukujących. W przeciwieństwie do nich, web scraping skupia się bardziej na przekształceniu niestrukturalnych danych ze stron WWW, zwykle w formacie HTML, do postaci danych strukturalnych, które można łatwo gromadzić i analizować w centralnej lokalnej bazie danych lub arkuszu kalkulacyjnym. (*)

To tyle jeśli chodzi o nakreślenie tematu. Zainteresowałem? Jeśli tak, to zachęcam do dalszego czytania. Poniżej zamieszczam prosty kod pythonowy, który ma pokazać, jak można wykorzystać web scraping do własnych celów. Program wczytuje dane ze strony http://www.linuxtoday.com i dodaje je do listy dictów (lub słowników – jeśli ktoś woli). Mając już dane w postaci strukturalnej można z nimi zrobić co się chce. Dodatkowo, program zapisuje wyświetlone dane do pliku linuxtoday.txt. Jako ćwiczenie pozostawiam dopisanie funkcji zapisującej pobrane dane do bazy np. SQLite3 (lub innej ulubionej).

Jak tworzyć odpowiednie ścieżki XPath?

Przeglądając stronę WWW np. w Chrome, najeżdżasz kursorem myszy nad interesującym Cię elementem i klikasz prawy przycisk myszy. Pojawia się menu kontekstowe, klikasz „zbadaj element”. W panelu „Inspektor” w dolnej części przeglądarki, klikasz na strzałce w linii, która została podświetlona i jeśli widzisz element, który Cię interesuje, klikasz prawym przyciskiem myszy na nim. Pojawia się kolejne menu kontekstowe, z którego wybierasz „Copy XPath”. Pewnie brzmi to dość zawile, ale po kilku próbach, stanie się prostą czynnością. :-)

BTW – w tym roku byłem na PyConPL i niestety za późno chciałem się zapisać na warsztat o web scrapingu. Temat mnie jednak zainteresował i zmobilizowałem się do zbadania tego tematu. Na pewno jeszcze coś o tym napiszę.

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# author Bart Grzybicki <bgrzybicki@gmail.com>
 
from lxml import html
import requests
 
OUT_FILE = u'linuxtoday.txt'
 
def main():
    page = requests.get('http://www.linuxtoday.com')
    tree = html.fromstring(page.text)
    lt_list = []
    index = 1
    while True:
        lt_dict = {}
        title = tree.xpath('//*[@id="container"]/table[3]/tbody/tr/td[2]/div[3]/div[{}]/a/strong/text()'.format(index))
        post_date = tree.xpath('//*[@id="container"]/table[3]/tbody/tr/td[2]/div[3]/div[{}]/span/text()[1]'.format(index))
        link = tree.xpath('//*[@id="container"]/table[3]/tbody/tr/td[2]/div[3]/div[{}]/a/@href'.format(index))
        desc = tree.xpath('//*[@id="container"]/table[3]/tbody/tr/td[2]/div[3]/div[{}]/p/span/text()'.format(index))
        if desc == []:
            desc = tree.xpath('//*[@id="container"]/table[3]/tbody/tr/td[2]/div[3]/div[{}]/p/text()'.format(index))
        else:
            desc_bckp = desc
        if title == []:
            break
        else:
            idx = post_date[0].index('\n')
            post_date[0] = post_date[0][:idx]
            post_date[0] = post_date[0].replace(' (', '')
            post_date[0] = post_date[0].replace(')', '')
            lt_dict['title'] = title[0]
            lt_dict['date'] = post_date[0]
            lt_dict['link'] = link[0]
            try:
                lt_dict['description'] = desc[0]
            except Exception:
                lt_dict['description'] = ''
            lt_list.append(lt_dict)
            index += 1
    out_file = open(OUT_FILE, 'w')
    for x in lt_list:
        final_out = (x['title'] + '\n' + x['date'] + '\n' + x['link'] + '\n' + x['description'] + '\n')
        final_utf8 = final_out.encode('utf-8')
        print(final_utf8)
        print(u'Saving data to ' + OUT_FILE + u'...')
        out_file.write(final_utf8)
        out_file.write(u'\n')
    out_file.close()
    print(u'Done.')
 
if __name__ == '__main__':
   main()

Dla ułatwienia zamieściłem powyższy kod na GitHubie w postaci gista, którego możesz sforkować i pobawić się kodem:

https://gist.github.com/bartgee/4eb1838a8fb9d3f6c340

* – źródło ze strony: http://en.wikipedia.org/wiki/Web_scraping

Jak z Pythona uruchomić skrypt bashowy i jak wykorzystać kod powrotu skryptu?

Czasami zachodzi potrzeba uruchomienia z naszego programu/skryptu pythonowego skryptu w Bashu. Oczywiście, można byłoby całą funkcjonalność tego skryptu przepisać w Pythonie, jednak czasami musimy użyć takiego rozwiązania i wtedy świetnie byłoby, gdyby można było odczytać kod powrotu skryptu bashowego.

W poniższym przykładzie mamy skrypt w Bashu, który sprawdza czy plik testfile istnieje w bieżącym katalogu. Jeśli istnieje, zwraca wartość 0, jeśli nie, zwraca 1. Dodatkowo dodałem kilka sleepów i skrypt wypisuje na ekranie ilość sekund do wywołania sprawdzenia.

sleep.sh:

#!/bin/bash
 
PLIK="./testfile"
 
echo "3"
sleep 1
echo "2"
sleep 1
echo "1"
sleep 1
if [ -e $PLIK ]; then
        exit 0
else
        exit 1
fi

W skrypcie pythonowym importujemy moduł subprocess, który umożliwia uruchamianie nowych procesów, podłączanie się do ich potoków wejścia, wyjścia i błędu oraz uzyskiwanie ich kodu powrotu.
Ze względów bezpieczeństwa zaleca się ustawienie parametru shell=False. Więcej informacji na ten temat można przeczytać tutaj.
Jeżeli potrzebujemy bardziej zaawansowanych opcji, warto użyć klasy Popen z modułu subprocess.

runbash.py:

#!/usr/bin/env python2
 
import subprocess
print('start')
if (subprocess.call('./sleep.sh', shell=False) == 0):
    print('plik istnieje')
else:
    print('plik nie istnieje')
print('koniec')

uruchomienie:

[bart@fedora20 python]$ ./runbash.py 
start
3
2
1
plik nie istnieje
koniec
[bart@fedora20 python]$ touch testfile
[bart@fedora20 python]$ ./runbash.py 
start
3
2
1
plik istnieje
koniec
[bart@fedora20 python]$

opis działania:

Przy pierwszym uruchomieniu w lokalnym katalogu nie było pliku testfile:

1. Skrypt runbash.py wywołał podproces sleep.sh
2. Instrukcja warunkowa if/else w sleep.sh sprawdziła, że plik nie istnieje i skrypt zakończył się z kodem powrotnym 1.
3. Instrukcja warunkowa if/else w runbash.py odczytała kod powrotny z sleep.sh (1) i wyświetliła na ekranie komunikat z informacją, że plik nie istnieje.

Przed drugim uruchomieniem utworzyliśmy plik testfile (touch testfile), sleep.sh zakończył się z kodem powrotu 0. Kod powrotu został odczytany przez runbash.py i skrypt wyświetlił informację, że plik istnieje.