Gå til innhold

Perl: Parse html og hente ut tekst


Anbefalte innlegg

Hei

Pusler litt med perl script som jeg har tenkt å bruke på server boksen min for å oppdatere

ip adressen mot navneserverene (har domene men kjører serveren hjemme).

Men jeg har ikke fast ip adresse og da denne skifter så mister jeg adresseringen på domenet og da er det meningen at serveren selv sjekker dette og oppdaterer hvis nødvendig.

 

ruteren min er en linksys 160N men den har bare funksjoner for 'DynDNS.org' og 'TZO.com' og ingen av dem er brukbare for min del da jeg har domene fra en annen leverandør 'Joker.com'

kan hente ut ip adressen fra en eller annen side også men det også blir uaktuelt da jeg helt sikkert vil bli bannet etter hvert hvis det kjøres regelmessige sjekk på ip.

 

Så langt har jeg kommet.

jeg kan logge meg på linksys boksen med perl script og hente ut status siden på boksen i html

form.

#!/usr/bin/perl
use LWP::UserAgent [search.cpan.org];
use HTTP::Request [kobesearch.cpan.org];

my $URL = 'http://10.0.0.1/Status_Router.asp';

#første forsøk for å logge inn på linksys boksen som i de fleste tilfeller vil generere
# en feil pga innlogging ikke blir registrert
my $agent = LWP::UserAgent->new(env_proxy => 1,keep_alive => 1, timeout => 30);
my $header = HTTP::Request->new(GET => $URL);
my $request = HTTP::Request->new('GET', $URL, $header);
$request->authorization_basic('*****', '******');
my $response = $agent->request($request);




#Andre forsøk for at innlogging skal registreres og sletter eventuelt det som ble registrert
# i variabelen $response
sleep(1);
my $response = "";

my $agent = LWP::UserAgent->new(env_proxy => 1,keep_alive => 1, timeout => 30);
my $header = HTTP::Request->new(GET => $URL);
my $request = HTTP::Request->new('GET', $URL, $header);
$request->authorization_basic('*******', '******');
my $response = $agent->request($request);




#sjekker ut data fra siden
if ($response->is_success){

my $tr_tag = $response->as_string;

}elsif ($response->is_error){

} 

 

 

Så kommer det som jeg ikke får helt til.

Jeg ønsker å parse stringen $tr_tag og hente ut IP adressen og da er tanken at dette må gjøres i 2 step ettersom det finnes flere varianter av ip nummer på denne siden.

 

Steg 1 som jeg ser det er å hente ut denne kode blokken ut fra variabelen $tr_tag for sammensetningen er unik og finnes bare en gang i tabellen bortsett fra IP adressen som jeg er ute etter

<TR><script>draw_table(SUBFUN,"");</script>
   <TD class=FUNNAME2><script>document.write("Internet IP Address")</script>:</TD>
   <TD class=FUNFIELD><B>123.456.789.123</B></TD>
</TR>

 

og da har jeg komponert en regex (i RegexBuddy)som finner akkurat denne blokken som jeg er ute etter

