从Perl中的HTMl / XML标签提取文本

时间:2019-06-27 22:23:48

标签: html regex xml perl

我有这样的HTTPS响应

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
        <title>Some tittle &lt;localconfig&gt;
  &lt;key name="ssl_default"&gt;
    &lt;value&gt;sha256&lt;/value&gt;
  &lt;/key&gt;

</title>
    </head>
    <body>
        <h2>Some h2</h2>
        <p>some text:

            <pre>    text &lt;localconfig&gt;
  &lt;key name="ssl_default"&gt;
    &lt;value&gt;sha256&lt;/value&gt;
  &lt;/key&gt;
  &lt;key name="some variable"&gt;
    &lt;value&gt;1024&lt;/value&gt;
  &lt;/key&gt;
&lt;/localconfig&gt;
</pre>
        </p>
        <hr>
        <i>
            <small>Some text</small>
        </i>
        <hr/>
    </body>
</html>
  • 键的名称是静态的,我需要使用变量来获取特定值。
  • 我正在使用 decide_entities 将文本解析为html
  • 有时该键在响应中被发布两次,但是它的值是相同的。

XML::LibXML在这里没有太大帮助,因为它不是正确的XML文件/字符串。

我试图使用正则表达式来获得它

sub get_key {
    my $start = '<key name="'.$_[0].'">\n<value>';
    print $_[1];
    my $end = "</value>";
    print " [*] Trying to get $_[0]\n";
    print "Start: $start  --- End $end";
    if($_[1] =~ /\b$start\b(.*?)\b$end\b/s){
        my $result = $1;
        print $result, "\n\n";
        return $result;
    }
}

get_key("string_to_search", $string_from_response);

我需要提取键和值之间的键

<key name="variable">
 <value>Grab me</value>
</key>

2 个答案:

答案 0 :(得分:5)

提取嵌入式XML文档后,应使用适当的XML解析器。

use XML::LibXML qw( );

my $xml_doc = XML::LibXML->new->parse_string($xml);

for my $key_node ($xml_doc->findnodes("/localconfig/key")) {
   my $key = $key_node->getAttribute("name");
   my $val = $key_node->findvalue("value/text()");
   say "$key: $val";
}

因此,剩下的问题是如何提取XML文档。

选项1:XML :: LibXML

您可以使用XML :: LibXML并简单地告诉它忽略该错误(伪造的</p>标签)。

