Informationsextraktion aus Websites Michael Haas - - PowerPoint PPT Presentation

informationsextraktion aus websites
SMART_READER_LITE
LIVE PREVIEW

Informationsextraktion aus Websites Michael Haas - - PowerPoint PPT Presentation

Informationsextraktion aus Websites Michael Haas <haas@computerlinguist.org> Service-Center Forschungsdaten, Universitt Mannheim 22.01.2013 Lessons Learned - Kontext Mein Hintergrund: B.A. Computerlinguistik, Universitt Heidelberg


slide-1
SLIDE 1

Informationsextraktion aus Websites

Michael Haas <haas@computerlinguist.org>

Service-Center Forschungsdaten, Universität Mannheim

22.01.2013

slide-2
SLIDE 2

Lessons Learned - Kontext

■ Mein Hintergrund: B.A. Computerlinguistik, Universität

Heidelberg

■ Projekte am Service-Center:

■ Manuel Trenz: Beobachtung der Preisveränderungen einer

gegebenen Menge an Produkten auf Online-Shops und Preisvergleichern

■ Dominic Nyhuis: Durchsuchen der Online-Archive von 10

Zeitungen per Screen Scraping

■ Georg Wernicke: NER und Sentiment-Analyse auf

Zeitungsartikeln

■ Ziele für heute

■ Tutorial Screen Scraping ■ Folien als Referenz ■ Lessons Learned als Hinweise/best practices

slide-3
SLIDE 3

Aufgabe

■ Kunde benötigt Daten von Website ■ Manuelle Extraktion mit HiWis und Copy&Paste zu aufwendig ■ Automatisieren!

slide-4
SLIDE 4

Python

Konkret: Kunde möchte Produktpreise über längeren Zeitraum überwachen

1 > > import u r l l i b 2 2 > > content = u r l l i b 2 . ur lo p en ( " http :// host / produkt / i d " ) 3 HTTPError : HTTP Error 403: denied contact webmaster@host

slide-5
SLIDE 5

Python - Ninja Level 1

Website mag unseren User-Agent nicht!

1 > > r e q u e s t = u r l l i b 2 . Request ( " http :// host / produkt /123" ) 2 > > r e q u e s t . add_header ( ’ UserAgent ’ , ’ M o z i l l a /5.0 ’ ) 3 > > opener = u r l l i b 2 . build_opener () 4 > > content = opener . open ( r e q u e s t ) . read () 5 > > content [ 0 : 3 0 ] 6 ’ <!DOCTYPE HTML > <html lang="de" ’

slide-6
SLIDE 6

Python - Iteration

Kunde will mehrere Produkte überwachen

1 > > f o r p in products ✄100: 2 r e q u e s t = u r l l i b 2 . Request ( " http :// host / produkt /" + p ) 3 r e q u e s t . add_header ( ’ UserAgent ’ , ’ M o z i l l a /5.0 ’ ) 4

  • pener = u r l l i b 2 . build_opener ()

5 content = opener . open ( r e q u e s t ) . read () 6 HTTPError : HTTP Error 421: too fast contact webmaster@host

slide-7
SLIDE 7

Python - Ninja Level 2

1 > > import time 2 > > f o r p in products ✄100: 3 r e q u e s t = u r l l i b 2 . Request ( " http :// host / product /" + p ) 4 r e q u e s t . add_header ( ’ UserAgent ’ , ’ M o z i l l a /5.0 ’ ) 5

  • pener = u r l l i b 2 . build_opener ()

6 content = opener . open ( r e q u e s t ) . read () 7 time . s l e e p (5)

slide-8
SLIDE 8

Python - Paranoid Ninja

Admin könnte Access Logs überwachen - Abstände der Zugriffe zufällig halten!

1 > > import random , time 2 > > f o r p in products ✄100: 3 r e q u e s t = u r l l i b 2 . Request ( " http :// host / product /" + p ) 4 r e q u e s t . add_header ( ’ UserAgent ’ , ’ M o z i l l a /5.0 ’ ) 5

  • pener = u r l l i b 2 . build_opener ()

6 content = opener . open ( r e q u e s t ) . read () 7 time . s l e e p ( random . uniform (1 ,5) )

slide-9
SLIDE 9

Python - there is a lib for that

Alles zu kompliziert! Besser:

1 $ sudo e a s y _ i n s t a l l 2.7 l e e c h i 2 $ ipython2 3 > > import l e e c h i 4 > > l = l e e c h i . Leechi () 5 > > f o r p in product : 6 content = l . fetchDelayed ( " http :// host / product /" + p )

slide-10
SLIDE 10

Python - Leechi

