Web Scraping mit Perl

Max Maischein

Frankfurt.pm

Übersicht

  • Was ist Web Scraping?

  • Beispiele von Web Scraping

  • Überlegungen zum eigenen Scraper

Wer bin ich?

  • Max Maischein

  • DZ BANK Frankfurt

  • Deutsche Zentralgenossenschaftsbank

  • Informationsmanagement TxB

Mein Leitmotiv

Automation

  • Wenn ich es von Hand kann

  • ... kann der Computer es wiederholen

  • ... jedesmal korrekt

Meine Umgebung

DZ BANK AG

Intranet Automation und Scraping (WWW::Mechanize::Firefox)

Meine Umgebung

DZ BANK AG

Intranet Automation und Scraping (WWW::Mechanize::Firefox)

http://perlmonks.org

  • Leute fragen nach Web Scraping

  • Leute fragen nach HTML Parsern

Was ist Web Scraping?

Web Scraping ist

  • die automatisierte

  • Extraktion von Daten

  • aus Datenquellen die über HTTP bereitgestellt sind

  • und als HTML kodiert sind

  • (oder Javascript)

Was ich voraussetze

  • Perl

  • Etwas HTTP

  • Etwas HTML+CSS

Konzepte und Module

  • WWW::Mechanize::Firefox::DSL

  • Gemeinsame API

  • WWW::Scripter, WWW::Mechanize

  • Selbe Konzepte

  • Mojo::DOM, Web::Scraper, App::scrape.

Aspekte des Web Scrapings

Nicht alles, was technisch möglich ist, ist auch erlaubt oder erwünscht

  • Können wir Scraping vermeiden?

     1:    Datenbankzugriff
     2:    Datenbankdump
  • Ist Scraping erlaubt?

     1:    Nutzungsbedingungen (TOS)
     2:    Gelten die für uns?

Vorgehen

  • Welche Schritte führt ein Mensch durch

  • -> Navigation

  • -> Inhalt/Datenextraktion

  • Automatisieren

  • Wiederholen

Beispiel: Extraktion von Nachrichten

Ansicht der Seite

HTML der Seite anzeigen

 1:  #!perl -w
 2:  use WWW::Mechanize::Firefox;
 3:  my $mech = WWW::Mechanize::Firefox->new();
 4:    
 5:  $mech->get('http://conferences.yapceurope.org/gpw2011');
 6:  print $mech->content();

Etwas Vereinfachung

WWW::Mechanize::Firefox

 1:  #!perl -w
 2:  use WWW::Mechanize::Firefox;
 3:  my $mech = WWW::Mechanize::Firefox->new();
 4:    
 5:  $mech->get('http://conferences.yapceurope.org/gpw2011');
 6:  print $mech->content();

With WWW::Mechanize::Firefox::DSL

WWW::Mechanize::Firefox::DSL

 1:  #!perl -w
 2:  use WWW::Mechanize::Firefox::DSL;
 3:    
 4:        
 5:         get('http://conferences.yapceurope.org/gpw2011');
 6:  print        content();

Webseite

Webseite

Firebug

Firebug

  • Add-on für Firefox

  • Visuelles Werkzeug um Seitenelemente zu untersuchen

Unser Ziel

Nachrichten extrahieren

Erster CSS Selector

 1:    .newsbox

Verfeinerter CSS Selector

 1:    .newsbox h3 a

Verfeinerter CSS Selector

 1:    .newsbox h3 a

Verfeinerter CSS Selector

 1:    .newsbox h3 a
 2:        Call for Papers online

Verfeinerter CSS Selector

 1:    .newsbox h3 a
 2:        Call for Papers online
 3:        
 4:    .newsbox h3+p+p
 5:        Endlich ist es so weit ...

Perl script

 1:  #!perl -w
 2:  use strict;
 3:  use WWW::Mechanize::Firefox::DSL;
 4:  
 5:  get_local('html/gpw-2011-news.htm');

