File Coverage

blib/lib/Email/Extractor.pm
Criterion Covered Total %
statement 86 96 89.5
branch 20 32 62.5
condition 6 12 50.0
subroutine 16 17 94.1
pod 6 6 100.0
total 134 163 82.2


line stmt bran cond sub pod time code
1             package Email::Extractor;
2             $Email::Extractor::VERSION = '0.03';
3              
4             # ABSTRACT: Fast email crawler
5              
6              
7 1     1   53641 use HTML::Encoding 'encoding_from_html_document';
  1         16753  
  1         70  
8 1     1   600 use List::Compare;
  1         16677  
  1         34  
9 1     1   8 use List::Util qw(uniq);
  1         2  
  1         87  
10 1     1   412 use Email::Find;
  1         97588  
  1         122  
11 1     1   14 use Email::Valid;
  1         3  
  1         41  
12 1     1   594 use Mojo::DOM;
  1         102019  
  1         51  
13              
14 1     1   577 use Email::Extractor::Utils qw[:ALL];
  1         3  
  1         1160  
15              
16              
17             sub new {
18 1     1 1 68 my ( $class, %param ) = @_;
19 1         8 $param{ua} = LWP::UserAgent->new;
20 1 50       2875 $param{timeout} = 20 if !defined $param{timeout};
21 1         6 $param{ua}->timeout( $param{timeout} );
22 1 50       20 $param{only_lang} = 'ru' if !defined $param{only_lang};
23 1 50       4 $Email::Extractor::Utils::Verbose = 1 if $param{verbose};
24 1         7 bless {%param}, $class;
25             }
26              
27              
28             sub search_until_attempts {
29 4     4 1 3056 my ( $self, $uri, $attempts ) = @_;
30              
31 4 100       12 $attempts = 10 if !defined $attempts;
32 4         13 my $emails = $self->get_emails_from_uri($uri);
33              
34 4         22 my $links_checked = 1;
35             print "No emails found on specified url\n"
36 4 50 66     28 if ( !@$emails && $self->{verbose} );
37 4 100       19 return $emails if @$emails;
38              
39 2         6 my $urls = $self->extract_contact_links;
40              
41             print "Contact links found: " . scalar @$urls . "\n"
42 2 50 66     15 if ( @$urls && $self->{verbose} );
43 2 50 66     10 print "No contact links found\n" if ( !@$urls && $self->{verbose} );
44 2 100       12 return if !@$urls;
45              
46 1         3 for my $u (@$urls) {
47              
48 2         7 $emails = $self->get_emails_from_uri($u);
49 2         15 $links_checked++;
50 2         5 $self->{last_attempts} = $links_checked;
51 2 100       13 return $emails if @$emails;
52 1 50       6 return $emails if ( $links_checked >= $attempts );
53             }
54              
55 0         0 return $emails; # can be empty array
56              
57             }
58              
59              
60             sub get_emails_from_uri {
61 4     4   2348 my ( $self, $addr ) = @_;
62 4         10 @emails = ();
63 4         10 $self->{last_uri} = $addr;
64 4         20 my $text = load_addr_to_str($addr);
65             $self->{last_text} =
66 4         905 $text; # store html in memory to speed up further search
67 4         14 return $self->_get_emails_from_text($text);
68             }
69              
70             sub _get_emails_from_text {
71 5     5   3132 my ( $self, $text ) = @_;
72             my $finder = Email::Find->new(
73             sub {
74 5     5   92074 my ( $email, $orig_email ) = @_;
75 5         25 push @emails, $orig_email;
76             }
77 5         60 );
78 5         94 $finder->find( \$text );
79 5         7953 @emails = uniq @emails;
80              
81             # remove values that passes email validation but in fact are not emails
82             # L
83 5         18 @emails = grep { !isin( $_, $self->get_exceptions ) } @emails;
  3         12  
84              
85             # MX record checking
86             @emails =
87 5         30 grep { defined Email::Valid->address( -address => $_, -mxcheck => 1 ) }
  2         20  
88             @emails;
89              
90 5         86424 return \@emails;
91             }
92              
93              
94             sub extract_contact_links {
95 6     6   1006 my ( $self, $text ) = @_;
96              
97 6 50       17 $text = $self->{last_text} if !defined $text;
98 6 50       14 return if !defined $text;
99              
100 6         22 my $all_links = find_all_links($text);
101 6 50       1500 return if ( !@$all_links );
102              
103 6         16 $self->{last_all_links} = $all_links;
104              
105             # TO-DO: do not remove links on social networks since there can be email too
106 6 50       17 if ( $self->{last_uri} ) {
107 0         0 $all_links = remove_external_links( $all_links, $self->{last_uri} );
108 0         0 $all_links = absolutize_links_array( $all_links, $self->{last_uri} );
109             }
110              
111 6         19 $all_links = remove_query_params($all_links);
112 6         16 $all_links = drop_asset_links($all_links);
113 6         13 $all_links = drop_anchor_links($all_links);
114              
115 6         7 my @potential_contact_links;
116              
117 6 50       13 if ( $self->{only_lang} ) {
118 6         15 my $contacts_loc = $self->contacts->{ $self->{only_lang} };
119             push @potential_contact_links,
120 6         12 @{ find_links_by_text( $text, $contacts_loc, 1 ) };
  6         13  
121             }
122             else {
123 0         0 for my $c ( @{ $self->contacts } ) {
  0         0  
124 0         0 my $res = find_links_by_text( $text, $c, 1 );
125 0         0 push @potential_contact_links, @$res;
126             }
127             }
128              
129 6         368 my $grep_url_expr = join( '|', $self->url_with_contacts );
130             my @potential_contact_links_by_url =
131 6         21 grep { $_ =~ /$grep_url_expr/ } @$all_links;
  6         84  
132              
133 6         14 my @contact_links =
134             ( @potential_contact_links_by_url, @potential_contact_links );
135 6         18 @contact_links = uniq @contact_links;
136              
137             $self->{non_contact_links} =
138 6         40 List::Compare->new( $all_links, \@contact_links )->get_symdiff;
139              
140 6         701 return \@contact_links;
141              
142             }
143              
144              
145             sub contacts {
146             return {
147             'en' => 'Contacts',
148             'ru' => 'Контакты',
149             };
150             }
151              
152              
153             sub url_with_contacts {
154 6     6 1 23 return qw/
155             contact
156             contacts
157             kontaktyi
158             kontakty
159             kontakti
160             about
161             /;
162             }
163              
164              
165             sub get_exceptions {
166 3     3 1 20 return [ '!--Rating@Mail.ru' ];
167             }
168              
169              
170             sub get_encoding {
171             my ( $self, $html ) = @_;
172             my $html_to_check = $html || $self->{last_text};
173             return encoding_from_html_document($html_to_check);
174             }
175              
176              
177             sub contacts {
178             return {
179 6     6 1 19 'en' => 'Contacts',
180             'ru' => 'Контакты',
181             };
182             }
183              
184              
185             sub get_encoding {
186 0     0 1   my ( $self, $html ) = @_;
187 0   0       my $html_to_check = $html || $self->{last_text};
188 0           return encoding_from_html_document($html_to_check);
189             }
190              
191             1;
192              
193             __END__