<TR><script>draw_table[(]SUBFUN,""[)];</script>\n\s+<TD\sclass=FUNNAME2><script>document.write[(]"Internet\sIP\sAddress"[)]</script>:</TD>\n\s+<TD\sclass=FUNFIELD><B>\b([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\b</B></TD>\n\s+</TR>

 

men jeg har ingen anelse om hvordan jeg skal plukke ut akkurat denne blokken fra variabelen $tr_tag.

Har prøvd med dette

($tr_blokk) = ($tr_tag =~ m{<TR><script>draw_table[(]SUBFUN,""[)];</script>\n\s+<TD\sclass=FUNNAME2><script>document.write[(]"Internet\sIP\sAddress"[)]</script>:</TD>\n\s+<TD\sclass=FUNFIELD><B>\b([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\b</B></TD>\n\s+</TR>}g);
print $tr_blokk;

men dette gir meg bare et nummer tilbake (87 i dette tilfelle, regner kanskje med at dette er linje 87?)

 

Step 2 er tanken å hente ut selve ip adressen og sjekke gammel ip med eventuelt ny ip og oppdatere hvis nødvendig.

 

Noe sier meg kankje at grep kan være en ting å bruke men jeg står litt fast i utplukket

 

Hilsen knut

Lenke til kommentar
Videoannonse
Annonse

Ikke så mye inne perl er ikke så begeistret for perl heller,skriver bare noe i python du kan se på.

Nå ser det ut som du plassert regex inne i tr_tagen og det går jo ikke bra.

 

import re

html = '''\
<TR><script>draw_table(SUBFUN,"");</script>
   <TD class=FUNNAME2><script>document.write("Internet IP Address")</script>:</TD>
   <TD class=FUNFIELD><B>123.456.789.123</B></TD>
</TR>
'''

test_match = re.findall(r'\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b', html)
print test_match
#-->['123.456.789.123']


def compare(new_ip, old_ip):
   new_ip = ''.join(new_ip)
   if new_ip == old_ip:
       return 'Ip has not changed'
   return 'Ip has changed'

if __name__ =='__main__':
   old_ip = '123.456.789.123'
   print compare(test_match, old_ip)
   #-->Ip has not changed

En ip adresse er rimlig greit og ta ut med regex.

Nå er ikke regex det beste verktøyet når det gjelder parse [X]HTML.

For python er beautifulsoup,lxml bra.

 

Les dette svaret fra bobince,et av de bedere svarene og artig :thumbup:

http://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags

Endret av SNIPPSAT
Lenke til kommentar

Hei

takk for innspill men jeg fikk det ikke til som ønsket

Jeg prøvde en annen løsning som forsåvidt fungerer greit og det er å skrive til en fil for å lese den inn linje for linje og plukke ut data som er relevant

 

PS fant en kortere linje som er unik i koden som er lettere å bruke istedenfor <TR> taggene

Denne linjen -> var wan_ip = "123.456.789.123";

 

#leses temp fil linje for linje
open(MYINPUTFILE, "<data.txt");
my(@lines) = <MYINPUTFILE>; 

my($line);
my($wan);

foreach $line (@lines)
{

if (substr($line, 1, 10) eq 'var wan_ip')
{
		$wan = $line;
}

}
close(MYINPUTFILE);

#Hent ut ip adressen fra stringen
$wan =~ /(\d{1,3}).(\d{1,3}).(\d{1,3}).(\d{1,3})/;
   $ip = $1 .".". $2 .".". $3 .".". $4;


if ($ip eq '0.0.0.0')
{
print "Error";
}
else
{
print "IP er: $ip \n";
}

 

Hilsen Knut

Lenke til kommentar

Er ikke sikker på om jeg skjønner spørsmålet.. men du har en string - $tr_tag - som inneholder en ip du vil hente ut?

 

Siden du vet hva den started med (sikkert bare det siste tallet som forandrer seg..) tror jeg det letteste er noe slikt (untested):

 

$tr_tag =~ /(213\.\d+\.\d+\.\d+)/;
die "Couldn't find the ip in the html \n" unless $1;
my $ip = $1;

Lenke til kommentar

Er ikke sikker på om jeg skjønner spørsmålet.. men du har en string - $tr_tag - som inneholder en ip du vil hente ut?

 

Siden du vet hva den started med (sikkert bare det siste tallet som forandrer seg..) tror jeg det letteste er noe slikt (untested):

 

$tr_tag =~ /(213\.\d+\.\d+\.\d+)/;
die "Couldn't find the ip in the html \n" unless $1;
my $ip = $1;

 

Hei

Det har seg slik at i $tr_tag så ligger hele status siden fra ruteren som html format og på denne siden forekommer det en del detaljer om oppkobling, navn etc og her er det også listet op ekstern ip, intern ip, subnet mask, gateway etc. tror det er rundt 6 forskjellige serier som kan tolkes som ip nummer men bare 1 av dem er den riktige wan adressen.

 

 

Prøvde kodesnutten din og den ga bare "Couldn't find the ip in the html".

Den løsningen jeg beskrev i forrige post fungerer den men er ikke akkurat elegant da jeg må lagre det i en fil og hente den opp igjen. burde gå an å hente den rett ut fra variabelen $tr_tag (dette ble lite beskrivende variabel navn men, men :roll:)

som sagt så fant jeg en bedre linje og hente ut ip i dokumentet da denne lå på følgende

linje var wan_ip = "123.456.789.123"; og er første forekomst i dokumentet/variabelen

Lenke til kommentar

Med en regex som dette kan du fint ta ut kun wan_ip

wan_ip\s\=\s(\d{3}.+\d)

Nå er denne for python med regexen bør være nogenlunde lik for perl.

 

import re

html = '''\
<TR><script>draw_table(SUBFUN,"");</script>
   <TD class=FUNNAME2><script>document.write("Internet IP Address")</script>:</TD>
   123.444.789.555
   <TD class=FUNFIELD><B>123.456.789.123.456.777.123111</B></TD>
    wan_ip = 123.456.789.123 #only match this ip
</TR>
'''

test_match = re.search(r'wan_ip\s\=\s(\d{3}.+\d)', html)
print test_match.group(1)
#-->123.456.789.123

Lenke til kommentar

Med en regex som dette kan du fint ta ut kun wan_ip

wan_ip\s\=\s(\d{3}.+\d)

Nå er denne for python med regexen bør være nogenlunde lik for perl.

 

import re

html = '''\
<TR><script>draw_table(SUBFUN,"");</script>
   <TD class=FUNNAME2><script>document.write("Internet IP Address")</script>:</TD>
   123.444.789.555
   <TD class=FUNFIELD><B>123.456.789.123.456.777.123111</B></TD>
    wan_ip = 123.456.789.123 #only match this ip
</TR>
'''

test_match = re.search(r'wan_ip\s\=\s(\d{3}.+\d)', html)
print test_match.group(1)
#-->123.456.789.123

 

 

Hei igjen

Prøver og bruke det du sendte men det ser ikke ut til at Perl er i nærheten av å ha slike funksjoner, muligens den har men jeg er ikke i nærheten av å finne ut hva som kan brukes.

 

For å si det slik så bruker jeg ikke perl til vanlig men syntes det var en grei måte å gjøre akkurat dette, men det ser ikke slik ut gitt i hvertfall ikke på en fin måte.

 

 

Har aldri brukt Python men det ser ut til at dette språket har mere funksjoner for slike ting.

Kan python kjøres på hvilken som helst Linux maskin? OS er debian v5

 

Edit: testet vilken versjon jeg hadde og fikk dette Python 2.5.2

skulle denne vært oppdatert eller duger den.

Endret av Icaro2
Lenke til kommentar
Prøver og bruke det du sendte men det ser ikke ut til at Perl er i nærheten av å ha slike funksjoner

Perl har nok mye muligheter nå det gjelder regex,men man må jo kunne bruke dem.

 

Kan python kjøres på hvilken som helst Linux maskin? OS er debian v5

Python kommer installert på alle linux distroer.

 

Edit: testet vilken versjon jeg hadde og fikk dette Python 2.5.2

Det skulle gå greit.

 

Bra editor for linux SPE.

sudo apt-get install spe

Lenke til kommentar

Prøvde kodesnutten din og den ga bare "Couldn't find the ip in the html".

Ah, poenget var først og fremst å vise deg hvordan du skulle bruke (old school) regulære uttrykk i perl, du gjorde det mye vanskeligere enn det egentlig er.. :)

 

Tenkte at kanskje det bare ver én ip som startet riktig (anntar at bare siste tallet forandrer seg når du får ny ip) slik at det var enkelt å finne den riktige selv om det er mange adresser.. Hvis ikke får du ta med "wan_ip" slik som SNIPPSAT foreslo..

 

Den løsningen jeg beskrev i forrige post fungerer den men er ikke akkurat elegant da jeg må lagre det i en fil og hente den opp igjen. burde gå an å hente den rett ut fra variabelen $tr_tag (dette ble lite beskrivende variabel navn men, men :roll:)

som sagt så fant jeg en bedre linje og hente ut ip i dokumentet da denne lå på følgende

linje var wan_ip = "123.456.789.123"; og er første forekomst i dokumentet/variabelen

Du trenger ikke å lagre det til fil nei.. grunne til at det var lettere, er nok at å matche på tvers av linjer med regulære uttrykk kan være litt tricky..

 

Lurt å sette seg litt inn i regulære uttrykk, nyttig i alle språk:

http://perldoc.perl.org/perlrequick.html

 

Enda kke funnet et språk hvor reg.exps er bedre enn i perl ;)

Lenke til kommentar

eksempel:

 

#!/usr/bin/env perl

use strict;
use warnings;

my $html = '
<TR><script>draw_table(SUBFUN,"");</script>
   <TD class=FUNNAME2><script>document.write("Internet IP Address")</script>:</TD>
   123.444.789.555
   <TD class=FUNFIELD><B>123.456.789.123.456.777.123111</B></TD>
    wan_ip = 123.456.789.123 #only match this ip
</TR>
';

# forandret uttrykket litt, men regulære uttrykk er nesten umulige å lese  mye lettere å lage

# begynn enkelt, f.eks. men å matche "wan_ip" og ett tall :

#  "wan_ip" - 0 eller flere mellomrom - = - 0 eller flere mellomrom - capture group (så det havner i
#  $1) - ett eller flere tall
$html =~ /wan_ip\s*=\s*(\d+)/;
die "fant ikke \n" unless $1;
print "Fant: '$1'\n";

# samme, men ta med alle tallene. punktum betyr "ett av hvilke som helst tall" så _faktisk_ punktum
# må escapes:
$html =~ /wan_ip\s*=\s*(\d+\.\d+\.\d+\.\d+)/;
die "fant ikke \n" unless $1;
print "Fant: '$1'\n";

# samme, sagt på en annen måte:
#                       ett-eller-flere tall, 0 eller 1 punktum, hele forrige parrentes 4 ganger
$html =~ /wan_ip\s*=\s*((\d+\.?){4})/;
die "fant ikke \n" unless $1;
print "Fant: '$1'\n";

Endret av gizzlon
Lenke til kommentar

Ruteren min er en linksys 160N men den har bare funksjoner for 'DynDNS.org' og 'TZO.com' og ingen av dem er brukbare for min del da jeg har domene fra en annen leverandør 'Joker.com'

kan hente ut ip adressen fra en eller annen side også men det også blir uaktuelt da jeg helt sikkert vil bli bannet etter hvert hvis det kjøres regelmessige sjekk på ip.

 

Nå kjenner ikke jeg den aktuelle ruteren, men det kan være at du kan sette denne opp til å oppdatere IP for ditt domene i DNS hos joker.com, selv om det ikke er et av standardvalgene.

 

Slik jeg forstår det så poller du ruteren din hele tiden til å sjekke om den har fått ny IP adresse ved å se på IP i HTML koden på admin sidene på ruteren. Det beste er jo om ruteren selv kan gjøre dette for den vet eksakt når den fikk ny IP.

 

Men om du skal polle så kan du også gjøre dette på andre måter. Jeg vet ikke hviket OS du kjører og topologien på nettverket ditt, men jeg kan finne IP på min WAN interface ved å skrive:

 

/usr/sbin/tracepath -n www.vg.no | grep ^\ 2: | cut -d' ' -f4

 

På en av Linux maskinene mine. Nå lønner det seg sikkert å bruke noe som er nærere (færre hopp) deg enn VG og hos meg er WAN på den 2. hoppet. Dette vil virke uavhengig av rutertype så lenge du får lov å kjøre tracepath.

dcclient og tilsvarende verktøy kan sikkert også gjøre jobben for deg.

 

Enda kke funnet et språk hvor reg.exps er bedre enn i perl

 

Bedre kan bety mange ting, men de fleste språk har regexp bibliotek o.l. som har samme egenskaper og syntaks som man finner i Perl. Enkelte kompilerer til effektiv maskinkode. Når det er sagt så er ikke regex alltid den beste løsningen på alle problemer.

Lenke til kommentar

Kan funke å bruke det (tracepath eller traceroute?), men er vel ikke mere effektivt enn å "polle" websiden på routern?

 

Bedre kan bety mange ting, men de fleste språk har regexp bibliotek o.l. som har samme egenskaper og syntaks som man finner i Perl. Enkelte kompilerer til effektiv maskinkode.

Nja, de etterligner så godt de kan ;)

 

Er kommet en del nytt de siste årene i perl 5.10 etc som ser litt kult ut:

http://search.cpan.org/~rgarcia/perl-5.10.0-RC2/pod/perlsyn.pod#Smart_matching_in_detail

http://search.cpan.org/~rgarcia/perl-5.10.0/pod/perl5100delta.pod#Regular_expressions (heavy ;)

 

og i perl 6 lager de noe helt nytt:

http://en.wikipedia.org/wiki/Perl_6_rules

 

Når det er sagt så er ikke regex alltid den beste løsningen på alle problemer.

 

Helt klart, men nyttig til mye..

Lenke til kommentar

Kan funke å bruke det (tracepath eller traceroute?), men er vel ikke mere effektivt enn å "polle" websiden på routern?

 

Nei, det er ikke spesielt mer effektivt. Men det er litt enklere samt at man er uavhengig av ruter og hva evt. ruterleverandøren skulle gjøre av endringer på HTML koden med hensyn på firmwareoppgraderinger osv. Som sagt så ville det mest effektive om ruteren gjorde dette selv når den fikk ny IP.

 

Nja, de etterligner så godt de kan ;)

 

