Sunday, September 09, 2007

Web::Scraper - ein echt nützliches Modul

Auf der YAPC::Europe hat Tatsuhiko Miyagawa sein Modul Web::Scraper vorgestellt. Damit soll man ziemlich einfach an Informationen von HTML-Seiten kommen. Jetzt bin ich gerade dabei, ein paar Module zu ändern - von der Verwendung von Regulären Ausdrücken und HTML::Parser hin zu Web::Scraper. Ich bin echt begeistert von dem Modul, da es viel Arbeit abnimmt. Und alte Module, die noch RegEx verwenden, müssen häufiger gewartet werden, weil sich die HTML-Struktur etwas ändert...

Ein kleines Beispiel:

HTML (wegen einem Fehler bei Blogger.com muss ich Leerzeichen bei den Tags einfügen):
< html >
< head >
< name="description" content="Beschreibung">
< /head >
< body >
< h1 >Test< /h1 >
< /body >
< /html >

Wenn hieraus die Beschreibung gefiltert werden soll, dann kann man das mit Regulären Ausdrücken, mit HTML::Parser und Web::Scraper lösen. In den folgenden Skripten lasse ich den HTML-Teil in der __DATA__-Sektion weg...

regex.pl
#!/usr/bin/perl

use strict;
use warnings;

my $content = do{ local $/; };
my ($description) = $content =~ /< meta \s+
name="description" \s+
content="(.*?)"/x;
print $description;

html_parser.pl
#!/usr/bin/perl

use strict;
use warnings;
use HTML::Parser;

my $content = do{ local $/; };
my $description = "";
my $parser = HTML::Parser->new();

$parser->handler( start => \&_start, "tagname,attr" );
$parser->parse( $content );

print $description;

sub _start{
my ($tag,$attr) = @_;
return unless $tag eq 'meta';
return unless $attr->{name} eq 'description';
$description = $attr->{content};
}

web_scraper.pl
#!/usr/bin/perl

use strict;
use warnings;
use Web::Scraper;

my $content = do{ local $/; };
my $parser = scraper {
process 'meta[name="description"]', description => '@content';
};
my $result = $parser->scrape( $content );
print $result->{description};

Bei der RegEx-Lösung ist die Wartbarkeit eher schlecht. Denn sobald etwas an der Reihenfolge der Attribute geändert wird, muss das Skript angepasst werden - und dieses Beispiel ist sehr einfach. Bei komplexeren RegEx geht sehr schnell etwas schief.

Die HTML::Parser-Lösung ist schon einiges sauberer, aber eher umständlich und lang. Hier ist die Wartbarkeit durch globale Variablen und die "Länge" etwas eingeschränkt. Soll nicht nur 1 Tag gesucht werden sondern viele verschiedene, werden die Handler auch dementsprechend komplex.

Mit Web::Scraper wird es recht übersichtlich. Und wenn man die Syntax einmal drin hat, dann ist es auch ziemlich einfach und schnell. Man muss sich halt mal die XPath-Syntax anschauen.
Es ist auf jeden Fall ein lohnenswertes Modul!

3 comments:

Anonymous said...

Eine weitere Möglichkeit ist es, das HTML-Dokument als XML mit XML::Simple zu verarbeiten.

ReneeB said...

Ich kann da jetzt aber keinen Vorteil gegenüber Web::Scraper erkennen. Im Gegenteil: Wenn das meta-Tag nicht geschlossen wird, bekomme ich eine Fehlermeldung und das Heraussuchen der Information ist auch eher schlecht.

Vielleicht hast Du ein Beispiel wie es einfach wäre...

Ich habe es zum Test mal so gemacht:

==
#!/usr/bin/perl

use strict;
use warnings;
use XML::Simple;
use Data::Dumper;

my $data = do{ local $/; < DATA > };
my $ref = XMLin( $data );
print Dumper $ref;

__DATA__
# Hier das Beispiel HTML
==

In diesem Fall wäre es noch relativ einfach, weil ich einfach

print $ref->{head}->{meta}->{content}

machen kann. Aber was wenn das HTML komplizierter wird? Was wenn ich in Abhängigkeit von CSS-Klassen etwas machen will?

Dann muss ich wohl oder übel selbst mit if's etc. anfangen.

Diese Arbeit nimmt mir Web::Scraper ab. Aber wenn Du ein gutes Beispiel hast, lasse ich mich gerne belehren.

Anonymous said...

Great post on web scrapers, with some well thought out points, I use python for simple web scrapers, data extraction can be a time consuming process but for other projects that include documents, files, or the web i tried http://www.extractingdata.com/web%20scraper.htm which worked great, they build quick custom screen scrapers, web scrapers, and data parsing programs