【问题标题】:How can I remove external links from HTML using Perl?如何使用 Perl 从 HTML 中删除外部链接?
【发布时间】:2010-12-08 13:21:18
【问题描述】:

我正在尝试从 HTML 文档中删除外部链接,但保留锚点,但我运气不佳。以下正则表达式

$html =~ s/<a href=".+?\.htm">(.+?)<\/a>/$1/sig;

将匹配锚标记的开头和外部链接标记的结尾,例如

<a HREF="#FN1" name="01">1</a>
some other html
<a href="155.htm">No. 155
</a> <!-- end tag not necessarily on the same line -->

所以我最终什么都没有,而不是

<a HREF="#FN1" name="01">1</a>
some other html

碰巧所有锚的 href 属性都是大写的,所以我知道我可以进行区分大小写的匹配,但我不想依赖它在未来总是如此。

我可以改变什么,让它只匹配a标签吗?

【问题讨论】:

  • 哦,每当我看到另一个“如何使用正则表达式解析 HTML?”时,我的大脑是多么的痛。题。在继续之前查看stackoverflow.com/questions/701166/…stackoverflow.com/questions/773340/…(以及stackoverflow.com/questions/487213/…)。
  • 在一般情况下,是的,正则表达式并不是真正为解析 XML/HTML 而设计的。也就是说,如果问题空间有限,它可能是一个可行的选择。
  • 这里有 Mark Jason Dominus 的一篇很棒的文章:perl.plover.com/yak/12views/samples/notes.html “我们不要忘记 Perl 的优点。它擅长与其他程序交互,它有利于快速原型设计。让我们当人们按照设计使用 Perl 的方式使用 Perl 时,不会给他们带来麻烦。”
  • @Kinopiko 你不能忽视 MJD 的讨论。另外,MJD 并没有为会给出错误结果的方法辩护。另外,关于调用外部命令,我完全同意 MJD。现在,这个页面上反对用正则表达式解析 HTML 的人已经提出了合理、深思熟虑的论点,向您展示了该方法是多么容易失败。你碰巧忽略了所有这些论点,声称这是教条。那好吧。 books.google.com/…
  • @Kinopiko,香烟会致癌。人类活动在气候变化中发挥着重要作用。人和猿有共同的祖先。许多人声称这些事实只是教条。但这并不能改变它们是真实的事实。用正则表达式解析 HTML 是个坏主意。有时越差越好,但在这种情况下,依赖正则表达式是自找麻烦——哎呀,我们有一个指向 .php 文件的链接,哎呀,这是一个指向 .cgi 的链接,ad naseum——所以正则表达式增长了,越来越笨拙,而且总是破碎。真正的解析器方法更容易编写(正确的代码)、更容易维护和更容易理解。

标签: html perl


【解决方案1】:

呼应 Chris Lutz 的评论,我希望以下内容表明使用解析器非常简单(特别是如果您希望能够处理您尚未见过的输入,例如 &lt;a class="external" href="..."&gt;),而不是放在一起使用s/// 的脆弱解决方案。

如果您打算采用s/// 路线,至少老实说,一定要依赖href 属性全部大写,而不是假装灵活。

编辑:应大众需求 ;-),这里是使用 HTML::TokeParser::Simple 的版本。仅使用 HTML::TokeParser 查看版本的编辑历史记录。

#!/usr/bin/perl

use strict; use warnings;
use HTML::TokeParser::Simple;

my $parser = HTML::TokeParser::Simple->new(\*DATA);

while ( my $token = $parser->get_token ) {
    if ($token->is_start_tag('a')) {
        my $href = $token->get_attr('href');
        if (defined $href and $href !~ /^#/) {
            print $parser->get_trimmed_text('/a');
            $parser->get_token; # discard </a>
            next;
        }
    }
    print $token->as_is;
}

__DATA__
<a HREF="#FN1" name="01">1</a>
some other html
<a href="155.htm">No. 155
</a> <!-- end tag not necessarily on the same line -->
<a class="external" href="http://example.com">An example you
might not have considered</a>

<p>Maybe you did not consider <a
href="test.html">click here >>></a>
either</p>

输出:

C:\Temp> hjk
<a HREF="#FN1" name="01">1</a>
some other html
No. 155 <!-- end tag not necessarily on the same line -->
An example you might not have considered

<p>Maybe you did not consider click here >>>
either</p>

注意:如果链接到的文件具有.html 扩展名而不是.htm,则您检查为“正确”的基于正则表达式的解决方案会中断。鉴于此,我发现您担心不依赖大写 HREF 属性是没有根据的。 如果你真的想要又快又脏,你不应该为其他任何事情而烦恼,你应该应该依靠全部大写的HREF 并完成它。但是,如果您想确保您的代码能够处理更多种类的文档并使用更长时间,那么您应该使用适当的解析器。

