使用 WWW::Mechanize 进行内存泄漏

CJ7*_*CJ7 4 perl out-of-memory www-mechanize

我在 Perl 中有这个脚本"Out of memory",运行几分钟后出现错误。我看不到任何循环引用,我无法弄清楚为什么会发生这种情况。

use feature 'say';
use WWW::Mechanize;
use HTML::TreeBuilder::XPath;
use utf8;

$url = "some url";

my $mech = new WWW::Mechanize;
$mech->get($url);
my $html = HTML::TreeBuilder::XPath->new_from_content($mech->content);
my $html2;

do { 
    for $item ($html->findnodes('//li[@class="dataset-item"]'))
    {
        my $title = $item->findvalue('normalize-space(.//a[2])');
        next unless $title =~ /environmental impact statement/i;        
        my $link = $item->findvalue('.//a[2]/@href');
        $mech->get($link);
        $html2 = HTML::TreeBuilder::XPath->new_from_content($mech->content);
        my @pdflinks = $html2->findvalues('//a[@title="Go to external URL"]/@href');
        my $date = $html2->findvalue('//tr[th="Date Created"]/td');
        for $pdflink (@pdflinks)
        {
            next unless $pdflink =~ /\.pdf$/;
            $mech->get($pdflink);
            $mech->save_content($filename = $mech->response->filename);
            say "Title: $title\nDate: $date\nFilename: $filename\n";
        }
    }
    if ($nextpage = $html->findvalue('//ul[@class="pagination"]/li/a[.="»"]/@href'))
    {
        say "Next Page: $nextpage\n";
        $mech->get("some site" . $nextpage);
        $html = HTML::TreeBuilder::XPath->new_from_content($mech->content);
    }
} while ($nextpage);

say "Completed.";
Run Code Online (Sandbox Code Playgroud)

zdi*_*dim 8

由于WWW::Mechanize默认情况下具有其用户代理在浏览时保留所有历史记录

  • 堆栈深度=> $value

设置跟踪所有下载页面的页面堆栈的深度。默认值实际上是无限的堆栈大小。如果堆栈正在占用您的内存,请将其设置为较小的数字,例如 5 或 10。将其设置为零意味着 Mech 将不保留任何历史记录。

因此,对象不断增长。通过使用Devel::Size qw(total_size)我跟踪的大小,$mech可以看到它在每个 pdf 后增加了数十 kB。剧本显然得到了很多匹配;当它吞噬了 10% 的内存(并且磁盘上有许多超过 Gb 的文件)时,我退出了我的测试。

一种解决方案是为每个 实例化一个新对象$item。这在原则上是浪费的,但实际上它不会增加太多开销,同时会限制最大大小。

或者重置它,或者确实限制它的堆栈深度。由于代码似乎根本不需要回到以前的状态,因此实际上不需要任何堆栈,因此您删除它的解决方案非常好。

注释

  • 准确地说,脚本中没有“泄漏”;它只是需要越来越多的内存

  • 总是use strict;use warnings;在脚本的顶部

  • 最好不要使用间接对象​​语法来实例化对象 ( new Package),而是使用普通的方法调用 ( Package->new),以避免在某些情况下处理歧义。请参阅文档本页中的解释,以及本帖本帖中的故障示例。