如何在正则表达式中的下一个特定字符处停止

How to stop at the next specific character in regex

我在一个大变量中有很多 link,我正在使用正则表达式提取 link。最理想的 link 应该是

<a href="/search/product/?vendornum=StaplesA03">View Stock</a>

我的正则表达式可以完美地寻找两个匹配项:完整的 Link 和 vendornum。

/<a href="\/search\/\product/(.*?)\/.*?>(.*?)<\/a>/igm

但有时,link 会包含其他信息,例如 class,它有自己的引号

<a href="/search/title/?vendornum=StaplesA03" class="product-lister" >View Stock</a>

额外的 "s 让我失望。我想不出第一个匹配项,应该是前两个 "s

<a href="([^"]+)".*[^>].*?>View Stock</a>

我知道正则表达式可能非常具有挑战性,我正在使用 RegEx101.com,一个真正的救星。

但我似乎无法弄清楚如何匹配第一个模式,完整的 href link,但在我到达结尾之前排除任何其他 classes 与他们自己的 >

正则表达式方面的专家可以指导我吗?

如果我没看错,您想从 URL 和 link 文本中提取 vendornum 值。最好使用 html 解析器。

如果你想冒险使用可能会破坏的代码,你可以使用正则表达式来解析 html:

my $html = '<a href="/search/title/?vendornum=StaplesA03" class="product-lister" >View Stock</a>';
if($html =~ /<a href="[^\?]*\?vendornum=([^"]*)[^>]*>([^<]*).*$/) {
    print "vendornum: , link text: \n";
} else {
    print "no match";
}

输出:

vendornum: StaplesA03, link text: View Stock

解释:

  • vendornum=([^"]*) - 扫描 vendornum=,并捕获之后直到 "
  • 之前的所有内容
  • [^>]*> - 扫描剩余的属性,例如 class="",直至右尖括号
  • ([^<]*) - 捕获 link 文本
  • .*$ - 扫描到文本末尾

通常没有理由从头开始手动构建 HTML 解析器,但通常会遇到麻烦;正则表达式很挑剔,对细节很敏感,即使是微小的输入变化也很脆弱,而需求往往会发生变化。为什么不使用几个很棒的 HTML 库之一?

带有HTML::TreeBuilder的示例(也提取了links,需要在评论中说明)

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

use HTML::TreeBuilder;

my $links_string = 
q(<a href="/search/title/?vendornum=StaplesA03" class="product-lister" >View Stock</a> 
  <a href="/search/title/?vendornum=StaplesA17" >View More Stock</a> );

my $dom = HTML::TreeBuilder->new_from_content($links_string);

my @links_html;
foreach my $tag ( $dom->look_down(_tag => "a") ) { 
    push @links_html, $tag->as_HTML;  # the whole link, as is
    my $href = $tag->attr("href"); 
    my ($name, $value) = $href =~ /\?([^=]+)=([^&]+)/;   #/
    say "$name = $value";

    say $tag->as_trimmed_text;     # or: ->as_text, keep some spaces
    # Or:
    # say for $tag->content_list;  # all children, and/or text
};
#say for @links_html;

我在 links 之间使用换行符表示你的“many links in a large variable”,也许周围有一些空格以及。这不会影响库完成的解析。

一些意见

  • 这里的主力是HTML::Elementclass,它具有强大而灵活的look_down方法。如果字符串确实只有 links 那么你可能可以直接使用那个 class,但是当像上面那样完成时,一个完整的 HTML 文档也可以解析

  • 获得 URL 后,我使用一个非常简单的正则表达式来提取单个名称-值对。调整是否可以有更多对,或者让我知道。最重要的是,如果还有更多内容,请使用 URI

  • 元素子元素的 as_trimmed_text returns 文本部分,在本例中可能只是 link 的文本。 content_list returns 所有子节点(此处相同)

  • 根据 RFC 3986

    ,如果有百分比编码的字符要转换,则使用 URI::Escape

这会打印

vendornum = StaplesA03
View Stock
vendornum = StaplesA17
View More Stock

另一种选择是Mojo::DOM,它是整个生态系统的一部分

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

use Mojo::DOM;

my $links_string = q( ... );  # as above

my $dom = Mojo::DOM->new($links_string);
 
my @links_html;
foreach my $node ( $dom->find('a')->each ) { 
    push @links_html, $node->to_string;  # or $node, gets stringified to HTML
    my $href = $node->attr('href');
    my ($name, $value) = $href =~ /\?([^=]+)=([^&]+)/;   #/
    say "$name = $value";

    say $node->text;
}
#say for @links_html;

我用的方法和上面一样,打印出来的也是一样的。但请注意 Mojolicious 提供了其他方便的方法。通常,使用一系列有用的方法链接调用,使用 CSS 选择器可以轻松完成 HTML 的非常精细的导航。

虽然像上面那样循环在这里可能很有用,但作为示例我们也可以这样做

my $v = $dom -> find('a') 
    -> map( 
        sub { 
            my ($name, $value) = $_->attr('href') =~ /\?(.+?)=([^&]+)/;  
            say "$name = $value"; 
            say $_->text;
        }
    );

what 打印与上面相同。请参阅 Mojo::Collection 以更好地使用它。

URL里面的参数,如果你真的知道名字,可以用Mojo::URL解析

my $value = Mojo::URL->new($href) 
    -> query
    -> param('vendornum');

如果这些问题没有解决,那么 Mojo::Parameters 很有用

my $param_names = Mojo::Parameters
    -> new( Mojo::URL->new($href)->query ) 
    -> names

其中 $param_names 是一个包含查询中所有参数名称的数组引用,或者使用

my $pairs = Mojo::Parameters->new( Mojo::URL->new($href)->query ) -> pairs;
# Or
# my %pairs = @{ Mojo::Parameters->new(Mojo::URL->new($href)->query) -> pairs };

其中 returns 一个包含所有名称、值对的 arrayref 连续列出(例如,什么可以直接分配给散列)。


HTML 文档也可以使用 XML::LibXML 很好地解析。

首先你应该考虑使用 HTML::TreeBuilder 这样的事情。一旦掌握了它,它就会比想出正则表达式更容易。然而,对于快速而肮脏的任务,正则表达式就可以了。


$text =
'<a href="/search/title/?vendornum=StaplesA03" class="product-lister" >View Stock</a>
<a x=y href="/search/product/?Vendornum=651687" foo=bar>View Stockings</A>';

$regex =
qr{<a\s[^>]*?href="(?<link>[^"]*?\?vendornum=(?<vendornum>\w+)[^"]*)"[^>]*?>(?<desc>(?:(?!</a>).)*)</a>}i;

while($text =~ m/$regex/g){ Data:Dump::pp1 %+; }

Returns

{
  # tied Tie::Hash::NamedCapture
  desc => "View Stock",
  link => "/search/title/?vendornum=StaplesA03",
  vendornum => "StaplesA03",
}
{
  # tied Tie::Hash::NamedCapture
  desc => "View Stockings",
  link => "/search/product/?Vendornum=651687",
  vendornum => 651687,
}


HTH