Perl script

 1:  #!perl -w
 2:  use strict;
 3:  use WWW::Mechanize::Firefox::DSL;
 4:  
 5:  get_local('html/gpw-2011-news.htm');
 6:  
 7:  my @headline = selector('.newsbox h3 a');
 8:  my @text = selector('.newsbox h3+p+p');

Perl script

 1:  #!perl -w
 2:  use strict;
 3:  use WWW::Mechanize::Firefox::DSL;
 4:  
 5:  get_local('html/gpw-2011-news.htm');
 6:  
 7:  my @headline = selector('.newsbox h3 a');
 8:  my @text = selector('.newsbox h3+p+p');
 9:  
10:  for my $article (0..$#headline) {
11:      print $headline[$article]->{innerHTML}, "\n";
12:      print "---", $text[$article]->{innerHTML}, "\n";
13:  };

Demo

 1:  demo/01-scrape-gpw-2011.pl

Verfeinern der Extraktion

  • Kommandozeile bequemer

  • XPath Extraktoren

  • Mojolicious::get

  • App::scrape

  • WWW::Mechanize::Firefox/examples/scrape-ff.pl

Zusammenfassung

Das Beispiel zeigte

  • Datenextraktion

  • mit CSS Selectoren

  • WWW::Mechanize + HTML::TreeBuilder::XPathEngine + HTML::XPath::Selector

  • WWW::Mechanize::Firefox

  • App::scrape

  • Mojo::DOM

Komplexere Beispielsituation (Wikipedia)

  • Bilder der (DC) Superhelden von Wikipedia extrahieren

  • Input: Name des Superhelden

  • Output: URL des Bilds (oder Bilddaten) des Superheldenbilds

  • und Beschreibung

Beispielsession

  • Heldennamen eingeben (Navigation)

  • Beschreibung auf der Ergebnisseite sehen (Extraktion)

  • Bild auf Ergebnisseite sehen (Extraktion)

Erster Wurf

 1:    use WWW::Mechanize::Firefox::DSL;
 2:    use strict;
 3:    
 4:    # Navigation
 5:    my ($hero) = @ARGV;
 6:    my $url = 'http://en.wikipedia.org';
 7:    get $url;
 8:    print content; # sind wir hier richtig?

Erster Wurf

 1:    use WWW::Mechanize::Firefox::DSL;
 2:    use strict;
 3:    
 4:    # Navigation
 5:    my ($hero) = @ARGV;
 6:    my $url = 'http://en.wikipedia.org';
 7:    get $url;
 8:    print content; # sind wir hier richtig?
 9:
10:    print title;
11:    # Wikipedia, the free encyclopedia ...

Check, wo wir sind

Wichtig, nicht gleich, aber später, wenn sich die Seite ändert

 1:    my ($hero) = @ARGV;
 2:    my $url = 'http://en.wikipedia.org';
 3:    get $url;
 4:    on_page("Wikipedia, the free encyclopedia");

Check, wo wir sind

Wichtig, nicht gleich, aber später, wenn sich die Seite ändert

 1:    my ($hero) = @ARGV;
 2:    my $url = 'http://en.wikipedia.org';
 3:    get $url;
 4:    on_page("Wikipedia, the free encyclopedia");
 5:
 6:    ...
 7:    on_page("$hero - Wikipedia");
 8:    
 9:    sub on_page {
10:        my ($expected) = @_;
11:        if (title !~ /\Q$expected\E/) {
12:            croak sprintf "Falsche Seite [%s], erwarte [%s]",
13:                title, $expected;
14:        };
15:    };

Suchfeld ausfüllen

  • Feld finden

     1:    search

Suchfeld ausfüllen

  • Feld finden

     1:    search
  • Feldnamen finden

Suchfeld ausfüllen

  • Feld finden

     1:    search
  • Feldnamen finden

 1:    submit_form( with_fields => {
 2:    	search => $hero,
 3:    });

Wo sind wir?

 1:    print title;
 2:    
 3:    # Superman - Wikipedia, the free ...

Wo sind wir?

 1:    print title;
 2:    
 3:    # Superman - Wikipedia, the free ...
 4:    
 5:    on_page($hero);

