无法用Mojolicious提取链接

时间:2014-08-30 00:53:29

标签: perl web-scraping mojolicious

我正在尝试使用Mojo :: DOM提取搜索结果页面中下一页的链接。但是,我遇到的问题是,在现有元素上使用->find()后,我得到一个字符串而不是Mojo :: DOM元素。

我有:

my $pagination_elements = $dom->find("div[class*=\"pagination-block\"]");
my $page_counter_text = $pagination_elements->find("div[class=\"page-of-pages\"]")->text();

$page_counter_text =~ /^Page (\d+) of (\d+)$/;
my $current_page = int($1);
my $last_page = int($2);

my $prev_next_elements = $pagination_elements->find("a[class*=\"prev-next\"]");
my $next_page_link = $prev_next_elements->last();
my $next_page_url = $next_page_link->attr("href");

在每个页面上,可能有2个链接标记,其类别为prev-next。我得到的不是获取最后一个元素的链接,而是包含两个标记的href的字符串(如果两者都在页面上可用)。

现在,如果不是这样,我会这样做:

my $next_page_link = $dom->find("div[class*=\"pagination-block\"] > ul > li > a[class*=\"prev-next\"]")->last();

my $next_page_url_rel = $next_page_link->attr("href");

我收到了所需的链接。

我的问题是,为什么第二个版本有效而​​不是第一个?为什么我必须从根DOM元素开始获取元素列表,以及为什么从根的子元素开始返回包含所有链接标记的字符串而不仅仅是我想要的那个?

修改 我正在解析的HTML的一个例子是:

<div class="pagination-block clearfix">
  <div class="page-of-pages">
    Page 2 of 100
  </div>

  <ul class="pagination-links">
    <li>
      .
      .
      .
    </li>

    <li>
      <a class="page-option prev-next" href="PREV LINK">Prev</a>
    </li>

    <li>
      <a class="page-option prev-next" href="NEXT LINK">Next</a>
    </li>
  </ul>
</div>

2 个答案:

答案 0 :(得分:2)

如果您可以显示正在处理的HTML示例,那将会有很大帮助。相反,我想到了这一点,我希望这很接近。

<html>
   <head>
      <title>Title</title>
   </head>
   <body>

      <div class="pagination-block">
         <div class="page-of-pages">Page 99 of 100</div>
         <ul>
            <li>
               <a class="prev-next" href="/page98">Prev</a>&nbsp;
            </li>
            <li>
               <a class="prev-next" href="/page100">Next</a>
            </li>
         <ul>
      </div>

      <div class="pagination-block">
         <div class="page-of-pages">Page 99 of 100</div>
         <ul>
            <li>
               <a class="prev-next" href="/page98">Prev</a>&nbsp;
            </li>
            <li>
               <a class="prev-next" href="/page100">Next</a>
            </li>
         <ul>
      </div>

   </body>
</html>

现在让我们看看你的代码

my $pagination_elements = $dom->find('div[class*="pagination-block"]')

这会为您提供一个Mojo::Collection,其中包含divpagination-block的两个{。}}实例。

my $prev_next_elements = $pagination_elements->find('a[class*="prev-next"]')

这类似于map,将Mojo::Collection的每个成员替换为对其进行find的结果。由于find会返回另一个Mojo::Collection,因此您现在拥有两个集合的集合,每个集合都有两个Mojo::DOM个对象。澄清

  • $prev_next_elements是一个Mojo::Collection对象,size为2

  • $prev_next_elements->[0]$prev_next_elements->[1]都是Mojo::Collection个对象,每个对象的大小为2

  • $prev_next_elements->[0][0]$prev_next_elements->[0][1]$prev_next_elements->[1][0]$prev_next_elements->[1][1]都是Mojo::DOM个对象,每个对象都包含一个<a>元素HTML文档

my $next_page_link = $prev_next_elements->last

这需要$prev_next_elements的第二个元素。它与$prev_next_elements->[1]相同,因此Mojo::Collection对象包含两个Mojo::DOM元素,这些元素包含HTML文档中的最后两个<a>元素。

my $next_page_url = $next_page_link->attr('href')

现在您正在执行另一项map操作:将attr应用于集合的两个元素,并返回包含两个href字符串/page98和{{1}的另一个集合}}。 Stinrgifying这个/page100只是连接所有元素并给你Mogo::Collection

