Utiliser le module requests pour envoyer des infos en POST

L’auteur de ce sujet a trouvé une solution à son problème.
Auteur du sujet

Bonsoir,

Je me suis mis/remis au python dernièrement, et pour m’entraîner, je voulais écrire un script pour me simplifier l’utilisation d’un site que j’utilise régulièrement, https://tatoeba.org.

L’idée que j’avais, c’était de pouvoir avoir un fichier texte avec une phrase par ligne, et d’automatiser l’envoi. De cette manière, je pourrai contribuer même sans internet.

Pour commencer, de manière la plus simple possible, j’ai juste une phrase en dur dans le code. J’ai décidé d’utiliser le module requests, qui semble permettre de faire ce que je veux. Je me suis renseigné un peu auprès du créateur du site, et apparement, les phrases sont ajoutées sur l’url https://tatoeba.org/swe/sentences/add_an_other_sentence en méthode POST avec les paramètres suivants :

  • selectedLang: ISO code de la langue de la phrase
  • value: texte de la phrase

De là, j’ai écrit quelques lignes, ainsi

import requests

sentence = "Je ne sais pas programmer."
tato_url = "https://tatoeba.org/swe/sentences/add_an_other_sentence"
selectedLang="fra"
value=sentence
data={'selectedLang':selectedLang,'value':value}

r=requests.post(url=tato_url,data=data)

En faisant print(r.url), j’obtiens https://tatoeba.org/fra/users/login, que j’interprête comme quoi il faut que j’envoie un cookie.

L’ennui, c’est que je ne suis pas sûr que ce soit le soucis, et que je ne parviens pas à trouver le cookie en question. Il y en a 6, dont un qui s’appelle CakeCookie[User], mais son contenu est une longue chaîne de caractères, mon pseudo ne semble pas y être. Une fois trouvé, j’imagine qu’il me suffira d’ajouter/modifier ces lignes.

cookies=dict(MyUsername="Username",MyPassword="password")
r=requests.post(url=tato_url,data=data,cookies=cookies)

Mes questions:

  1. Est-ce que je me trompe du tout au tout ?
  2. Comment faire pour le cookie ?
  3. Y a-t-il une manière plus simpe ou plus pythonesque de faire ?
  4. Aurais-je du poster dans le forum de dev web ?

Merci !

Édité par Rockaround

Cette réponse a aidé l’auteur du sujet

Alors, je ne suis pas sûr que ça réponde à ta question, mais j’ai déjà fait un script pour aller chercher mes notes de cours sur un site où il faut être authentifié et je suis passé par ces étapes :

  1. Demander à requests que les requêtes soient faites dans une même session (c’est-à-dire en gardant les cookies)
  2. Se connecter en envoyant une requête POST sur la page d’authentification (pour toi, https://tatoeba.org/fra/users/login) avec ses identifiants
  3. Faire mes requêtes
+2 -0
Auteur du sujet

Merci, c’est exactement le type de réponse qui m’aide.

J’ai donc été lire un peu la doc sur requests.Session(), et même si tout n’est pas clair pour moi, je crois comprendre le principe.

Ensuite, j’ai regardé le code source de la page de login. Déjà, il semblerait que le formulaire renvoie vers une autre page action="/fra/users/check_login". C’est sur celle-là qu’il faut que j’envoie mes infos?

Le second point, c’est que je ne trouve pas le nom des variables que je dois envoyer. Si je regarde le code des champs, ça donne

<input name="data[User][username]" maxlength="20" type="text" id="UserUsername" required="required" class="md-input">

Normalement, si j’ai tout suivi, c’est ce name qui est envoyé. Mais là, ça ne semble pas être une variable classique. Qu’est-ce ? Et comment puis-je l’utiliser dans mon petit script ?

Merci !

Cette réponse a aidé l’auteur du sujet

Ensuite, j’ai regardé le code source de la page de login. Déjà, il semblerait que le formulaire renvoie vers une autre page action="/fra/users/check_login". C’est sur celle-là qu’il faut que j’envoie mes infos?

Oui, c’est exact !

Normalement, si j’ai tout suivi, c’est ce name qui est envoyé. Mais là, ça ne semble pas être une variable classique. Qu’est-ce ? Et comment puis-je l’utiliser dans mon petit script ?

Si je ne me trompe pas, ça permet que les valeurs de data[User][username] et data[User][password] soient automatiquement transformées en un dictionnaire ou une autre structure de données :

data = {
    'User': {
        'username': '',
        'password': ''
    }
}

Mais pour toi ça ne change strictement rien par rapport à une variable classique foo-bar. :)


En fait pour garder la même session, il faut commencer par écrire ça au début du fichier :

import requests

session = requests.session()

Puis ensuite tu peux faire tes requêtes GET ou POST :