Regex har eksistert "siden tidenes morgen". Perl gjorde en del utvidelser som mange andre har adoptert og enkelte har samme funksjonalitet og har bedre ytelse enn tilsvarende i Perl.

 

Peter Seibel skriver:

For instance, the CL-PPCRE regular expression library, running in CMUCL, is faster than Perl's regular expression engine on some benchmarks, even though Perl's engine is written in highly tuned C

 

I CL-PPCRE så oversettes rexep strengene til symbolske uttrykk som igjen optimaliseres og kompileres. Man kan også skrive disse uttrykkene direkte, noe jeg ofte finner mer oversiktlig enn regex strenger. F.eks. så kan jeg skrive dette for å match space, tab, newline, return og " ":

(defparameter *white* 
 '(:ALTERNATION (:CHAR-CLASS #\Space #\Tab #\Newline #\Return) " "))

 

Det står forøvrig " " på slutten av koden ovenfor, men det blir byttet ut i

 formateringen. Kanskje en regex som matchet data 

 

og i perl 6 lager de noe helt nytt:

http://en.wikipedia....ki/Perl_6_rules

 

SEGs er ikke noe helt nytt. Det har eksistert bibliotek for diverse språk og lex/parser generatorer i lenger tid:

 

http://pdos.csail.mit.edu/~baford/packrat/

 

 

Helt klart, men nyttig til mye..

 

 

Larry Wall inrømmer selv at han har skapt en ukultur "In fact, regular expression culture is a mess, and I share some of the blame for making it that way"

 

Det er mange ganger jeg har sett Perl kode hvor alt er basert på regex som feiler under mange omstendigheter (man får match i kommentarer, spesielt kode som er kommentert ut, får match i strenger og annen embedded data osv.). I mange av disse tilfellene hadde det vært mye mer ryddig å bruke en lex'er og en parser generator som holder state informasjon. Man kan håndtere det med regex også, men det blir veldig komplisert og uoversiktlig. At Perl får støtte for dette nå vil forhåpentligvis hjelpe litt på regex ukulturen.

Lenke til kommentar

eksempel:

 

#!/usr/bin/env perl

use strict;
use warnings;

my $html = '
<TR><script>draw_table(SUBFUN,"");</script>
   <TD class=FUNNAME2><script>document.write("Internet IP Address")</script>:</TD>
   123.444.789.555
   <TD class=FUNFIELD><B>123.456.789.123.456.777.123111</B></TD>
    wan_ip = 123.456.789.123 #only match this ip
</TR>
';

# forandret uttrykket litt, men regulære uttrykk er nesten umulige å lese  mye lettere å lage

# begynn enkelt, f.eks. men å matche "wan_ip" og ett tall :

#  "wan_ip" - 0 eller flere mellomrom - = - 0 eller flere mellomrom - capture group (så det havner i
#  $1) - ett eller flere tall
$html =~ /wan_ip\s*=\s*(\d+)/;
die "fant ikke \n" unless $1;
print "Fant: '$1'\n";

# samme, men ta med alle tallene. punktum betyr "ett av hvilke som helst tall" så _faktisk_ punktum
# må escapes:
$html =~ /wan_ip\s*=\s*(\d+\.\d+\.\d+\.\d+)/;
die "fant ikke \n" unless $1;
print "Fant: '$1'\n";

# samme, sagt på en annen måte:
#                       ett-eller-flere tall, 0 eller 1 punktum, hele forrige parrentes 4 ganger
$html =~ /wan_ip\s*=\s*((\d+\.?){4})/;
die "fant ikke \n" unless $1;
print "Fant: '$1'\n";

 

Hei igjen

Har vært litt opptatt i det siste dagene så har ikke fått svart tidligere på forslagene

Men nå har det endelig løsnet takket være eksemplene og hjelpen som jeg har fått.

Måtte bytte litt om på regex syntaxen som jeg hadde men nå tar den ut eksakt det jeg er ute etter fra ruteren.

 

#!/usr/bin/perl
use LWP::UserAgent [search.cpan.org];
use HTTP::Request [kobesearch.cpan.org];
use strict;
use warnings;

my $URL = 'http://10.0.0.1/Status_Router.asp';

my $agent = LWP::UserAgent->new(env_proxy => 1,keep_alive => 1, timeout => 30);
my $header = HTTP::Request->new(GET => $URL);
my $request = HTTP::Request->new('GET', $URL, $header);
$request->authorization_basic('****', '****');
my $response = $agent->request($request);




#Andre forsøk for at innlogging skal registreres
   sleep(1);
  $response = "";

$agent = LWP::UserAgent->new(env_proxy => 1,keep_alive => 1, timeout => 30);
$header = HTTP::Request->new(GET => $URL);
$request = HTTP::Request->new('GET', $URL, $header);
$request->authorization_basic('****', '*****');
$response = $agent->request($request);




#Check the outcome of the response
if ($response->is_success){

my $tr_tag = $response->as_string;

$tr_tag =~ /var\s*wan_ip\s*=\s*["]\b([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\b["]/;
die "fant ikke \n" unless $1;
my $wan_ip = "$1.$2.$3.$4";


print "$wan_ip\n";


#if ($ip eq '0.0.0.0')
#{
#	print "Error";
#}
#else
#{
#	print "IP er: $ip \n";
#}







}elsif ($response->is_error){

} 

 

Nå er det værste gjort og resten av funksjonene har jeg intakt fra da jeg hadde en smootwall boks gående som ruter og oppdaterte seg ved behov.

 

Tusen takk for all hjelp

Lenke til kommentar

Opprett en konto eller logg inn for å kommentere

Du må være et medlem for å kunne skrive en kommentar

Opprett konto

Det er enkelt å melde seg inn for å starte en ny konto!

Start en konto

Logg inn

Har du allerede en konto? Logg inn her.

Logg inn nå
  • Hvem er aktive   0 medlemmer

    • Ingen innloggede medlemmer aktive
×
×
  • Opprett ny...