Javascript Array aus Webseite parsen

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
fuku
User
Beiträge: 7
Registriert: Montag 24. März 2008, 00:23

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?
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.
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

pypy hat eine JavaScript Parser. Den sollte man mit ein wenig Bastelei auch ohne dem pypy Rest verwenden können.
TUFKAB – the user formerly known as blackbird
fuku
User
Beiträge: 7
Registriert: Montag 24. März 2008, 00:23

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
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

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.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
fuku
User
Beiträge: 7
Registriert: Montag 24. März 2008, 00:23

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)
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)
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Es muss in Zeile 5

Code: Alles auswählen

word = Word(string.uppercase + string.lowercase)
lauten. Außerdem fehlt dir in Zeile 9 noch ein

Code: Alles auswählen

+ Literal(")")
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
fuku
User
Beiträge: 7
Registriert: Montag 24. März 2008, 00:23

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?
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

"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)
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.
fuku
User
Beiträge: 7
Registriert: Montag 24. März 2008, 00:23

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

Code: Alles auswählen

var name = new array('bla', [1, 2, 3])
funktioniert

Code: Alles auswählen

var name = new array('foo', [1, 2, ['bar', 'bar']])
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?
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())
fuku
User
Beiträge: 7
Registriert: Montag 24. März 2008, 00:23

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?).
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.
Antworten