要解决所有问题,请抓住"/page98\n/page100"的{​​{1}},为您提供last个对象。 然后$pagination_elementsMojo::DOM元素执行find,为您提供&#34; prev&#34;的prev和 &#34;下一个&#34; next个元素,最后使用Mojo::Collection将这些元素映射到链接。最后,<a>包含&#34; prev&#34;的attr('href')文字。和&#34; next&#34;最后一个分页栏中的链接。

Mojo::Collection

<强>输出

href

您可以将所有内容折叠为更方便的内容,例如

my $pagination_elements = $dom->find('div[class*="pagination-block"]');
my $last_pagination_element = $pagination_elements->last;
my $prev_next_elements = $last_pagination_element->find('a[class*="prev-next"]');
my $prev_next_links = $prev_next_elements->attr('href');
my ($prev_page_link, $next_page_link) = ($prev_next_links->first, $prev_next_links->last);
say $prev_page_link;
say $next_page_link;

答案 1 :(得分:1)

如果您使用Data::Dump(或某些等效模块)代替print,您将获得有关进展情况的线索:

use Data::Dump;
dd $next_page_url;
dd $next_page_url_rel;

输出:

bless(["PREV LINK", "NEXT LINK"], "Mojo::Collection")
"NEXT LINK"

如您所见,您的第一个变量实际上包含一个集合,而不是一个字符串。

问题出现是因为Mojo::DOM->find返回Mojo::Collection

my $pagination_elements = $dom->find('div[class*="pagination-block"]');

对集合执行后续find将返回一个嵌套集合,该集合无法像last之类的调用那样执行。

以下是三种不同的解决方案,可帮助您解决首次尝试查找链接文本的问题:

  1. 使用Mojo::DOM->at方法{DOM}与DOM选择器匹配的第一个元素find

    my $pagination_elements = $dom->at('div[class*="pagination-block"]');
    
  2. 使用Mojo::Collection->first->last在后续find之前隔离集合中的特定元素。

    my $pagination_elements
        = $dom->find('div[class*="pagination-block"]')->last();
    
  3. 使用Mojo::Collection->flatten将后续find创建的嵌套集合展平为包含所有元素的新集合:

    my $pagination_elements = $dom->find('div[class*="pagination-block"]');
    my $prev_next_elements
        = $pagination_elements->find('a[class*="prev-next"]')->flatten();
    
  4. 所有这些方法都会使您的脚本按预期运行:

    use strict;
    use warnings;
    
    use Mojo::DOM;
    use Data::Dump;
    
    my $dom = Mojo::DOM->new(do { local $/; <DATA> });
    
    # Fix 1
    my $pagination_elements = $dom->at('div[class*="pagination-block"]');
    
    # Fix 2
    #my $pagination_elements
    #    = $dom->find('div[class*="pagination-block"]')->last();
    
    # Fix 3
    #my $pagination_elements = $dom->find('div[class*="pagination-block"]');
    #my $prev_next_elements
    #    = $pagination_elements->find('a[class*="prev-next"]')->flatten();
    
    my $prev_next_elements = $pagination_elements->find('a[class*="prev-next"]');
    my $next_page_link     = $prev_next_elements->last();
    my $next_page_url      = $next_page_link->attr("href");
    
    dd $next_page_url;
    
    $next_page_link = $dom->find('div[class*="pagination-block"] > ul > li > a[class*="prev-next"]')->last();
    my $next_page_url_rel = $next_page_link->attr("href");
    
    dd $next_page_url_rel;
    
    __DATA__
    <html>
    <head>
    <title>Paging Example</title>
    </head>
    <body>
        <div class="pagination-block clearfix">
          <div class="page-of-pages">
            Page 2 of 100
          </div>
    
          <ul class="pagination-links">
            <li>
              .
              .
              .
            </li>
    
            <li>
              <a class="page-option prev-next" href="PREV LINK">Prev</a>
            </li>
    
            <li>
              <a class="page-option prev-next" href="NEXT LINK">Next</a>
            </li>
          </ul>
        </div>
    </body>
    </html>
    

    输出:

    "NEXT LINK"
    "NEXT LINK"