1 > > l = l e e c h i . Leechi ( c o o k i e s=True , r e t r y =3) 2 > > l . chooseRandomUA () 3 > > l . setCustomUA ( "Wget / 1 . 9 . 1 " ) 4 > > handle = l . obtainHandle ( " http :// host /" ) 5 > > content = handle . read () 6 > > handle = l . obtainHandleDelayed ( " http :// host /" )

slide-11
SLIDE 11

Python - Leechi - Source

■ http://github.com/mhaas/leechi/ ■ http://pypi.python.org/pypi/Leechi/0.2 ■ Send Patches:

■ Periodisches Wechseln von UA ■ Unterstützung (anonymer) Proxy-Server ■ Tests

slide-12
SLIDE 12

Python - HTML Parsing

Und nun?

1 > > content = """<html> <body> 2 <p c l a s s =" p r i c e "> p r e i s i s t : 5 euro </p> 3 </body></html>""" 4 > > import re 5 > > re . s e ar c h ( ur ’ p r e i s i s t : (\ d {0 ,4}) euro ’ , content ) . group (1) 6 ’ 5 ’

slide-13
SLIDE 13

Python - HTML Parsing - Nie RegEx

■ HTML/XML sind kontextfreie Sprachen ■ Reguläre Ausdrücke beschreiben reguläre Sprachen ■ Kontextfreie Sprachen sind mächtiger als reguläre Sprachen1

1“Parsing HTML with regex summons tainted souls into the realm of the

living.”http://stackoverflow.com/a/1732454

slide-14
SLIDE 14

Python - HTML Parsing - BeautifulSoup

■ Besser: BeautifulSoup 4 ■ Kann alles, auch Tagsuppe:

■ fehlende schließende Tags ■ mangelhaft kodierte Sonderzeichen

■ http://www.crummy.com/software/BeautifulSoup/

slide-15
SLIDE 15

Python - HTML Parsing - BeautifulSoup - Navigation

HTML-Attribut class sehr nützlich als Ziel.

1 $ sudo e a s y _ i n s t a l l 2.7 b e a u t i f u l s o u p 4 2 $ python2 3 > > content = """<html> 4 <body> 5 <p class=”price”>p r e i s i s t : 5e</p> 6 </body> 7 </html>""" 8 > > from bs4 import BeautifulSoup 9 > > soup = BeautifulSoup ( content ) 10 > > soup . f i n d (class_=”price” ) 11 <p c l a s s=" p r i c e ">p r e i s i s t : 5e</p>

slide-16
SLIDE 16

Python - HTML Parsing - BeautifulSoup - Navigation

Container für Listen

1 > > content = """<u l id=”priceList”> 2 <l i >p r e i s : 5e</ l i > 3 <l i >p r e i s : 10e</ l i > 4 <l i >p r e i s : 15e</ l i > 5 </ul>""" 6 > > p r i c e L i s t = soup . f i n d ( ’ u l ’ , id=’priceList’) 7 > > f o r node in p r i c e L i s t . c h i l d r e n : # p r i c e L i s t . c o n te n t s 8 re . s e ar c h ( ur " p r e i s : (\ d {1 ,4}) e" , node . s t r i n g ) . group (1) 9 5 10 10 11 15

slide-17
SLIDE 17

Python - HTML Parsing - BeautifulSoup - Navigation

Durch den Baum hangeln

1 > > content = """<div > 2 <h1 class=”section-header”>Preise </h1> 3 <h3>Zubehoer </h3> 4 <ul> 5 <l i >p r e i s : 5e</ l i > 6 </ul> 7 </div >""" 8 > > soup . d i v . h1 . n e x t S i b l i n g . n e x t S i b l i n g 9 <ul> <l i >p r e i s : 5e</ l i ></ul> 10 > > soup . d i v . c o n te n ts [ 2 ] 11 <ul> <l i >p r e i s : 5e</ l i ></ul> 12 > > soup . f i n d (class_=”section-header” ) . n e x t S i b l i n g . n e x t S i b l i n g 13 <ul> <l i >p r e i s : 5e</ l i ></ul>

slide-18
SLIDE 18

Python - HTML Parsing - BeautifulSoup - Navigation

1 > > f o r s t r i n g in soup . s t r i n g s : 2 p r i n t s t r i n g 3 P r e i s e 4 Zubehoer 5 p r e i s : 5e ■ soup.stripped_strings: ohne Leerzeichen ■ soup.descendants: depth-first search

slide-19
SLIDE 19

BeautifulSoup - Suchen

■ Nach Tag-Namen ■ Nach Attributen ■ Nach Text ■ Kombinationen

slide-20
SLIDE 20