Gegenbeispiel

 1:  Plastix man

Gegenbeispiel

 1:  Plastix man
 2:
 3:  ...
 4:  Falsche Seite [Plastix man - Search results -...]

Was haben wir bisher?

  • Heldennamen eingeben (Navigation)

  • Beschreibung auf der Ergebnisseite sehen (Extraktion)

  • Bild auf Ergebnisseite sehen (Extraktion)

Beschreibung finden

  • Text Selector finden (Firebug, App::scrape, examples/scrape-ff.pl)

     1:    .infobox +p
  • Extrahieren

     1:    print $_->{innerHTML}
     2:        for selector('.infobox +p');

Bild finden

  • Bild-Selektor finden (Firebug, ...)

     1:    .infobox a.image img
  • Extrahieren

     1:    print $_->{src}
     2:        for selector('.infobox a.image img');
     3:
     4:    # http://upload.wikimedia.org/wikipedia/en/thumb/7/72/Superman.jpg/250px-Superman.jpg

Zusammenfassung

Das Beispiel zeigte

  • Navigation der Webseite

  • Datenextraktion mit CSS Selectoren

  • Seitenüberprüfung

Beispiel: Extraktion von Google+ Profilen

Schritte

  • Google+ nach dem Namen suchen

  • Information aus dem Profil extrahieren

  • Die "Leute in Circles" extrahieren

Webseite

 1:  <h2 class="a-b-D-Nd-aa d-q-p">Links</h2>
 2:  <ul class="a-b-D-G-lg Nd">
 3:  <li><img alt="" class="a-b-D-Mf" src="113231249772841733835_files/favicons_007.png">
 4:    <div class="a-b-D-k k"><a class="a-b-D-k-cg url" href="http://corion.net/" 
 5:    ...

"Related" Links finden

"Related" Links finden

 1:  div.k a.url
 2:
 3:  my @links = selector('div.k a.url');
 4:
 5:  for my $link (@links) {
 6:    print $link->{innerHTML}, "\n";
 7:    print $link->{href}, "\n";
 8:    print "\n";
 9:  };

Live Demo

 1:  04-scrape-gplus-profile.pl

Kontakte in "Circles" finden

Kontakte in "Circles" finden

Kontakte in "Circles" finden

