Στο Διαδίκτυο υπάρχει μεγάλη ποσότητα πληροφορίας ‘εγκλωβισμένη’ μέσα σε ιστοσελίδες. Οι ιστοσελίδες είναι φτιαγμένες για ανάγνωση από τον άνθρωπο κι όχι από ένα πρόγραμμα. Πολλές φορές όμως προκύπτει η ανάγκη για ‘μηχανική’ ανάγνωση των περιεχομένων ενός web site και η μετατροπή τους σε δομές πιο εύκολα επεξεργάσιμες από τον υπολογιστή. Η ‘μηχανική’ εξαγωγή της πληροφορίας ονομάζεται web(page) scraping.
Η διαδικασία του web scraping δεν είναι εύκολη:
- Πρέπει να βρεθεί ένα (επαναλαμβανόμενο) σχέδιο στην HTML μιας σελίδας που να επιτρέπει τον εντοπισμό της αρχής και του τέλους των κομματιών πληροφορίας που θέλουμε.
- Το σχέδιο αυτό είναι μοναδικό. Για κάθε άλλη ιστοσελίδα, πρέπει να βρεθεί νέο σχέδιο.
- Αν η σχεδίαση της ιστοσελίδας αλλάξει, πρέπει να αλλάξει και το σχέδιο (άρα και το πρόγραμμα) εξαγωγής δεδομένων.
- Προσοχή στους ‘Όρους Χρήσης’ του κάθε site: σε κάποια από αυτά, η μηχανική εξαγωγή πληροφορίας δεν επιτρέπεται.
Παρ’όλα τα προηγούμενα, πρέπει να πούμε ότι το web scraping είναι πολλές φορές αναγκαίο! Στην Python έχουμε διάφορους τρόπους στη διαθεσή μας για να υλοποιήσουμε το web scraping, όπως θα δούμε στη συνέχεια.
Η κύρια μέθοδος string που μπορούμε να χρησιμοποιήσουμε είναι η find(), η οποία επιστρέφει το σημείο (index) που βρίσκεται ένα string μέσα σε κάποιο άλλο, ή -1 αν δεν υπάρχει.
>>> s = 'abcdefghijk'
>>> i = s.find('def')
>>> i
3
>>> i = s.find('dxf')
>>> i
-1
>>> s = 'abcdabcd'
>>> i = s.find('abc')
>>> i
0
>>> i = s.find('abc',3)
>>> i
4
Η γενική μεθοδολογία είναι η ακόλουθη:
Από το site του μεταπτυχιακού (http://di.ionio.gr/msc/) θέλουμε να εξάγουμε τους τίτλους και τα links των ‘νέων/ανακοινώσεων’. Μια προσεκτική ανάγνωση της HTML δείχνει το σημείο που μας ενδιαφέρει:
<h3>Νέα/Ανακοινώσεις</h3>
<ul class="latestnews-hilite8">
<li class="latestnews-hilite8">
<a href="/msc/news/1-announcements/45-2010-02-26-10-35-44.html" class="latestnews-hilite8">
Παράταση δηλώσεων μαθημάτων επιλογής Π.Μ.Σ</a>
</li>
<li class="latestnews-hilite8">
<a href="/msc/news/1-announcements/42--2009-2010.html" class="latestnews-hilite8">
ΟΡΘΗ ΕΠΑΝΑΛΗΨΗ: Δηλώσεις επιλεγομένων μαθημάτων ΠΜΣ εαρινού εξαμήνου ακαδ. έτους 2009-2010</a>
</li>
</ul>
To string <li class="latestnews-hilite8"> είναι αυτό που μας οδηγεί σε κάθε ανακοίνωση.
- Προσοχή! Το hilite8 ή το latestnews-hilite8 δεν αρκεί! (γιατί;)
Στη συνέχεια μπορούμε να εντοπίσουμε την έναρξη του link με το <a href=", το τέλος του link με το " που έπεται, την έναρξη του τίτλου με το πρώτο > που θα βρεθεί αμέσως μετά και το τέλος με το <.
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import urllib
# acquire page
page = urllib.urlopen("http://di.ionio.gr/msc/")
pagetext = page.read()
page.close()
# locate last news - done by string search here - not re
i = pagetext.find('<li class="latestnews-hilite8">')
while i!=-1:
i += 31 # skip found item
j = pagetext.find('<a href=',i)
if j==-1: break # should not happen
i = pagetext.find('"',j+9)
if i==-1: break # should not happen
url = pagetext[j+9:i].strip()
j = pagetext.find('>',i)
if j==-1: break # should not happen
i = pagetext.find('<',j)
if i==-1: break # should not happen
title = pagetext[j+1:i].strip()
print "%s\n[%s]\n" % (title,url)
i = pagetext.find('<li class="latestnews-hilite8">',i)
Σχόλια στον προηγούμενο κώδικα:
>>> title = 'a title'
>>> url = 'http://a.url'
>>> print "%s\n[%s]" % (title,url)
a title
[http://a.url]
Με τη διαδικασία του web scraping επεξεργαζόμαστε κείμενο, το οποίο μπορεί να βρίσκεται σε διάφορες κωδικοποιήσεις χαρακτήρων και όχι π.χ. στη μοντέρνα utf-8 κωδικοποίηση που χρησιμοποιεί πλέον ο υπολογιστής μας. Πώς αντιμετωπίζουμε την κατάσταση αυτή;
H Python διαθέτει 2 είδη strings:
Τα κοινά strings που έχουμε δει ως τώρα, τα οποία μπορούν να θεωρηθούν απλές ακολουθίες από bytes. Αν και μπορεί να αποθηκευτεί σε αυτά κείμενο σε οποιαδήποτε κωδικοποίηση, οι μέθοδοι των string δεν θα δουλέψουν σωστά όταν ένας χαρακτήρας αποτελείται από 2 ή περισσότερα bytes!
>>> s = '123δοκιμή' >>> len(s) 15 >>> print s[3] >>> print s[4] � >>> for c in s: ... print c ... 1 2 3 � � � � � �Για να καταλάβετε γιατί το μέγεθος του s είναι 15 (!) ή γιατί δεν τυπώνονται οι χαρακτήρες όπως θα περίμενε κανείς, σκεφτείτε ότι στην εξ’ορισμού κωδικοποίηση utf-8 ένας ελληνικός χαρακτήρας καταλαμβάνει 2 bytes.
Unicode strings όπου, σε αντίθεση με τα κοινά strings, κάθε θέση περιέχει έναν ολόκληρο χαρακτήρα, όσα bytes κι αν καταλαμβάνει αυτός.
>>> us = u'123δοκιμή' >>> len(us) 9 >>> print us[4] ο >>> print us[5] κ >>> for c in us: ... print c ... 1 2 3 δ ο κ ι μ ήΠαρατηρήστε πώς το u' ... ' ορίζει μια σταθερά unicode string.
Μπορούμε να μετατρέψουμε το ένα είδος string στο άλλο και αντίστροφα.
Για μετατροπή από unicode string σε απλό string
>>> us = u'123δοκιμή' >>> us u'123\u03b4\u03bf\u03ba\u03b9\u03bc\u03ae' >>> s = us.encode('utf-8') >>> s '123\xce\xb4\xce\xbf\xce\xba\xce\xb9\xce\xbc\xce\xae'Η εκτύπωση των περιεχομένων των us και s σε ‘debug mode’ (χωρίς το print) δείχνει πώς το unicode string μετατρέπεται σε κωδικοποίηση UTF-8.
Αντίστροφα, για τη μετατροπή απλού string σε unicode string (με τη σημαντική διαφορά ότι εδώ πρέπει να ξέρουμε εκ των προτέρων την κωδικοποίηση του απλού string)
>>> s = '123δοκιμή' >>> s '123\xce\xb4\xce\xbf\xce\xba\xce\xb9\xce\xbc\xce\xae' >>> us = s.decode('utf-8') >>> us u'123\u03b4\u03bf\u03ba\u03b9\u03bc\u03ae'Παρατηρήστε ότι αν η κωδικοποίηση του s δεν ήταν UTF-8, η κλήση της decode θα αποτύγχανε!
Υποθέστε ότι θα δουλέψετε στην ιστοσελίδα http://www.meteo.gr/cf.asp?city_id=5. Μια γρήγορη ματιά στον κώδικα HTML σας πείθει ότι δεν πρόκειται για περιεχόμενο σε UTF-8:
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-7">
Θέλετε να επεξεργαστείτε το περιεχόμενο σε Unicode και να τυπώσετε αποτελέσματα σε UTF-8.
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import urllib
page = urllib.urlopen("http://www.meteo.gr/cf.asp?city_id=5")
# read and convert to unicode
pagetext = page.read().decode("iso8859_7")
page.close()
# additional work on unicode can be done here, like for example
outstr = pagetext[100:230]
# print after converting to UTF-8 that our console supports
print outstr.encode("utf_8")