Python - HTML Parsing - BeautifulSoup - Suchen - Tag

1 > > soup . h1 2 <h1 c l a s s=" s e c t i o n header ">Preise </h1> 3 > > soup . f i n d ( ’ h1 ’ ) 4 <h1 c l a s s=" s e c t i o n header ">Preise </h1> 5 > > soup . f i n d _ a l l ( ’ h1 ’ ) [ 0 ] 6 <h1 c l a s s=" s e c t i o n header ">Preise </h1>

slide-21
SLIDE 21

Python - HTML Parsing - BeautifulSoup - Suchen - Attribut

1 > > content = """<div > 2 <h1>Preise </h1> 3 <h3 id=”header”>Zubehoer </h3> 4 <ul> 5 <l i >p r e i s : 5e</ l i > 6 </ul> 7 </div >’ 8 > > soup . f i n d ( i d="header ") 9 <h3 i d="header">Zubehoer </h3> 10 > > soup . f i n d (" h3 " , i d="header ") 11 <h3 i d="header">Zubehoer </h3> 12 > > soup . f i n d ( i d=re . compile ( ’ head ’) ) 13 <h3 i d="header">Zubehoer </h3> 14 > > soup . f i n d ( i d=re . compile ( ’ head ’) ) [" i d "] 15 ’ header ’

slide-22
SLIDE 22

Python - HTML Parsing - BeautifulSoup - Suchen - Text

1 > > soup . f i n d ( t e x t=" Zubehoer " ) 2 u ’ Zubehoer ’ 3 > > soup . f i n d ( t e x t=" Zubehoer " ) . parent 4 <h3 i d=" header ">Zubehoer </h3> 5 > > soup . f i n d _ a l l ( t e x t=True ) 6 [ u ’ P r e i s e ’ , u ’ Zubehoer ’ , u ’ p r e i s : 5e ’ ] 7 > > soup . f i n d _ a l l ( t e x t=re . compile ( " [ pP ] r e i s " ) ) 8 [ u ’ P r e i s e ’ , u ’ p r e i s : 5e ’ ]

slide-23
SLIDE 23

Zwischenstand

Wir können:

■ unerkannt Content herunterladen ■ Content parsen und Information extrahieren

Spezialfall: Suchanfragen auf Websites automatisieren!

slide-24
SLIDE 24

Suchmasken - Automatisierung von Formularen

■ Suchmasken sind <form>-Objekte mit <input>-Feldern ■ Übermittlung per HTTP GET oder POST ■ Achtung: benötigt oft Cookies für Session Management! 1 Leechi ( c o o k i e s=True )

slide-25
SLIDE 25

Suchmasken - Automatisierung von Formularen

http://de.wikipedia.org/wiki/Spezial:Suche

1 <form id=" s e ar c h " method=”get” action=”/w/index.php”> 2 <input value=" S p e z i a l : Suche" name=" t i t l e " type=" hidden " /> 3 <input value=" d e f a u l t " name=" p r o f i l e " type=" hidden " /> 4 <input id=" searchText " name=”search”/> 5 <input value=" Search " name=" f u l l t e x t " type=" hidden " /> 6 <input type=" submit " value=" V o l l t e x t " /> 7 </form>

slide-26
SLIDE 26

Suchmasken - GET Request

http://de.wikipedia.org/w/index.php?title=Spezial%3ASuche&profile=default&sea

1 > > import u r l l i b 2 > > params = { " t i t l e " : " S p e z i a l : Suche" , 3 " f u l l t e x t " : " Search " , 4 " s e a r ch " : " katze " , 5 " p r o f i l e " : " Defa u l t " } 6 > > u r l l i b . u r l e n c o d e ( params ) 7 ’ p r o f i l e=De fau l t&f u l l t e x t=Search&s e ar ch=katze& t i t l e=S p e z i a l %3ASuche ’ 8 > > content = l . fetchDelayed ( " http :// de . w i k i p e d i a . org /w/ index . php?" + u r l l i b . u r l e n c o d e ( params ) )

slide-27
SLIDE 27

Suchmasken - POST Request

■ POST als Request Method ■ Parameter als separaten Wert übergeben 1 > > l . obtainHandleDelayed ( baseURL , u r l l i b . u r l e n c o d e ( params ) )

slide-28
SLIDE 28

Suchmasken - Wikipedia - Pagination

Suchergebnisse über mehrere Seiten verteilt

■ Zusätzliche Parameter

■ offset ■ limit

1 > > params = {

  • f f s e t

: " 20 , 2 l i m i t : "20" , 3 " t i t l e " : " S p e z i a l : Suche" , 4 " f u l l t e x t " : " Search " , 5 " s e a r ch " : " katze " , 6 " p r o f i l e " : " Defa u l t " }

http://de.wikipedia.org/w/index.php?title=Spezial:Suche&limit=20&offset=20&redirs=0&p

slide-29
SLIDE 29

Suchmasken - Wikipedia - Alle Links extrahieren

Beispiel: extrahiere alle Links aus Suchergebnis

1 > > while has_more_results () : 2 params [ " o f f s e t " ] = o f f s e t 3 content = l . fetchDelayed ( " http : / / . . ? " + u r l l i b . u r l e n c o d e ( params ) ) 4 soup = BeautifulSoup ( content ) 5 f o r node in soup . f i n d _ a l l ( " d i v " , c l a s s \_ ="mw search r e s u l t heading " ) : 6 r e s u l t s . append ( node . a [ " h r e f " ] ) 7

  • f f s e t += 20
slide-30
SLIDE 30

Suchmasken - has_more_results()?

Wann haben wir alle Ergebnisse gesehen?

■ Server liefert auf letzter Seite weniger Ergebnisse als limit ■ Server liefert Fehler 404, 500 bei zu großem limit ■ Server liefert doppelte Ergebnisse bei zu großem limit ■ Am Besten: Vergleich mit Anzahl Ergebnisse - nicht immer

korrekt

■ Keine allgemeingültige Formel!

slide-31
SLIDE 31

Zusammenfassung

■ Kommunikation mit Server über Leechi ■ Extraktion von Informationen aus DOM-Baum über

BeautifulSoup

■ Suchmasken: GET/POST requests mit allen Parametern,

Cookies!

■ Ende der Ergebnisliste: here be dragons!

slide-32
SLIDE 32

Werkzeugkasten

■ Python, BeautifulSoup, Leechi ■ Browser: “View Source”, DOM Inspector ■ Firefox: Web Console, Live HTTP Headers ■ Wireshark

slide-33
SLIDE 33

Wireshark

slide-34
SLIDE 34

Lessons Learned - Encodings

■ Encoding matters: Parameter passend kodieren ■ Encoding aus HTTP-Headern: charset-Parameter in

Content-Type

■ Alternative: <meta http-equiv="Content-Type" ...> ■ Achtung: "latin-1" bedeutet oft "cp1252" 1 > > params = {" query " : u" Müller ".decode(”utf-8”)} 2 > > l . fetchDelayed ( " http :// host /" , u r l l i b . u r l e n c o d e ( params ) )

slide-35
SLIDE 35

Lessons Learned - Tests

■ Umfangreichere Projekte: Unit-Test-Frameworks! 1 > > items = crawl ( " Katze " , from=" 0 1 . 0 1 . " , to=" 0 1 . 0 3 . " ) 2 > > a s s e r t ( l e n ( items ) == 84) ■ Alternative: unglücklicher Kunde, unglücklicher Entwickler

slide-36
SLIDE 36

Lessons Learned - Parsers

■ Nicht jeder Parser in BeautifulSoup4 kommt mit jeder

Tagsuppe klar 2

■ Gut, aber langsam: html5lib - BeautifulSoup(content,

“html5lib”)

■ Auch html5lib funktioniert nicht immer; lxml auch brauchbar ■ Parsing-Fehler subtil - DOM-Tree laden und wieder serialisieren ■ Tests schreiben!

2http://www.crummy.com/software/BeautifulSoup/bs4/doc/#installing-a-

parser

slide-37
SLIDE 37

Lessons Learned - Session Management

■ Session Management für einige Websites notwendig ■ Session Management per Cookie ■ Oder: Per Parameter in GET-Request - muss dann extrahiert

& übergeben werden!

■ Selten: verpflichtende, wechselnde Parameter (form input) -

muss bei jedem Seitenwechsel extrahiert werden

slide-38
SLIDE 38

Bonus: AJAX

■ Keine Daten im HTML-Source, aber im DOM-Baum ■ Dynamische Website mit AJAX ■ Content wird dynamisch nachgeladen als JSON oder XML und

DOM-Baum modifiziert

■ URL finden:

■ Javascript-Code lesen ■ Wireshark ■ Browser-Extension

slide-39
SLIDE 39

Bonus: AJAX

1 > > import j s o n 2 > > content = u r l l i b . u r lo pen ( ’ h t t p s :// a j a x . g o o g l e a p i s . com/ a j a x / s e r v i c e s / feed / load ?v=1.0&q=http ://www. digg . com/ r s s / index . xml ’ ) . read () 3 > > data = j s o n . l o a d s ( content ) 4 > > data [ " responseData " ] [ " feed " ] [ " author " ] 5 u ’ Digg ’