Google setzt auf JSON / dynamisches Javascript

 1:    var OZ_initData = 
 2:    ...
 3:    ,""]
 4:    ,1,,1]
 5:    ,[[41,[["113771019406524834799","/113771019406524834799",...,"Paul Boldra"]
 6:    ,["101868469434747579306","/101868469434747579306",...,"Curtis Poe"]
 7:    ,["114091227580471410039","/114091227580471410039",...,"James theorbtwo Mastros"]
 8:    ,["116195765101222598270","/116195765101222598270",...,"Michael Kröll"]
 9:    ...

Kontakte in "Circles" finden

Google setzt auf JSON / dynamisches Javascript

 1:    var OZ_initData = 
 2:    ...
 3:    ,""]
 4:    ,1,,1]
 5:    ,[[41,[["113771019406524834799","/113771019406524834799",...,"Paul Boldra"]

Wir wollen

 1:    OZ_initData["5"][3][0]

Extraction "direkt" aus Javascript

 1:    my ($info,$type)
 2:      = eval_in_page('OZ_initData["5"][3][0]');
 3:    
 4:    my $items = $info->[0];
 5:    print "$items Nutzer in Circles\n";
 6:    for my $i (@{ $info->[1] }) {
 7:        print $i->[0], "\t", $i->[3],"\n";
 8:    };

Live Demo

 1:  demo/05-scrape-gplus-circle.pl

Zusammenfassung

Dieses Beispiel zeigte

  • Seiten/Anwendungsnavigation

  • Datenextraktion

  • aus JSON / Javascript Strukturen

  • WWW::Mechanize::Firefox

  • Vielleicht auch WWW::Scripter

Ein Eigener Scraper

  • Du willst es doch auch!

  • Seiten Navigieren

  • Extraktion beschreiben

  • Daten oder (Perl) Code?

Ein Eigener Scraper

Andere Scraper anschauen und benutzen

  • Konfiguration

  • Navigation

  • Extraktion

  • Geschwindigkeit

  • Externe Unterstützung (Flash, Java, Javascript, ...)

Performance verbessern (Latenz)

  • Der Flaschenhals sind Latenz und Netzwerktransfer

  • Navigation vermeiden

     1:    http://en.wikipedia.org/wiki/Superman
     2:    http://en.wikipedia.org/wiki/$super_hero
  • Funktioniert nicht für Google+

     1:    https://plus.google.com/v/xXxXxXx

Performance verbessern (Durchsatz)

  • Nicht den Server überlasten

  • Durchsatz erhöhen mit mehr als einem Scraper (AnyEvent::HTTP)

  • Durchsatz erhöhen mit mehr als einem Scraper (threads)

  • Nicht den Server überlasten

Webseiten, die kein Scraping mögen

On the internet, nobody knows that you're a dog(strike)Perl script

  • Browser automatisieren

    or

  • Dieselben Daten senden

Webseiten, die kein Scraping mögen

Browser automatisieren

  • WWW::Mechanize::Firefox , PhantomJS

  • Visuell

  • Brauchen VNC Display oder X Terminal

  • Portable Firefox Installationen

Webseiten, die kein Scraping mögen

Wireshark / Live HTTP Headers

  • Nachahmen, was der Browser sendet

     1:  GET /-8RekFwZY8ug/AAAAAAAAAAI/AAAAAAAAAAA/7gmV6WFSVG8/photo.jpg?sz=80 HTTP/1.1
     2:  Host: lh5.googleusercontent.com
     3:  User-Agent: Mozilla/5.0 (Windows; U; ...

Webseiten, die kein Scraping mögen

Wireshark / Live HTTP Headers

  • Nachahmen, was der Browser sendet

     1:  GET /-8RekFwZY8ug/AAAAAAAAAAI/AAAAAAAAAAA/7gmV6WFSVG8/photo.jpg?sz=80 HTTP/1.1
     2:  Host: lh5.googleusercontent.com
     3:  User-Agent: Mozilla/5.0 (Windows; U; ...
  • Unterschiede eliminieren

     1:    ->user_agent()
     2:    ->cookies()
     3:
     4:    Accept-Encoding
     5:    ...

Zusammenfassung

  • Viele Frameworks mit unterschiedlichen Tradeoffs

  • Das Netzwerk versteckt das Programm

  • Browser automatisieren

  • Netzwerktraffic nachahmen

Beispielcode

Der Beispielcode ist online unter

https://github.com/corion/webscraping-workshop

Danke

Der Beispielcode ist online unter

https://github.com/corion/webscraping-workshop

Fragen?

Max Maischein (corion@cpan.org)

"Cognitive Hazard" von Anders Sandberg

Bilder der Superhelden von Wikipedia

Bonus Section

Bonus Section

Extraktionsabfragen

HTML::Selector::XPath - konvertiert von CSS(3) nach XPath

pQuery - jQuery-ähnliche extraction

HTML::JQuery - jQuery-ähnliche Extraktion

 1:    $dom->q('p')->q('div')->...

Mojo::DOM - AUTOLOAD jQuery-ähnliche Extraktion

 1:    $dom->p->div->(...)

Web::Scraper - spezielle DSL

 1:    scrape { title => ...,
 2:             'url[]' => scrape { ... }
 3:           }

Relevante Module

Relevante Module

Relevante Module

Relevante Module

Relevante Module

Relevante Module

Relevante Module

Relevante Module

Relevante Module

Relevante Module

Relevante Module

Relevante Module

Relevante Module

Relevante Module

Relevante Module

Der eigene Scraper

Die Arbeit der anderen

Tatsuhiko Miyagawa

  • HTML::Selector::XPath - für CSS3 Selektoren

  • HTML::AutoPagerize - mehrseitige Suchergebnisse