Blog of :/blog/weboob/Browser2_:_Les_formulaires.html

Browser2 : Les formulaires

Cet article fait partie d'une série sur le Browser2.

Mechanize offre la possibilité de remplir et de soumettre le formulaire d'une page assez facilement, mais souffre de nombreux problèmes. Prenons par exemple ce code issu du module ING :

def login(self, password):
    # ...
    self.browser.select_form('mrc')
    self.browser.set_all_readonly(False)
    self.browser.controls.append(ClientForm.TextControl('text', 'mrc:mrg', {'value': ''}))
    self.browser.controls.append(ClientForm.TextControl('text', 'AJAXREQUEST', {'value': ''}))
    self.browser['AJAXREQUEST'] = '_viewRoot'
    self.browser['mrc:mrldisplayLogin'] = vk.get_string_code(realpasswd)
    self.browser['mrc:mrg'] = 'mrc:mrg'
    self.browser['sens'] = ['1']
    self.browser.submit()

On peut noter les choses suivantes :

  • La sélection se fait par le browser (à partir du nom du formulaire)
  • Ce système est stateful, c'est à dire qu'on sélectionne un formulaire, puis on change des attributs du browser pour définir les valeurs
  • Par défaut, mechanize respecte les contraintes de la page, empêchant par exemple de modifier les champs désactivés ou les <input type="hidden">. Il est nécessaire d'appeler set_all_readyonly(False) pour contourner ça
  • Le parsing des pages est mal foutu, ce qui fait qu'il loupe parfois des champs. On est alors obligé de les rajouter nous-mêmes
  • Lorsque le champ est à valeurs multiples (un <select> par exemple), on doit passer une liste plutôt qu'une chaîne, et il gueule si il n'y a pas de champ <option> ayant cette valeur

Enfin, si le formulaire n'a pas de nom, il n'est possible que de passer un prédicat de ce genre :

self.browser.select_form(predicate=lambda x: x.attrs.get('id','')=='setInfosCGS')

Browser2 à la rescousse

L'approche du nouveau système est un peu différente. On décorrèle les formulaires du browser en rajoutant une méthode get_form() à la page, qui retourne un objet Form sur lequel on peut modifier et rajouter des champs sans restriction. On réécrirait le code ci-dessus comme suit :

def login(self, password):
    # ...
    form = self.get_form(name='mrc')
    form['AJAXREQUEST'] = '_viewRoot'
    form['mrc:mrldisplayLogin'] = vk.get_string_code(realpasswd)
    form['mrc:mrg'] = 'mrc:mrg'
    form['sens'] = '1'
    form.submit()

La méthode get_form accepte également un paramètre xpath qui, comme son nom l'indique, est une chaîne xpath :

form = self.get_form(xpath='//form[@id="setInfosCGS"]')

Liens vers la documentation