my $html_doc = XML::LibXML->new( recover => 2 )->parse_html_fh($html);
my $xml = encode_utf8( $html_doc->findvalue('/html/body/pre/text()') =~ s/^[^<]*//r );

选项2:正则表达式匹配

您可能会避免使用正则表达式模式匹配。

use HTML::Entities qw( decode_entities );

my $xml = decode_entities( ( $html =~ m{<pre>[^&]*(.*?)</pre>}s )[0] );

选项3:Mojo :: DOM

您可以使用Mojo :: DOM提取嵌入式XML文档。

use Encode    qw( decode encode_utf8 );
use Mojo::DOM qw( );

my $decoded_html = decode($encoding, $html);
my $html_doc = Mojo::DOM->new($decoded_html);    
my $xml = encode_utf8( $html_doc->at('html > body > pre')->text =~ s/^[^<]*//r );

Mojo :: DOM的问题在于,在将文档传递给解析器之前,您需要了解文档的编码(因为必须将其解码后传递),但是您需要解析文档以提取文件的编码形式。

(当然,您也可以使用Mojo :: DOM来解析XML。)


请注意,HTML片段<p><pre></pre></p>的意思是<p></p><pre></pre>,并且XML :: LibXML和Mojo :: DOM都可以正确处理此问题。

答案 1 :(得分:2)

此问题的难点在于,所呈现的文档混合了格式-它具有有效的HTML结构,但还具有类似XML的元素,这些元素看起来像“扔进去”而没有特定的模式。有一些方法可以使这些部件脱离纠结,即使它们不是防弹的且需要权衡。

在这种情况下,XML::LibXML可以完成全部工作,因为它可以处理不良数据,但要注意警告。

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

use Encode qw(encode_utf8); 
use XML::LibXML;

my $html_doc = XML::LibXML->new(recover => 2)->parse_html_fh(\*DATA);
my $xml = encode_utf8( 
    $doc->findvalue('/html/body/pre/text()') =~ s/^[^<]*//r 
);
my $xml_doc = XML::LibXML->new->parse_string($xml);

say for $xml_doc->findnodes('//key');  # node object stringifies

__DATA__
<html>
    <head> 
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
        <title>Some tittle &lt;localconfig&gt;
  &lt;key name="ssl_default"&gt;
    &lt;value&gt;sha256&lt;/value&gt;
  &lt;/key&gt;

</title>
    </head>
    <body>
        <h2>Some h2</h2>
        <p>some text:

            <pre>    text &lt;localconfig&gt;
  &lt;key name="ssl_default"&gt;
    &lt;value&gt;sha256&lt;/value&gt;
  &lt;/key&gt;
  &lt;key name="some variable"&gt;
    &lt;value&gt;1024&lt;/value&gt;
  &lt;/key&gt;
&lt;/localconfig&gt;
</pre>
        </p>
        <hr>
        <i>
            <small>Some text</small>
        </i>
        <hr/>
    </body>
</html>

parser option recover使上面的解析得以通过

  

一个真值打开恢复模式,该模式允许解析损坏的XML或HTML数据。 [...]

尽管如此有用,但由于我们故意使用不良数据(或此处不合格的数据),因此请特别谨慎。这种情况带来了两个这样的问题。

    实体需要
  • 正则表达式。该示例处理<pre>下的示例,但可能还有更多。我们需要检查输入,并可能需要更改不同数据的代码。

  • 这利用了这样的观察:类似XML的“标签”是由实体(&lt;等)给定的,它们在解析过程中保持不变,以后才解码。但是...

  • ...这不是一条规则,如果某些不是以这种方式给出(而是<key>),那么那些可以使库解析文档放入(略微)不同的树。这再次需要检查输入,并可能需要对任何新数据进行代码调整。

感谢ikegami提出了首先解析数据然后才处理实体,进行讨论以及上面的XML代码的要点。上面的与XML相关的代码的原始版本首先被解码,因此最终得到了稍微不同的树。

还要注意,HTML::TreeBuilder确实设置了ignore_unknown来处理此数据。那么问题就在于这些新的“标签”(<key>等)仅是数据,因此对所获得的树的任何实际使用都可能必须依靠正则表达式。


处理此数据的另一种方法是使用灵活的高级HTML解析器Marpa::HTML

一个非常基本演示

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

use Marpa::HTML qw(html);
use HTML::Entities qw(decode_entities);    

my $input = do { local $/; <DATA> };    
my $html = decode_entities($input);

my (@attrs, @cont);

my $marpa_key = Marpa::HTML::html( 
    \$html,
    {
        'key' => sub {
            push @attrs, Marpa::HTML::attributes();
            push @cont, Marpa::HTML::contents();
        },
    }
);

for my $i (0..$#cont) {
    say "For attribute \"name=$attrs[$i]->{name}\" the <key> has: $cont[$i]"
}

__DATA__
...the same as in the first example, data from the question...

这将使用attributescontents的API为元素<key>收集views进行解析。

它可能原则上适合您的问题,因为它仅接受<...>的语义作为元素。但是这些并没有被视为XML,如果您的数据对XML的依赖程度超过了所显示的范围,那么这可能是一个弊端。而且,当然,这是具有自己规则的另一种方法。

请注意,模块的基本逻辑和用法是每个coderef returns ,并且此返回值用于其触发的元素;其余文本保持不变。因此,这对于更改文档的特定元素很自然。

我在上面使用了不同的方式,只是为了收集有关“标签”的信息。该代码将显示

For attribute "name=ssl_default" the <key> has: 
    <value>sha256</value>

For attribute "name=some variable" the <key> has: 
    <value>1024</value>