session.get(url)
session.post(url, data)

Pour une requête POST, url correspond à la valeur de l’attribut action de la balise <form> (enfin pas exactement, il faut rajouter la partie https://tatoeba.org) et data est un dictionnaire qui contient chaque attribut name avec sa valeur. Pour toi ça va donner quelque chose comme ça :

url = 'https://tatoeba.org/fra/users/check_login'
data = {
    'data[User][username]': '...',
    'data[User][password]': '...',
    '...': '...'
}

Une difficulté que tu vas probablement rencontrer est que dans le formulaire il y a un champ caché qui contient un token. Ce token est unique et, si le site web est bien fait, il est nécessaire de l’avoir pour envoyer la requête :

<input type="hidden" name="data[_Token][key]" value="fc40bc57dab6cd58ecdcd7ed634b6896c10d64d7a2e338d685514cf318f0a87488392f0548fd1831a4350104176645629b5e6d4eb12834607c27980617d8c2ca" id="Token764596425"/>

Il va donc falloir faire ces étapes pour se connecter :

  1. Envoyer une requête sur https://tatoeba.org/fra/users/login
  2. Trouver la valeur de data[_Token][key] dans le formulaire de connexion (attention, il y a plusieurs formulaires sur cette page)
  3. Envoyer ta requête POST avec la valeur de data[_Token][key] et tes identifiants

Édité par Situphen

+1 -0
Auteur du sujet

Encore une fois merci pour la réponse. Je n’avais pas du tout vu l’input caché, merci.

Maintenant, j’arrive à faire ma requête pour récupérer la clé, pas de soucis, mais ça ne marche toujours pas. Je copie mon code en dessous (password a bien entendu été changé, mais il est correct dans le script, vérifié).

import requests

#Phrase à insérer
sentence = "Un réacteur nucléaire produit autant d'énergie que plus de mille éoliennes."

#Différentes url utiles
login_url = "https://tatoeba.org/fra/users/login"
check_login_url = "https://tatoeba.org/fra/users/check_login"
add_sentence_url ="https://tatoeba.org/fra/sentences/add_an_other_sentence/"

#Arguments pour ajouter une phrase
selectedLang="fra"
value=sentence
data={'selectedLang':selectedLang,'value':value}

#Création de ma session
s=requests.Session()

#Récupération de la page pour pouvoir en extraire la clé cachée.
r0=s.get(url=login_url)

#Ça semble marcher, mais peut-être pas du tout pythonique. Je prends les conseils.
for line in r0.text.split("\n"):
    if (line.find("hidden")!=-1 and line.find("UserLoginForm")!=-1):
        hiddenkeyin=line
for attribute in hiddenkeyin.split():
    if "value" in attribute:
        key=attribute[7:-1]

#Création de l'argument pour se connecter
connexion={'data[User][username]':"Rockaround",'data[User][password]':"password",'data[_Token][key]':key}

#quelques tests
r1=s.post(url=check_login_url,data=connexion)
r2=s.post(url=login_url,data=connexion)
r=s.post(url=add_sentence_url,data=data)

r1 et r2 retourne une erreur 400.

r retourne un code 200, mais avec une redirection vers la page de login.

Il semblerait donc que je me trompe toujours. Ai-je bien géré les data[User][username] ? Se pourrait-il que la clé cachée ait changée entre mon GET et mon POST ?

En tout cas, j’aime bien ta manière de répondre : elle aide, mais elle me laisse réfléchir et (avec du bol) progresser. Là par exemple, j’ai mis un bon moment à comprendre que je ne pouvais pas récupérer la clé cachée dans le code source et l’inscrire en dur dans le script. Par contre, je ne suis pas sûr de voir l’intérêt de cette clé, si on peut de toute façon la récupérer.

Cette réponse a aidé l’auteur du sujet

#Ça semble marcher, mais peut-être pas du tout pythonique. Je prends les conseils.
for line in r0.text.split("\n"):
    if (line.find("hidden")!=-1 and line.find("UserLoginForm")!=-1):
        hiddenkeyin=line
for attribute in hiddenkeyin.split():
    if "value" in attribute:
        key=attribute[7:-1]

Es-tu sûr de bien récupérer le value de <input type="hidden" name="data[_Token][key]" value="a0e7e962ecd015f798911dc8f1a0798c38a6140c5187874654c15aa0211398f934890dcb165840c7d262760058a061619dcd4771fe06f62b5af484926bb12cee" id="Token860200558"/> et non pas <input type="hidden" name="_method" value="POST"/> qui est sur la même ligne ?

Ta méthode est assez rudimentaire mais en l’améliorant un peu ça doit pouvoir faire le job. Si tu veux plus de facilité tu peux passer par BeautifulSoup qui permet de rechercher facilement une balise HTML puis de chercher parmi ses balises enfant. Par exemple tu pourrais rechercher le <form id="UserLoginForm"> puis recherche le <input name="data[_Token][key]"> à l’intérieur de ce formulaire, comme ça tu es sûr d’avoir la bonne balise. Ceci dit, c’est peut-être prendre un bazooka pour tuer une mouche.

Par contre, je ne suis pas sûr de voir l’intérêt de cette clé, si on peut de toute façon la récupérer.

Je cite la page Wikipédia sur les attaques CSRF :

Utiliser des jetons de validité dans les formulaires : faire en sorte qu’un formulaire posté ne soit accepté que s’il a été produit quelques minutes auparavant : le jeton de validité en sera la preuve. Le jeton de validité doit être transmis en paramètre et vérifié côté serveur.

Wikipédia

Édité par Situphen

+1 -0
Auteur du sujet

Édit : c’est résolu ! Voir plus bas. Merci !

Merci pour la réponse.

Es-tu sûr

Ça semble ok. En dessous, hiddenkeyin

<form action="/fra/users/check_login" id="UserLoginForm" method="post" accept-charset="utf-8"><div style="display:none;"><input type="hidden" name="_method" value="POST"/><input type="hidden" name="data[_Token][key]" value="1ab777751d5c524c9f85230338119dc06be0258e9493a890dd940b0195c07549606ebddda67f8caa7d92dab6c5011b7b22f7b4126bcc57526b1932287c44733d" id="Token1963811480"/></div><div md-whiteframe="1" id="login-form">

Et ici, key

1ab777751d5c524c9f85230338119dc06be0258e9493a890dd940b0195c07549606ebddda67f8caa7d92dab6c5011b7b22f7b4126bcc57526b1932287c44733d

Donc d’après l’explication wikipedia, cette clé reste valide quelques minutes. La question que je me pose est donc si elle change entre ma requête GET pour l’isoler, et ma requête POST qui l’envoie. Je suis tenté de dire que oui, et que c’est le soucis, mais je n’en sais rien.

Pour BeautifulSoup, c’est bon à savoir, mais puisque j’essaie d’apprendre, je vais me concentrer sur requests pour l’instant. Déjà que mon python de base est pas génial… Mais merci pour l’information !

Et merci encore pour l’aide.


Édit : J’ai réussi ! En gros, il fallait que je mette tous les inputs, même ceux a priori inutiles (rememberMe) et les deux cachés. Ça donne (sans commentaires cette fois, c’est presque le même code)

import requests

sentence = "Un réacteur nucléaire produit autant d'énergie que plus de mille éoliennes."

login_url = "https://tatoeba.org/swe/users/login"
check_login_url = "https://tatoeba.org/swe/users/check_login"
add_sentence_url ="https://tatoeba.org/swe/sentences/add_an_other_sentence/"
selectedLang="fra"
value=sentence
data={'selectedLang':selectedLang,'value':value}

s=requests.Session()

r0=s.get(url=login_url)

for line in r0.text.split("\n"):
    if (line.find("hidden")!=-1 and line.find("UserLoginForm")!=-1):
        hiddenkeyin=line
    if (line.find("hidden")!=-1 and line.find("TokenFields")!=-1):
        Tokenline=line
for attribute in hiddenkeyin.split():
    if "value" in attribute:
        key=attribute[7:-1]
for attribute in Tokenline.split():
    if "value" in attribute:
        token=attribute[7:-1]
        break

connexion={'data[User][username]':"Rockaround",'data[User][password]':"password",'data[_Token][key]':key,'data[User][rememberMe]':"0",'data[_Token][fields]':token,'data[_Token][unlocked]':''}

r1=s.post(url=check_login_url,data=connexion)
r=s.post(url=add_sentence_url,data=data)

print(r1.url)
print(r1)
print(r.url)
print(r)

Un très gros merci pour l’aide. J’aurais pu chercher très longtemps sinon. :)

Édité par Rockaround

Cette réponse a aidé l’auteur du sujet

Ah, c’est peut-être parce que tu n’envoies pas <input type="hidden" name="_method" value="POST"/> dans ta requête POST, et donc ça fait une erreur 400. Regarde aussi s’il n’y a pas d’autres champs que tu ne mets pas dans ta requête POST.

Donc d’après l’explication wikipedia, cette clé reste valide quelques minutes. La question que je me pose est donc si elle change entre ma requête GET pour l’isoler, et ma requête POST qui l’envoie. Je suis tenté de dire que oui, et que c’est le soucis, mais je n’en sais rien.

Je ne vois pas pourquoi elle changerait. En fait, avec ton script tu fais le même comportement que lorsque tu te connectes avec ton navigateur, tu fais une requête GET sur la page de connexion, puis tu fais une requête POST sur la page d’authentification du formulaire.

+1 -0
Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte