ฉันจะลบลิงก์ภายนอกออกจาก HTML โดยใช้ Perl ได้อย่างไร

ฉันกำลังพยายามลบลิงก์ภายนอกออกจากเอกสาร HTML แต่เก็บจุดยึดไว้ แต่ฉันโชคไม่ดีนัก regex ต่อไปนี้

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

จะจับคู่จุดเริ่มต้นของแท็ก Anchor และส่วนท้ายของแท็กลิงก์ภายนอก เช่น

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

ฉันก็เลยไม่ได้อะไรมาแทน

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

มันบังเอิญว่า Anchors ทั้งหมดมีแอตทริบิวต์ href เป็นตัวพิมพ์ใหญ่ ดังนั้นฉันจึงรู้ว่าฉันสามารถจับคู่แบบคำนึงถึงตัวพิมพ์เล็กและตัวพิมพ์ใหญ่ได้ แต่ฉันไม่ต้องการพึ่งพาให้เป็นเช่นนั้นในอนาคต

เป็นสิ่งที่ฉันสามารถเปลี่ยนแปลงได้เพื่อให้ตรงกับแท็ก a เพียงแท็กเดียวหรือไม่


person Mark    schedule 21.10.2009    source แหล่งที่มา
comment
โอ้ มันทำให้สมองของฉันเจ็บแค่ไหนทุกครั้งที่ฉันเห็นคนอื่น ฉันจะใช้ regexes เพื่อแยกวิเคราะห์ HTML ได้อย่างไร คำถาม. ดูที่ stackoverflow.com/questions/701166/ และ stackoverflow ได้ไหม .com/questions/773340/ (และ stackoverflow.com/questions/487213/ ในกรณีของคุณ) ก่อนดำเนินการต่อ   -  person Chris Lutz    schedule 21.10.2009
comment
ในกรณีทั่วไป ใช่แล้ว regex ไม่ได้ออกแบบมาเพื่อแยกวิเคราะห์ XML/HTML จริงๆ กล่าวคือ หากพื้นที่ปัญหามีจำกัด ก็อาจเป็นทางเลือกที่ใช้การได้   -  person Amber    schedule 21.10.2009
comment
โปรดทราบว่าปัญหาใหญ่ที่สุดประการหนึ่งในการพยายามใช้ regex เพื่อแยกวิเคราะห์ XML/HTML ก็คือข้อเท็จจริงที่ว่าโดยทั่วไปต้องใช้การแยกวิเคราะห์แบบเรียกซ้ำ แท็กจุดยึดเป็นข้อยกเว้นที่สะดวก เนื่องจากไม่อนุญาตให้ซ้อนจุดยึด   -  person Amber    schedule 21.10.2009
comment
มันง่ายสำหรับกรณีนี้ - โดยปกติแล้วฉันจะคัดค้านเกี่ยวกับวงเล็บเหลี่ยมที่ซ้อนกัน แต่ไม่มี regexes ใดที่ให้ไว้จะได้รับผลกระทบมากนัก ฉันจะแจ้งว่าข้อกำหนดมีการเปลี่ยนแปลง และ regex ของคุณอาจไม่สามารถบำรุงรักษาได้ในขณะที่คุณพยายามปรับให้เข้ากับการคัดค้านข้อกำหนดใหม่ เพื่อความครบถ้วนสมบูรณ์   -  person Chris Lutz    schedule 21.10.2009
comment
นี่กำลังเปลี่ยนเป็น comp.lang.perl.misc ฉันแน่ใจว่าเราทุกคนต้องการแสดงความเชี่ยวชาญของเรา แต่ลองพิจารณาถึงความเป็นไปได้เล็กน้อยที่บางคนต้องการ regex อย่างรวดเร็วเพื่อทำการแก้ไขเพียงครั้งเดียว แทนที่จะต้องการให้เราทุกคนมีโอกาสที่จะแสดงผลงานภายในของเรา ความรู้เชิงลึก?   -  person    schedule 21.10.2009
comment
มีบทความดีๆ โดย Mark Jason Dominus ที่นี่: perl.plover.com/yak /12views/samples/notes.html อย่าลืมข้อดีเกี่ยวกับ Perl กัน สามารถโต้ตอบกับโปรแกรมอื่นได้ดี และเหมาะสำหรับการสร้างต้นแบบอย่างรวดเร็ว อย่าไปรบกวนผู้คนเมื่อพวกเขาใช้ Perl ในแบบที่มันถูกออกแบบมาให้ใช้งาน   -  person    schedule 21.10.2009
comment
@Kinopiko MJD กำลังพูดถึงการเรียกใช้โปรแกรมภายนอกผ่าน system: ดู perl.plover.com /yak/12views/samples/slide003.html   -  person Sinan Ünür    schedule 21.10.2009
comment
ไม่ เขากำลังพูดถึงเรื่องนี้ การร้องเรียนของฉันไม่ได้เกี่ยวกับจุดยืนมากนัก ... เช่นเดียวกับความหยั่งรู้และความไร้ความคิดที่ได้ประกาศใช้   -  person    schedule 21.10.2009
comment
ความเชื่อและความไร้ความคิดคือทั้งหมดที่ฉันเห็นที่นี่   -  person    schedule 21.10.2009
comment
@Kinopiko คุณไม่สามารถเพิกเฉยต่อสิ่งที่ MJD พูดคุยได้ นอกจากนี้ MJD ไม่ได้ปกป้องวิธีการที่จะให้ผลลัพธ์ที่ผิด นอกจากนี้ ฉันเห็นด้วยกับ MJD อย่างสมบูรณ์เกี่ยวกับการเรียกใช้คำสั่งภายนอก ตอนนี้ ผู้คนในหน้านี้ซึ่งคัดค้านการแยกวิเคราะห์ HTML ด้วย regexes ได้ให้ข้อโต้แย้งที่สมเหตุสมผลและรอบคอบเพื่อแสดงให้คุณเห็นว่าวิธีการนั้นล้มเหลวได้ง่ายเพียงใด คุณบังเอิญเพิกเฉยต่อข้อโต้แย้งเหล่านั้น อ้างว่านี่คือความเชื่อ โอ้ดี. books.google. คอม/   -  person Sinan Ünür    schedule 21.10.2009
comment
@Kinopiko บุหรี่ทำให้เกิดมะเร็ง กิจกรรมของมนุษย์มีบทบาทสำคัญในการเปลี่ยนแปลงสภาพภูมิอากาศ มนุษย์และลิงมีบรรพบุรุษร่วมกัน หลายคนอ้างว่าข้อเท็จจริงเหล่านี้เป็นเพียงความเชื่อเท่านั้น แต่มันไม่ได้เปลี่ยนความจริงที่ว่ามันเป็นความจริง การแยกวิเคราะห์ HTML ด้วย regexes เป็นความคิดที่ไม่ดี บางครั้งแย่กว่านั้นก็ดีกว่า แต่ในกรณีนี้ การพึ่งพา regex ทำให้เกิดปัญหา--โอ๊ะ เรามีลิงก์ไปยังไฟล์ .php โอ๊ะโอ นี่คือลิงก์ไปยัง .cgi, ad naseum--ดังนั้น regex จึงเติบโตขึ้น เลวทรามยิ่งขึ้นและแตกหักอยู่เสมอ วิธีแยกวิเคราะห์ที่แท้จริงนั้นเขียนง่ายกว่า (รหัสที่ถูกต้อง) บำรุงรักษาง่ายกว่าและเข้าใจง่ายกว่า   -  person daotoad    schedule 21.10.2009
comment
ความคิดเห็นประเภทนี้ตรงกับที่ผมหมายถึงเรื่องลัทธิคัมภีร์ ด้วยเหตุผลบางอย่าง คุณตัดสินใจว่าสคริปต์ข้างต้นเป็นตัวแยกวิเคราะห์ HTML ที่ใช้งานหนักซึ่งจำเป็นต้องได้รับการบำรุงรักษาในอนาคตอันไกลโพ้น และจะต้องแข็งแกร่งพอที่จะประมวลผลอินพุต HTML ชนิดใดก็ได้ที่เป็นไปได้ อาจจะใช่ แต่อาจจะเป็นแค่ครั้งเดียวซึ่งจะไม่มีวันได้ใช้อีก ลัทธิคัมภีร์ที่ฉันกล่าวหาคุณคือการสันนิษฐานว่าเป็นอย่างแรกและยืนกรานในการแก้ปัญหาเต็มรูปแบบซึ่งอาจไม่เหมาะสมอย่างยิ่ง   -  person    schedule 27.10.2009


คำตอบ (5)


สะท้อนความคิดเห็นของ Chris Lutz ฉันหวังว่าสิ่งต่อไปนี้จะแสดงให้เห็นว่าการใช้ parser นั้นตรงไปตรงมามาก (โดยเฉพาะถ้าคุณต้องการจัดการกับอินพุตที่คุณยังไม่เคยเห็นเช่น <a class="external" href="/th...">) แทนที่จะรวบรวมวิธีแก้ปัญหาที่เปราะบางโดยใช้ 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="/th#FN1" name="01">1</a>
some other html
<a href="/th155.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="/thtest.html">click here >>></a>
either</p>

เอาท์พุท:

C:\Temp> hjk
<a HREF="/th#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>

หมายเหตุ: โซลูชันที่ใช้ regex ที่คุณทำเครื่องหมายว่า "ถูกต้อง" จะหยุดทำงานหากไฟล์ที่เชื่อมโยงให้มีนามสกุล .html แทนที่จะเป็น .htm ด้วยเหตุนี้ เราพบว่าคุณกังวลเรื่องการไม่อาศัยแอตทริบิวต์ตัวพิมพ์ใหญ่ HREF ที่ไม่สมเหตุสมผล หากคุณต้องการความรวดเร็วและสกปรกจริงๆ คุณไม่ควรกังวลกับสิ่งอื่นใด และคุณควรใช้ตัวพิมพ์ใหญ่ทั้งหมด HREF และจัดการกับมันให้เสร็จ อย่างไรก็ตาม หากคุณต้องการให้แน่ใจว่าโค้ดของคุณทำงานกับเอกสารได้หลากหลายและใช้งานได้นานกว่ามาก คุณควรใช้ parser ที่เหมาะสม

