使用 Perl 在 HTML 中查找 Favicon

Find Favicons in HTML using Perl

我正在尝试使用 Perl 寻找给定 URL 的网站图标(和变体)(我想避免使用外部服务,例如 Google 的网站图标查找器) .有一个 CPAN 模块,WWW::Favicon,但它已经十多年没有更新了——在这十年中,“apple-touch-icon”等重要变体已经取代了古老的“ico”文件.

我想我在 WWW::Mechanize 中找到了解决方案,因为它可以列出给定 URL 中的所有链接,包括 <link> header 标签。但是,我似乎无法找到一种干净的方法来使用“find_link”方法来搜索 rel 属性。

例如,我尝试使用 'rel' 作为搜索词,希望它可能在其中,尽管文档中没有提及,但它不起作用。此代码 returns 关于无效“link-finding 参数的错误。”

my $results = $mech->find_link( 'rel' => "apple-touch-icon" );
use Data::Dumper;
say STDERR Dumper $results;

我也尝试使用其他 link-finding 参数,但其中 none 似乎适合搜索 rel 属性。

我能弄清楚如何做到这一点的唯一方法是遍历所有链接并查找这样的 rel 属性:

my $results = $mech->find_all_links(  );

foreach my $result (@{ $results }) {
    my $attrs = $result->attrs();
    #'tag' => "apple-touch-icon"
    
    foreach my $attr (sort keys %{ $attrs }) {
        if ($attrs->{'rel'} =~ /^apple-touch-icon.*$/) {
            say STDERR "I found it:" . $result->url();
        }

        # Add tests for other types of icons here.
        # E.g. "mask-icon" and "shortcut icon."

    }

}

可行,但看起来很乱。有没有更好的方法?

问题很容易解决:

  • 任何允许加载网页的模块的协助
  • 为所有可能的 favicon 变体定义 $regex
  • 寻找<link rel="$regex" href="icon_address" ...>

注意: 该脚本在代码中嵌入了默认 YouTube url

use strict;
use warnings;
use feature 'say';

use HTTP::Tiny;

my $url = shift || 'https://www.youtube.com/';

my $icons = get_favicon($url);

say for @{$icons};

sub get_favicon {
    my $url = shift;
    
    my @lookup = (
                    'shortcut icon',
                    'apple-touch-icon',
                    'image_src',
                    'icon',
                    'alternative icon'
                );
                
    my $re      = join('|',@lookup);
    my $html    = load_page($url);
    my @icons   = ($html =~ /<link rel="(?:$re)" href="(.*?)"/gmsi);
    
    return \@icons;
}

sub load_page {
    my $url = shift;
    
    my $response = HTTP::Tiny->new->get($url);
    my $html;

    if ($response->{success}) {
        $html = $response->{content};
    } else {
        say 'ERROR:  Could not extract webpage';
        say 'Status: ' . $response->{status};
        say 'Reason: ' . $response->{reason};
        exit;
    }

    return $html;
}

运行 作为 script.pl

https://www.youtube.com/s/desktop/8259e7c9/img/favicon.ico
https://www.youtube.com/s/desktop/8259e7c9/img/favicon_32.png
https://www.youtube.com/s/desktop/8259e7c9/img/favicon_48.png
https://www.youtube.com/s/desktop/8259e7c9/img/favicon_96.png
https://www.youtube.com/s/desktop/8259e7c9/img/favicon_144.png
https://www.youtube.com/img/desktop/yt_1200.png

运行 作为 script.pl "http://www.microsoft.com/"

https://c.s-microsoft.com/favicon.ico?v2

运行 作为 script.pl "http://finance.yahoo.com/"

https://s.yimg.com/cv/apiv2/default/icons/favicon_y19_32x32_custom.svg

这是我使用 Mojo::DOM 的方法。获取 HTML 页面后,使用 dom 进行所有解析。从那里,使用 CSS 选择器找到有趣的节点:

link[rel*=icon i][href]

