Seite 1 von 1
Javascript Array aus Webseite parsen
Verfasst: Montag 24. März 2008, 00:38
von fuku
Hallo wie kann ich verschiedene Javascript Arrays (var a=new Array(...), etc...) aus einer Webseite (mit mechanize.Browser() geladen) in Python parsen?
Was ist da die eleganteste Methode?
Verfasst: Montag 24. März 2008, 07:42
von BlackJack
Wenn denen allen gemeinsam ist, dass die Daten zwischen 'Array(' und ')' und in den Daten selber keine ')' vorkommen, dann könnte man in einem ersten Schritt diese Informationen heraus ziehen. Dann kommt es darauf an, wie der Inhalt des Arrays aussieht.
Verfasst: Montag 24. März 2008, 18:42
von mitsuhiko
pypy hat eine JavaScript Parser. Den sollte man mit ein wenig Bastelei auch ohne dem pypy Rest verwenden können.
Verfasst: Montag 24. März 2008, 23:37
von fuku
hey danke für eure antworten.
werde die 3-4 arrays jetzt wohl irgendwie manuell parsen.
Das mit pypy wär ne gute idee wenn ich mehr javascript zu verarbeiten hätte.
btw, 2. platz bei google, das ging aber schnell
http://www.google.de/search?q=python+javascript+parsen
Verfasst: Montag 24. März 2008, 23:43
von Leonidas
fuku hat geschrieben:werde die 3-4 arrays jetzt wohl irgendwie manuell parsen.
Das mit pypy wär ne gute idee wenn ich mehr javascript zu verarbeiten hätte.
Du kannst entweder eine kleine EBNF aufstellen fürs Array oder so Lösungen wie Pyparsing verwenden. Das sollte recht einfach zu machen sein.
Verfasst: Dienstag 25. März 2008, 02:13
von fuku
ok, ich probiers mal mit pyparsing, eine grammatik für arrays zu erstellen ist nicht schwer.
jedoch habe ich so meine probleme das in pyparsing umzusetzen.
soweit bin ich bisher gekommen, jedoch bricht das program beim variablennamen des arrays ab (pyparsing.ParseException: Expected W:(ABCD...,abcd...) (at char 4), (line:1, col:5))
Was ist denn da falsch gelaufen. "name" gehört eigentlich zu word.
Nunja ich geh jetzt schlafen. Gute Nacht =)
Code: Alles auswählen
#!/usr/bin/python
from pyparsing import *
exp = "var name=new Array('testtest', 3345434343,34543543543,'test',3232323);"
word = Word(string.uppercase, string.lowercase)
string = Literal("'") + word + Literal("'")
digit = Word("0123456789")
var = string | digit
bracket = Literal("(") + var + Optional(OneOrMore(Literal(",")+var))
array = Literal("var ") + word + Literal("=new Array") + bracket + Literal(";")
print array.parseString(exp)
Verfasst: Dienstag 25. März 2008, 09:51
von BlackJack
@fuku: So wie Du `word` definiert hast muss es mit einem Grossbuchstaben beginnen und darf danach nur noch Kleinbuchstaben enthalten. Und das trifft auf 'name' nunmal nicht zu.
`pyparsing` kümmert sich im Normalfall selbst um Leerzeichen, die sollte man also in der Grammatik nicht angeben.
Dann hast Du die schliessende Klammer vor dem Semikolon in der Grammatik vergessen.
Code: Alles auswählen
from pyparsing import (alphas, delimitedList, Group, Literal, nums, OneOrMore,
Optional, removeQuotes, sglQuotedString, Suppress, Word)
def main():
source = ("var name=new Array('testtest',"
"3345434343,34543543543,'test',3232323);\n"
"var foo = new Array ( 'answer', 42 ) ;")
name = Word(alphas)
string = sglQuotedString
digit = Word(nums)
value = string | digit
bracketed = Suppress('(') + Group(delimitedList(value)) + Suppress(')')
array = (Suppress('var') + name
+ Suppress(Literal('=') + 'new' + 'Array')
+ bracketed + Suppress(';'))
print array.searchString(source)
Verfasst: Dienstag 25. März 2008, 10:15
von sma
Es muss in Zeile 5
lauten. Außerdem fehlt dir in Zeile 9 noch ein
Du hast mit diesem Ansatz aber noch einen weiten Weg vor dir, denn jedes Element des Array-Konstruktors kann ja beliebig komplex sein. Willst du vielleicht einfach nur JSON parsen? Dafür gibt es ein
Beispiel für Pyparsing.
Ansonsten, ein JavaScript-Parser in Python, der einen AST bauen kann, wäre schon eine schicke Sache. Du könntest ihn dann ja vielleicht als weiteres Beispiel für Pyparsing einreichen. Ich fand bislang die Doku von Pyparsing nur so schlecht und den Quelltext sehr unübersichtlich, sodass ich nicht abschätzen kann, was für ein Aufwand das wäre.
Ansonsten? Kann nicht auch
ANTLR Python-Code erzeugen? Da gibt es schon eine fertige
Grammatik für EMCAScript 3. Vielleicht gibt es ja noch einen anderen
Python-basierten Parsergenerator, der JavaScript als Beispiel mitbringt.
Ganz nett sieht
Aperiot, insbesondere gefällt mir, dass dieser Parsergenerator (auf wenn's für deinen Fall unwichtig ist) indent/dedent-Token synthetisieren kann.
Ansonsten, CoCo/R ist ein netter kleiner Parsergenerator für LL(1)-Parser, für den es offenbar auch einen
Python-Port gibt. Da könnte man diese etwas ältere
JavaScript LL(1)-Grammatik einfüttern, die allerdings nicht konform zur aktuellen Spec ist, weil sie nicht richtig mit ";" umgeht. LL(1)-Grammatiken einfach genug, dass man dafür auch manuell einen rekursiv absteigenden Parser bauen kann, das fummeln mit CoCo/R zu umständlich ist.
Stefan
Verfasst: Freitag 11. April 2008, 21:09
von fuku
Hallo Leute,
der Code funktioniert, danke.
Momentan sieht die Function folgendermaßen aus:
Code: Alles auswählen
def parse(self, source):
def correctList(lst):
cor = {}
for item in lst:
cor[item[0]] = list(conv(elem) for elem in item[1])
return cor
def conv(x):
try:
return int(x)
except ValueError:
return x
name = Word(alphas)
string = quotedString
digit = Word(nums)
value = string | digit
squareBracket = Suppress('[') + Group(delimitedList(value)) + Suppress(']')
value = string | digit | squareBracket
bracketed = Suppress('(') + Group(delimitedList(value)) + Suppress(')')
array = (Suppress('var') + name
+ Suppress(Literal('=') + 'new' + 'Array')
+ bracketed + Suppress(';'))
return correctList(array.searchString(source))
die correctList function habe ich eingebaut, da die list die mir diese Funktion geliefert hat jeweils Indexnamen und Werte zusammen in einer Liste angezeigt hat und Integer als Strings angezeigt wurden.
Außerdem muss ich die Korrekturfunktion so erweitern, dass von den Strings die Anführungszeichen gelöscht werden.
Ist das so ok oder kann ich das mittels pyparsing eleganter hinbekommen?
Verfasst: Freitag 11. April 2008, 21:22
von EyDu
"correctList" könntest du noch etwas vereinfachen:
Code: Alles auswählen
def correct_list(lst):
return dict((k, map(conv, v)) for (k, v) in lst)
Verfasst: Samstag 12. April 2008, 09:56
von BlackJack
Mit `parseAction()` kann man die Konvertierungen gleich in den Parser einbauen.
Code: Alles auswählen
def parse(source):
name = Word(alphas)
string = quotedString.setParseAction(removeQuotes)
digit = Word(nums).setParseAction(lambda toks: int(toks[0]))
value = string | digit
squareBracket = Suppress('[') + Group(delimitedList(value)) + Suppress(']')
value = string | digit | squareBracket
bracketed = Suppress('(') + Group(delimitedList(value)) + Suppress(')')
array = (Suppress('var') + name
+ Suppress(Literal('=') + 'new' + 'Array')
+ bracketed + Suppress(';'))
return dict(array.searchString(source).asList())
Warum gibt's bei Dir ein `self`-Argument, das gar nicht verwendet wird?
Ausserdem ist `value` unschön. Du definierst das zweimal, d.h. es hat vor der zweiten Zusweisung eine andere Bedeutung als nachher.
Verfasst: Dienstag 6. Mai 2008, 22:06
von fuku
Danke Jungs für die Verbesserungen.
@BlackJack:
Das self-Argument ist bei den Parametern weil die Funktion Teil einer Klasse ist.
Die doppelte value Zuweisung habe ich eingebaut, damit ich auf verschachtelte Arrays zugreifen kann, jedoch gefällt mir das selbst nicht.
Optimal ist es auch nicht, da es momentan nur auf eine Tiefe zugreifen kann
funktioniert
nicht
Code: Alles auswählen
class Parse:
def parse(self, source):
name = Word(alphas)
string = quotedString.setParseAction(removeQuotes)
digit = Word(nums).setParseAction(lambda toks: int(toks[0]))
value = string | digit
for i in range(2):
squareBracket = Suppress('[') + Group(delimitedList(value)) + Suppress(']')
value = string | digit | squareBracket
bracketed = Suppress('(') + Group(delimitedList(value)) + Suppress(')')
array = (Suppress('var') + name + Suppress(Literal('=') + 'new' + 'Array')
+ bracketed + Suppress(';'))
return dict(array.searchString(source).asList())
mit der for Schleife kann ich die Tiefe zwar einstellen, so richtig elegant ist das aber nicht.
Nochwas: Falls eines der Sub-Arrays leer ist funktioniert das Parsen nicht, da delimitedList mind. einen Treffer erwartet.
Gibt es eine passende Funktion die auch bei einem leeren Inhalt parst oder muss ich irgendwas mit ZeroOrMore basteln?
Verfasst: Mittwoch 7. Mai 2008, 07:56
von BlackJack
@fuku: Nächste Frage: Warum ist die Funktion teil einer Klasse, wenn gar nicht auf das Objekt zugegriffen wird? Ist oft ein Zeichen, dass man Java oder C# in Python programmiert.
Beliebig tief verschachteln kann man, indem man die Grammatik rekursiv angibt. Dazu benutzt man für ein Element der Grammatik erst einmal einen Platzhalter (`Forward`) und legt die eigentliche Grammatikregel erst fest (mit ``<<``), wenn alle nötigen Einzelteile mit dem Platzhalter definiert sind.
Leere Arrays und Unter-Arrays würde ich mit `Optional` realisieren.
Code: Alles auswählen
def parse(source):
name = Word(alphas)
string = quotedString.setParseAction(removeQuotes)
digit = Word(nums).setParseAction(lambda toks: int(toks[0]))
value = Forward()
values = Group(Optional(delimitedList(value)))
bracketed = Suppress('[') + values + Suppress(']')
value << (string | digit | bracketed)
paranthesed = Suppress('(') + values + Suppress(')')
array = (Suppress('var') + name
+ Suppress(Literal('=') + 'new' + 'Array')
+ paranthesed + Suppress(';'))
return dict(array.searchString(source).asList())
Verfasst: Mittwoch 7. Mai 2008, 11:28
von fuku
Hey BlackJack, danke für deine Hilfe.
Zu deiner Frage: Welches Objekt meinst du?
Meinst du, dass diese Funktion auf keine anderen Werte und Funktionen der Instanz zugreift und deswegen nichts in einer Klasse zu suchen hat?
Ich muss zugeben, dass ich im Bezug auf OOP noch Aufholbedarf habe.
Deswegen wäre es cool, wenn du mir sagen würdest, wie ich das besser machen kann (Soll die Funktion aus der Klasse ausgekapselt und seperat aufgerufen werden?).
Verfasst: Mittwoch 7. Mai 2008, 12:33
von BlackJack
@fuku: Das meinte ich. Wobei es trotzdem Gründe geben kann, so etwas in eine Klasse zu stecken. Dann würde ich aber eine `staticmethod` daraus machen, um dem Leser deutlicher zu machen, dass es eigentlich eine Funktion ist, die da im Namensraum einer Klasse steckt.