person Sinan Ünür    schedule 21.10.2009
comment
+1 สำหรับตัวอย่างที่เขาอาจไม่ได้พิจารณา นั่นคือสิ่งที่ ควรเป็น ข้อโต้แย้งของฉันกับ regexes ในกรณีนี้ - person Chris Lutz; 21.10.2009
comment
ฉันไม่เห็นว่าสิ่งนี้ตรงไปตรงมาอย่างไร คุณมีโค้ดซึ่งขึ้นอยู่กับความรู้เกี่ยวกับระบบอ้างอิงของ Perl อย่างกว้างขวาง และมีตัวเลขมหัศจรรย์หลายตัวในโค้ดโดยไม่มีคำอธิบาย นอกจากนี้ยังอาศัยโมดูลอื่น และโมดูล CPAN มักขาดเอกสารที่ชัดเจน โซลูชันของคุณมีขนาดกะทัดรัด แต่ก็ห่างไกลจากความตรงไปตรงมา เว้นแต่จะมีผู้เชี่ยวชาญ Perl - person ; 21.10.2009
comment
@Kinopiko: 1. มันถูกต้อง ซึ่งแตกต่างจากโซลูชันของคุณซึ่งจะพังในทุกสถานการณ์ 2. ผู้ที่มีความสามารถควรอ่านโค้ดได้ การอ้างอิงไม่ใช่อุปสรรคสูงในการเข้า การทำความเข้าใจการอ้างอิงโดยสมบูรณ์นั้นง่ายกว่ามากสำหรับผู้เริ่มต้นที่จะเข้าใจมากกว่าความเข้าใจที่สมบูรณ์เกี่ยวกับ regexes 3. ฉันอยากให้ HTML::TokeParser::Simple มีส่วนต่อประสานที่อ่านง่ายขึ้น แต่ถ้าคุณไม่สามารถใช้เวลาดูเอกสารสักครู่ได้ ก็ถือว่าล้มเหลวอีกครั้ง - person hobbs; 21.10.2009
comment
และ 4. มีการใช้โมดูลเพราะ นี่ไม่ใช่ปัญหาเล็กๆ น้อยๆ อีกครั้ง หากคุณถือว่ามันเป็นปัญหาเล็กๆ น้อยๆ คุณจะได้รับวิธีแก้ไขที่ผิด เช่นเดียวกับผู้โพสต์ต้นฉบับและเช่นเดียวกับคุณ โมดูลที่เหมาะกับงานเกือบจะรับประกันได้ว่าจะมีรถ น้อยลง - person hobbs; 21.10.2009
comment
ฉันยอมรับว่าตัวเลขมหัศจรรย์ทำให้เกิดความสับสน นี่เป็นคุณสมบัติของ HTML::TokeParser แทนที่จะเป็นคุณสมบัติทั่วไปที่ไม่แยกวิเคราะห์ด้วย regexes การใช้ XML::LibXML ของ W3C DOM น่าจะชัดเจนกว่า แต่มีรายละเอียดมากกว่า - person jrockway; 21.10.2009
comment
สัญชาตญาณแรกของฉันมักจะเป็น HTML::TreeBuilder แต่ฉันขอขอบคุณที่ใช้ตัวแยกวิเคราะห์การสตรีมมากกว่า DOM (หรือ Pseudo-DOM) ซึ่งปัญหาเกิดขึ้น สิ่งที่ดีอีกอย่างเกี่ยวกับ TokeParser::Simple คือมันทำให้การสะดุดรอบง่ายขึ้นมาก :) - person hobbs; 21.10.2009
comment
@jrockway ในทางกลับกัน เอกสารมีความชัดเจนมากว่าตัวเลขเหล่านั้นคืออะไร search.cpan.org/perldoc/HTML::TokeParser::Simple#DESCRIPTION - person Sinan Ünür; 21.10.2009
comment
โมดูล CPAN มักขาดเอกสารที่ชัดเจน และนั่นคือเหตุผลที่คุณไม่ควรพิจารณาใช้โมดูลหากคุณสามารถใช้งานนิพจน์ทั่วไปได้อย่างรวดเร็ว ว้าว! - person innaM; 21.10.2009
comment
ใครสนใจโมดูล CPAN มักจะทำอะไร? สิ่งสำคัญคือโมดูล CPAN ที่คุณต้องการใช้ทำอะไรเท่านั้น - person brian d foy; 21.10.2009

