root/lang/perl/tiarra/trunk/module/Auto/FetchTitle/Plugin/ExtractHeading.pm @ 11371

Revision 11371, 11.2 kB (checked in by hio, 5 years ago)

Auto::FetchTitle::Plugin::ExtractHeading?, tiarra.conf でも抽出パターンを記述できるように.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Revision Author HeadURL Id
Line 
1## ----------------------------------------------------------------------------
2#  Auto::FetchTitle::Plugin::ExtractHeading.
3# -----------------------------------------------------------------------------
4# Mastering programmed by YAMASHINA Hio
5#
6# Copyright 2008 YAMASHINA Hio
7# -----------------------------------------------------------------------------
8# $Id$
9# -----------------------------------------------------------------------------
10package Auto::FetchTitle::Plugin::ExtractHeading;
11use strict;
12use warnings;
13use base 'Auto::FetchTitle::Plugin';
14use Mask;
15
16our $DEBUG;
17*DEBUG = \$Auto::FetchTitle::DEBUG;
18
191;
20
21# -----------------------------------------------------------------------------
22# $pkg->new(\%config).
23#
24sub new
25{
26  my $pkg   = shift;
27  my $this = $pkg->SUPER::new(@_);
28
29  $this->{extra} = undef;
30  $this->_parse_extra_config();
31
32  $this;
33}
34
35# -----------------------------------------------------------------------------
36# $obj->register($context).
37#
38sub register
39{
40  my $this = shift;
41  my $context = shift;
42
43  $context->register_hook($this, {
44    name => 'extract-heading',
45    'filter.prereq'   => \&filter_prereq,
46    'filter.response' => \&filter_response,
47  });
48}
49
50# -----------------------------------------------------------------------------
51# $this->_parse_extra_config().
52# parse user config.
53#
54sub _parse_extra_config
55{
56  my $this = shift;
57  my @config;
58  $this->{extra} = \@config;
59
60  $this->notice(__PACKAGE__."#_parse_extra_config");
61  $this->notice(">> ".join(", ", map{split(' ', $_)}$this->config->extra('all')));
62
63  foreach my $token (map{split(' ', $_)}$this->config->extra('all'))
64  {
65     $this->notice("extra: $token");
66     my $name  = "extra-$token";
67     my $block = $this->config->$name;
68     if( !$block )
69     {
70       $this->notice("no such extra config: $name");
71       next;
72     }
73     if( !ref($block) )
74     {
75       my $literal = $block;
76       $block = Configuration::Block->new($name);
77       $block->extract($literal);
78     }
79     my $has_param;
80     my $config = {};
81     $config->{name} = $name;
82     $config->{url}  = $block->url;
83     if( !$config->{url} )
84     {
85       $this->notice("no url on $name");
86       next;
87     }
88     if( my $recv_limit = $block->get('recv_limit') )
89     {
90       while( $recv_limit =~ s/^\s*(\d+)\*(\d+)/$1*$2/e )
91       {
92       }
93       $config->{recv_limit} = $recv_limit;
94       $has_param = 1;
95     }
96     my @extract;
97     foreach my $line ($block->extract('all'))
98     {
99       $has_param ||= 1;
100       my $type;
101       my $value = $line;
102       if( $value =~ s/^(\w+)(:\s*|\s+)// )
103       {
104         $type = $1;
105       }
106       $type ||= 're';
107       if( $type eq 're' )
108       {
109         $value =~ s{^/(.*)/(\w*)\z/}{(?$2:$1)};
110         my $re = eval{
111           local($SIG{__DIE__}) = 'DEFAULT';
112           qr/$value/s;
113         };
114         if( my $err = $@ )
115         {
116           chomp $err;
117           $this->notice("invalid regexp $re on $name, $err");
118           next;
119         }
120         push(@extract, $re);
121       }else
122       {
123         $this->notice("unknown extract type $type on $name");
124         next;
125       }
126     }
127     if( @extract )
128     {
129       $config->{extract} = @extract==1 ? $extract[0] : \@extract;
130     }
131     if( keys %$config==1 )
132     {
133       $this->notice("no config on $name");
134       next;
135     }
136     push(@config, $config);
137  }
138
139  $this;
140}
141
142# -----------------------------------------------------------------------------
143# $this->_config().
144# config for extract-heading.
145#
146sub _config
147{
148  my $this = shift;
149  my $config = [
150    @{$this->{extra}},
151    {
152      # 1. ぷりんと楽譜.
153      url        => 'http://www.print-gakufu.com/*',
154      recv_limit => 8*1024,
155      extract    => qr{<p\s+class="topicPath">(.*?)</p>}s,
156      #remove     => qr/^ぷりんと楽譜 総合案内 > /;
157    },
158    {
159      # 2. zakzak.
160      url        => 'http://www.zakzak.co.jp/*',
161      recv_limit => 10*1024,
162      extract    => qr{<font class="kijimidashi".*?>(.*?)</font>}s,
163    },
164    {
165      # 3. nikkei.
166      url        => 'http://www.nikkei.co.jp/*',
167      recv_limit => 16*1024,
168      extract => [
169        qr{<META NAME="TITLE" CONTENT="(.*?)">}s,
170        qr{<h3 class="topNews-ttl3">(.*?)</h3>}s,
171      ],
172      remove => qr/^NIKKEI NET:/,
173    },
174    {
175      # 4a. nhkニュース.
176      url     => 'http://www*.nhk.or.jp/news/*',
177      extract => qr{<p class="newstitle">(.*?)</p>},
178    },
179    {
180      # 4b. nhk関西のニュース.
181      url        => 'http://www*.nhk.or.jp/*/lnews/*',
182      recv_limit => 8*1024,
183      extract    => qr{<h3>(.*?)</h3>},
184    },
185    {
186      # 5. creative (timeout).
187      url     => 'http://*.creative.com/*',
188      timeout => 5,
189    },
190    {
191      # 6. soundhouse news.
192      url        => 'http://www.soundhouse.co.jp/shop/News.asp?NewsNo=*',
193      recv_limit => 50*1024,
194      extract    => qr{(<td class='honbun'>\s*<font size='\+1'><b>.*?</b></font>.*?)<br>}s,
195    },
196    {
197      # 7. trac changeset.
198      url        => '*/changeset/*',
199      extract    => qr{<dd class="message" id="searchable"><p>(.*?)</p>}s,
200      recv_limit => 8*1024,
201    },
202    {
203      # 8a. amazon (page size).
204      url        => 'http://www.amazon.co.jp/*',
205      recv_limit => 15*1024,
206    },
207    {
208      # 8b. amazon (page size).
209      url        => 'http://www.amazon.com/*',
210      recv_limit => 15*1024,
211    },
212    {
213      # 9. ニコニコ動画 (メンテ画面).
214      status     => 503,
215      url        => 'http://www.nicovideo.jp/*',
216      extract    => sub{
217        if( m{<div class="mb16p4 TXT12">\s*<p>現在ニコニコ動画は(メンテナンス中)です。</p>\s*<p>(.*?)<br />}s )
218        {
219          "$1: $2";
220        }else
221        {
222          return;
223        }
224      },
225    },
226    {
227      # 10. sanspo.
228      url        => 'http://www.sanspo.com/*',
229      recv_limit => 5*1024,
230      extract    => qr{<h2>(.*?)</h2>}s,
231    },
232    {
233      # 11. sakura.
234      url        => 'http://www.sakura.ad.jp/news/archives/*',
235      recv_limit => 10*1024,
236      extract    => qr{<h3 class="newstitle">(.*?)</h3>}s,
237    },
238    {
239      # 12. viewvc.
240      url        => '*/viewcvs.cgi/*',
241      extract    => qr{<pre class="vc_log">(.*?)</pre>}s,
242    },
243    {
244      # 13. toshiba.
245      url        => 'http://www.toshiba.co.jp/about/press/*',
246      extract    => qr{<font size=\+2><b>(.*?)</b></font>}s,
247    },
248    {
249      # 14. tv-asahi.
250      url        => 'http://www.tv-asahi.co.jp/ann/news/*',
251      extract    => qr{<FONT class=TITLE>(.*?)</FONT>}s,
252    },
253    {
254      # 15. game?
255      url        => 'http://splax.net/jun.html?p=*',
256      extract    => sub{
257        my $req = shift;
258        if( $req->{url} =~ /\?p=([^&;=#]+)/ )
259        {
260          my $q = $1;
261          $q =~ s/%([0-9A-F]{2})/pack("H*",$1)/gie;
262          $q =~ s/\*([0-9A-F]{2})/pack("H*",$1)/gie;
263          $q = Unicode::Japanese->new($q, "sjis")->utf8;
264          $q =~ s/\*.*//;
265          $q = "「$qの唄」";
266        }else
267        {
268          return;
269        }
270      },
271    },
272    {
273      # 16. recordchina.
274      url        => 'http://www.recordchina.co.jp/group/*',
275      recv_limit => 12*1024,
276      extract    => qr{<div id="news_detail_title" class="ft04">(.*?)</div>}s,
277    },
278  ];
279  $config;
280}
281
282# -----------------------------------------------------------------------------
283# $this->filter_prereq($ctx, $arg).
284# (impl:fetchtitle-filter)
285# extract_heading/prereq.
286#
287sub filter_prereq
288{
289  my $this  = shift;
290  my $ctx   = shift;
291  my $arg   = shift;
292  my $req  = $arg->{req};
293
294  my $extract_list = $this->_config();
295
296  foreach my $conf (@$extract_list)
297  {
298    Mask::match($conf->{url}, $req->{url}) or next;
299    $DEBUG and $ctx->_debug($req, "debug: - $conf->{url}");
300    if( my $new_recv_limit = $conf->{recv_limit} )
301    {
302      $ctx->_apply_recv_limit($req, $new_recv_limit);
303      $DEBUG and $ctx->_debug($req, "debug: - recv_limit, $new_recv_limit");
304    }
305    if( my $new_timeout = $conf->{timeout} )
306    {
307      $ctx->_apply_timeout($req, $new_timeout);
308      $DEBUG and $ctx->_debug($req, "debug: - timeout, $new_timeout");
309    }
310  }
311}
312
313# -----------------------------------------------------------------------------
314# $this->filter_response($block, $req, $when, $type).
315# (impl:fetchtitle-filter)
316# extract_heading/response.
317#
318sub filter_response
319{
320  my $this  = shift;
321  my $ctx   = shift;
322  my $arg   = shift;
323  my $req  = $arg->{req};
324
325  my $response = $req->{response};
326  if( !ref($response) )
327  {
328    $DEBUG and $ctx->_debug($req, "debug: - - skip/not ref");
329    return;
330  }
331  my $status = $req->{result}{status_code};
332
333  my $extract_list = $this->_config();
334
335  my $heading;
336
337  foreach my $conf (@$extract_list)
338  {
339    Mask::match($conf->{url}, $req->{url}) or next;
340    $DEBUG and $ctx->_debug($req, "debug: - $conf->{url}");
341
342    my $extract_status = $conf->{status} || 200;
343    if( $status != $extract_status )
344    {
345      $DEBUG and $ctx->_debug($req, "debug: - - status:$status not match with $extract_status");
346      next;
347    }
348
349    my $extract_list = $conf->{extract};
350    if( !$extract_list )
351    {
352      $DEBUG and $ctx->_debug($req, "debug: - - no extract");
353      next;
354    }
355    if( ref($extract_list) ne 'ARRAY' )
356    {
357      $extract_list = [$extract_list];
358    }
359    foreach my $_extract (@$extract_list)
360    {
361      $DEBUG and $ctx->_debug($req, "debug: - $_extract");
362      my $extract = $_extract; # sharrow-copy.
363      $extract = ref($extract) ? $extract : qr/\Q$extract/;
364      my @match;
365      if( ref($extract) eq 'CODE' )
366      {
367        local($_) = $req->{result}{decoded_content};
368        @match = $extract->($req);
369      }else
370      {
371        @match = $req->{result}{decoded_content} =~ $extract;
372      }
373      @match or next;
374      @match==1 && !defined($match[0]) and next;
375      $heading = $match[0];
376      last;
377    }
378    defined($heading) or next;
379    $DEBUG and $ctx->_debug($req, "debug: - $heading");
380
381    $heading = $ctx->_fixup_title($heading);
382
383    my $remove_list = $conf->{remove};
384    if( ref($remove_list) ne 'ARRAY' )
385    {
386      $remove_list = [$remove_list];
387    }
388    foreach my $_remove (@$remove_list)
389    {
390      my $remove = $_remove; # sharrow-copy.
391      $remove = ref($remove) ? $remove : qr/\Q$remove/;
392      $heading =~ s/$remove//;
393    }
394  }
395
396  if( defined($heading) && $heading =~ /\S/ )
397  {
398    $heading =~ s/\s+/ /g;
399    $heading =~ s/^\s+//;
400    $heading =~ s/\s+$//;
401
402    my $title = $req->{result}{result};
403    $title = defined($title) && $title ne '' ? "$heading - $title" : $heading;
404
405    $req->{result}{result} = $title;
406  }
407}
408
409
410# -----------------------------------------------------------------------------
411# End of Module.
412# -----------------------------------------------------------------------------
413# -----------------------------------------------------------------------------
414# End of File.
415# -----------------------------------------------------------------------------
416__END__
417
418=encoding utf8
419
420=for stopwords
421        YAMASHINA
422        Hio
423        ACKNOWLEDGEMENTS
424        AnnoCPAN
425        CPAN
426        RT
427
428=begin tiarra-doc
429
430info:    本文から見出しを抽出するFetchTitleプラグイン.
431default: off
432
433# Auto::FetchTitle { ... } での設定.
434# + Auto::FetchTitle {
435#     plugins {
436#       ExtractHeading {
437#         extra: name1 name2 ...
438#         extra-name1 {
439#           url:        http://www.example.com/*
440#           recv_limit: 10*1024
441#           extract:    re:<div id="title">(.*?)</div>
442#         }
443#       }
444#    }
445#  }
446
447=end tiarra-doc
448
449=cut
450
Note: See TracBrowser for help on using the browser.