【讨论】:

  • +1 他可能没有考虑过的例子。这就是我在这种情况下应该是反对正则表达式的论点。
  • @Kinopiko:1. 它正确,不像您的解决方案,它在任何情况下都会中断。 2. 代码应该能够被胜任的人阅读。参考文献并不是进入的陡峭障碍。对于初学者来说,完全理解引用比完全理解正则表达式要容易得多。 3. 我更喜欢HTML::TokeParser::Simple,因为它的界面更易读,但如果你不能花一点时间看文档,那你又失败了。
  • 还有 4. 使用模块是因为再次这不是一个小问题。如果你把它当作一个微不足道的问题,你会得到一个错误的解决方案,就像原始发帖人和你的一样。适合该任务的模块几乎可以保证less bug。
  • 我同意神奇的数字令人困惑。这是 HTML::TokeParser 的属性,而不是一般的“不使用正则表达式解析”。使用 XML::LibXML 的 W3C DOM 实现会更清晰,但更冗长。
  • 谁在乎 CPAN 模块经常做什么?只关心您需要的 CPAN 模块做什么。
【解决方案2】:

HTML::Parser 有点像 SAX 类型解析器:

use strict;
use warnings;

use English qw<$OS_ERROR>;
use HTML::Parser;
use List::Util qw<first>;

my $omitted;

sub tag_handler { 
    my ( $self, $tag_name, $text, $attr_hashref ) = @_;
    if ( $tag_name eq 'a' ) { 
        my $href = first {; defined } @$attr_hashref{ qw<href HREF> };
        $omitted = substr( $href, 0, 7 ) eq 'http://';
        return if $omitted;
    }
    print $text;
}

sub end_handler { 
    my $tag_name = shift;
    if ( $tag_name eq 'a' && $omitted ) { 
        $omitted = false;
        return;
    }
    print shift;
}

my $parser
    = HTML::Parser->new( api_version => 3
                       , default_h   => [ sub { print shift; }, 'text' ]
                       , start_h     => [ \&tag_handler, 'self,tagname,text,attr' ]
                       , end_h       => [ \&end_handler, 'tagname,text' ]
                       );
$parser->parse_file( $path_to_file ) or die $OS_ERROR;

【讨论】:

  • +1 顺便说一句,请参阅 perlfoundation.org/perl5/… 上的 Smart::Comments。我不确定我是否有这种强烈的感觉,但我一般不喜欢源过滤器。
  • @Sinan Ünür:通常我会从完成的答案中删除调试代码。 Smart::Comments 的亮点在于调试代码。
  • 这还不错,但最终确实依赖于另一个正则表达式。但是,如果你问得好,HTML::Parser 会给你属性和它们的值。
  • @Manni: 同意——我知道它是这样做的,但我不想在更改标签时写一个复杂的管道——但如果我这样做的话,它会是一个更好的解决方案'没有写出任何东西。我要改了。
【解决方案3】:

另一种解决方案。我爱HTML::TreeBuilder 和家人。

#!/usr/bin/perl
use strict;
use warnings;
use HTML::TreeBuilder;

my $root = HTML::TreeBuilder->new_from_file(\*DATA);
foreach my $a ($root->find_by_tag_name('a')) {
    if ($a->attr('href') !~ /^#/) {
        $a->replace_with_content($a->as_text);
    }
}
print $root->as_HTML(undef, "\t");

__DATA__
<a HREF="#FN1" name="01">1</a>
some other html
<a href="155.htm">No. 155
</a> <!-- end tag not necessarily on the same line -->
<a class="external" href="http://example.com">An example you
might not have considered</a>

<p>Maybe you did not consider <a
href="test.html">click here >>></a>
either</p>

【讨论】:

  • TreeBuilder 和朋友的文档本来可以多一点...一起,但我同意,一旦你习惯了这是使用 HTML 的好方法给它。
【解决方案4】:

为什么不只删除 href 属性不以井号开头的链接呢?像这样的:

html =~ s/<a href="[^#][^"]*?">(.+?)<\/a>/$1/sig;

【讨论】:

  • 不处理裸链接——我知道,裸链接很恶心,你永远不会在我编写或编写生成器的 HTML 中找到它们,但它们和单引号属性符合规格。
【解决方案5】:

更简单,如果你不关心标签属性:

$html =~ s/<a[^>]+>(.+?)<\/a>/$1/sig;

【讨论】:

    猜你喜欢
    • 2017-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-27
    • 1970-01-01
    • 2022-11-20
    • 1970-01-01
    相关资源
    最近更新 更多