File Coverage

blib/lib/App/scrape.pm
Criterion Covered Total %
statement 70 70 100.0
branch 18 18 100.0
condition 4 10 40.0
subroutine 7 7 100.0
pod 0 1 0.0
total 99 106 93.4


line stmt bran cond sub pod time code
1             package App::scrape;
2 5     5   86136 use strict;
  5         12  
  5         129  
3 5     5   4179 use URI;
  5         34640  
  5         146  
4 5     5   4681 use HTML::TreeBuilder::XPath;
  5         358637  
  5         53  
5 5     5   4351 use HTML::Selector::XPath 'selector_to_xpath';
  5         13137  
  5         304  
6 5     5   31 use Exporter 'import';
  5         10  
  5         141  
7              
8 5     5   26 use vars qw($VERSION @EXPORT_OK);
  5         9  
  5         3392  
9             $VERSION = '0.05';
10              
11             @EXPORT_OK = qw;
12              
13             =head1 NAME
14              
15             App::scrape - simple HTML scraping
16              
17             =head1 ABSTRACT
18              
19             This is a simple module to extract data from HTML by
20             specifying CSS3 or XPath selectors.
21              
22             =head1 SYNOPSIS
23              
24             use App::scrape 'scrape';
25             use LWP::Simple 'get';
26             use Data::Dumper;
27            
28             my $html = get('http://perlmonks.org');
29             my @posts = scrape(
30             $html,
31             ['a','a@href'],
32             {
33             absolute => [qw[href src rel]],
34             base => 'http://perlmonks.org',
35             },
36             );
37             print Dumper \@posts;
38              
39             my @posts = scrape(
40             $html,
41             {
42             title => 'a',
43             url => 'a@href',
44             },
45             {
46             absolute => [qw[href src rel]],
47             base => 'http://perlmonks.org',
48             },
49             );
50             print Dumper \@posts;
51              
52             =head1 DESCRIPTION
53              
54             This module implements yet another scraping engine
55             to extract data from HTML.
56              
57             This engine does not (yet) support nested data
58             structures. For an engine that supports nesting, see
59             L.
60              
61             =cut
62              
63             sub scrape {
64 6     6 0 8864 my ($html, $selectors, $options) = @_;
65            
66 6   50     22 $options ||= {};
67 6         8 my $delete_tree;
68 6 100       23 if (! ref $options->{tree}) {
69 4         40 $options->{tree} = HTML::TreeBuilder::XPath->new;
70 4         1181 $options->{tree}->parse($html);
71 4         6220 $options->{tree}->eof;
72 4         648 $delete_tree = 1;
73             };
74 6         15 my $tree = $options->{tree};
75            
76 6   50     43 $options->{make_uri} ||= {};
77 6         9 my %make_uri = %{$options->{make_uri}};
  6         21  
78              
79             # now fetch all "rows" from the page. We do this once to avoid
80             # fetching a page multiple times
81 6         10 my @rows;
82              
83 6         19 my %known_uri = (
84             'href' => 1, # a@href
85             'src' => 1, # img@src , script@src
86             );
87              
88 6         10 my @selectors;
89 6 100       21 if (ref $selectors eq 'ARRAY') {
90 4         10 @selectors = @$selectors
91             } else {
92 2         9 @selectors = map { $selectors->{ $_ } } sort keys %$selectors;
  2         6  
93             };
94              
95 6         12 my $rowidx=0;
96 6         11 for my $selector (@selectors) {
97 6         9 my ($attr);
98 6         9 my $s = $selector;
99 6 100       33 if ($selector =~ s!/?\@(\w+)$!!) {
100 3         10 $attr = $1;
101             };
102 6 100       24 if ($selector !~ m!^\.?/!) {
103 5         24 $selector = selector_to_xpath( $selector );
104             };
105             # We always make the selector relative to the current node:
106 6 100       417 $selector = ".$selector" unless $selector =~ /^\./;
107 6         12 my @nodes;
108 6 100       17 if (! defined $attr) {
109 3         25 @nodes = map { $_->as_trimmed_text } $tree->findnodes($selector);
  10         6094  
110             } else {
111 3   33     39 $make_uri{ $rowidx } ||= (($known_uri{ lc $attr }) and ! $options->{no_known_uri});
      33        
112 3         31 @nodes = $tree->findvalues("$selector/\@$attr");
113             };
114 6 100       5514 if ($make_uri{ $rowidx }) {
115 3         23 @nodes = map { URI->new_abs( $_, $options->{base} )->as_string } @nodes;
  7         19485  
116             };
117 6         9758 push @rows, \@nodes;
118 6         32 $rowidx++;
119             };
120            
121             # Now convert the result from rows to columns
122 6         11 my @result;
123 6         11 for my $idx (0.. $#{ $rows[0] }) {
  6         25  
124             push @result, [ map {
125 14         28 $rows[$_]->[$idx]
  18         54  
126             } 0..$#rows ];
127             };
128            
129             # Now check what the user wants, array or hash:
130 6 100       26 if( ref $selectors eq 'HASH') {
131             @result = map {
132 2         4 my $arr = $_;
  4         5  
133 4         5 my $i = 0;
134             $_ = +{
135 4         11 map { $_ => $arr->[$i++] } sort keys %$selectors
  8         27  
136             };
137             } @result
138             };
139              
140 6 100       38 $tree->delete
141             if $delete_tree;
142             @result
143 6         495 };
144              
145             =head1 SEE ALSO
146              
147             L - the scraper inspiring this module
148              
149             =head1 REPOSITORY
150              
151             The public repository of this module is
152             L.
153              
154             =head1 SUPPORT
155              
156             The public support forum of this program is
157             L.
158              
159             =head1 AUTHOR
160              
161             Max Maischein C
162              
163             =head1 COPYRIGHT (c)
164              
165             Copyright 2011-2011 by Max Maischein C.
166              
167             =head1 LICENSE
168              
169             This module is released under the same terms as Perl itself.
170              
171             =cut