ตัวแยกวิเคราะห์ประเภท SAX อีกเล็กน้อยคือ HTML::Parser:

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;
person Axeman    schedule 21.10.2009
comment
+1 BTW โปรดดูที่ perlfoundation.org/perl5/ บน Smart::Comments ฉันไม่แน่ใจว่าฉันรู้สึกหนักแน่นขนาดนั้นหรือไม่ แต่โดยทั่วไปแล้วฉันไม่ใช่แฟนตัวกรองแหล่งที่มา - person Sinan Ünür; 21.10.2009
comment
@Sinan Ünür: โดยปกติฉันจะลบรหัสการดีบักออกจากคำตอบที่เสร็จสิ้นแล้ว นั่นคือสิ่งที่ Smart::Comments ส่องสว่าง แต่กำลังแก้ไขโค้ด - person Axeman; 21.10.2009
comment
นั่นก็ไม่เลว แต่ในที่สุดก็ต้องอาศัยนิพจน์ทั่วไปอื่น อย่างไรก็ตาม HTML::Parser จะให้แอตทริบิวต์และค่าของมันแก่คุณหากคุณถามอย่างดี - person innaM; 21.10.2009
comment
@Manni: เห็นด้วย - และฉันรู้ว่ามันทำอย่างนั้น แต่ฉันไม่ต้องการเขียนไปป์ที่ซับซ้อนเมื่อฉันแก้ไขแท็ก - แต่มันจะทำให้เป็นทางออกที่ดีกว่าถ้าฉันไม่ได้เขียนอะไรเลย ฉันจะเปลี่ยนมัน - person Axeman; 21.10.2009

อีกวิธีหนึ่ง ฉันชอบ 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="/th#FN1" name="01">1</a>
some other html
<a href="/th155.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="/thtest.html">click here >>></a>
either</p>
person Leonardo Herrera    schedule 22.10.2009
comment
เอกสารประกอบสำหรับ TreeBuilder และเพื่อนๆ อาจจะมากกว่านี้อีกหน่อย... ร่วมกัน แต่ฉันยอมรับว่า นี่เป็นวิธีที่ดีมากในการทำงานกับ HTML เมื่อคุณคุ้นเคยกับมันแล้ว - person r0berts; 25.12.2015

ทำไมไม่เพียงแต่ลบลิงก์ที่แอตทริบิวต์ href ไม่ได้ขึ้นต้นด้วยเครื่องหมายปอนด์ออกเท่านั้น บางสิ่งเช่นนี้:

html =~ s/<a href="/th[^#][^"]*?">(.+?)<\/a>/$1/sig;
person Amber    schedule 21.10.2009
comment
ไม่รองรับลิงก์เปล่า ฉันรู้ว่าลิงก์เปล่านั้นน่ารังเกียจ และคุณจะไม่พบลิงก์เหล่านั้นใน HTML ที่ฉันเขียนหรือเขียนโปรแกรมสร้างให้ แต่ลิงก์และแอตทริบิวต์ที่มีเครื่องหมายคำพูดเดี่ยวนั้นเหมาะสมกับข้อมูลจำเพาะ - person Axeman; 22.10.2009

ง่ายกว่านั้นอีก หากคุณไม่สนใจแอตทริบิวต์ของแท็ก:

$html =~ s/<a[^>]+>(.+?)<\/a>/$1/sig;
person Павел П    schedule 24.11.2016