此 CSS 选择器查找同时具有 relhref 标签的 link 标签。此外,我要求 rel 中的值包含 (*=)“图标”,不区分大小写(i)。如果你想假设所有节点都将有 href,只需离开 [href].

获得链接列表后,我只提取 href 中的值并将该列表转换为数组引用(尽管我可以使用 Mojo::Collection 方法完成其余部分):

use v5.10;

use Mojo::UserAgent;
my $ua = Mojo::UserAgent->new->max_redirects(3);

my $results = $ua->get( shift )
    ->result
    ->dom
    ->find( 'link[rel*=icon i][href]' )
    ->map( attr => 'href' )
    ->to_array
    ;

say join "\n", @$results;

到目前为止效果很好:

$ perl mojo.pl https://www.perl.org
https://cdn.perl.org/perlweb/favicon.ico

$ perl mojo.pl https://www.microsoft.com
https://c.s-microsoft.com/favicon.ico?v2

$ perl mojo.pl https://leanpub.com/mojo_web_clients
https://d3g6anj9jkury9.cloudfront.net/assets/favicons/apple-touch-icon-57x57-b83f183ad6b00aa74d8e692126c7017e.png
https://d3g6anj9jkury9.cloudfront.net/assets/favicons/apple-touch-icon-60x60-6dc1c10b7145a2f1156af5b798565268.png
https://d3g6anj9jkury9.cloudfront.net/assets/favicons/apple-touch-icon-72x72-5037b667b6f7a8d5ba8c4ffb4a62ec2d.png
https://d3g6anj9jkury9.cloudfront.net/assets/favicons/apple-touch-icon-76x76-57860ca8a817754d2861e8d0ef943b23.png
https://d3g6anj9jkury9.cloudfront.net/assets/favicons/apple-touch-icon-114x114-27f9c42684f2a77945643b35b28df6e3.png
https://d3g6anj9jkury9.cloudfront.net/assets/favicons/apple-touch-icon-120x120-3819f03d1bad1584719af0212396a6fc.png
https://d3g6anj9jkury9.cloudfront.net/assets/favicons/apple-touch-icon-144x144-a79479b4595dc7ca2f3e6f5b962d16fd.png
https://d3g6anj9jkury9.cloudfront.net/assets/favicons/apple-touch-icon-152x152-aafe015ef1c22234133158a89b29daf5.png
https://d3g6anj9jkury9.cloudfront.net/assets/favicons/favicon-16x16-c1207cd2f3a20fd50de0e585b4b307a3.png
https://d3g6anj9jkury9.cloudfront.net/assets/favicons/favicon-32x32-e9b1d6ef3d96ed8918c54316cdea011f.png
https://d3g6anj9jkury9.cloudfront.net/assets/favicons/favicon-96x96-842fcd3e7786576fc20d38bbf94837fc.png
https://d3g6anj9jkury9.cloudfront.net/assets/favicons/favicon-128x128-e97066b91cc21b104c63bc7530ff819f.png
https://d3g6anj9jkury9.cloudfront.net/assets/favicons/favicon-196x196-b8cab44cf725c4fa0aafdbd237cdc4ed.png

现在,如果您发现无法轻松为其编写选择器的更多有趣案例,问题就来了。假设并非所有 rel 值中都有“图标”。您可以通过指定多个用逗号分隔的选择器来获得更多乐趣,这样您就不必使用实验性的大小写不敏感标志:

link[rel*=icon][href], link[rel*=ICON][href]

rel 中的不同值:

link[rel="shortcut icon"][href], link[rel="apple-touch-icon-precomposed"][href]

想排多少就排多少。

但是,您也可以在没有选择器的情况下过滤结果。使用Mojo::Collection的grep选择你想要的节点:

my %Interesting = ...;
my $results = $ua->get( shift )
    ->result
    ->dom
    ->find( '...' )
    ->grep( sub { exists $Interesting{ $_->attr('rel') } } )
    ->map( attr => 'href' )
    ->to_array
    ;

我在 Mojo Web Clients 中还有很多 Mojo::DOM 的例子,我想我现在就去添加这个例子。