Solicitudes de Python y sesiones persistentes

119

Estoy usando el módulo de solicitudes (versión 0.10.0 con Python 2.5). He descubierto cómo enviar datos a un formulario de inicio de sesión en un sitio web y recuperar la clave de sesión, pero no veo una forma obvia de usar esta clave de sesión en solicitudes posteriores. ¿Alguien puede completar la elipsis en el código a continuación o sugerir otro enfoque?

>>> import requests
>>> login_data =  {'formPosted':'1', 'login_email':'[email protected]', 'password':'pw'}
>>> r = requests.post('https://localhost/login.py', login_data)
>>> 
>>> r.text
u'You are being redirected <a href="profilePage?_ck=1349394964">here</a>'
>>> r.cookies
{'session_id_myapp': '127-0-0-1-825ff22a-6ed1-453b-aebc-5d3cf2987065'}
>>> 
>>> r2 = requests.get('https://localhost/profile_data.json', ...)
ChrisGuest
fuente

Respuestas:

209

Puede crear fácilmente una sesión persistente usando:

s = requests.Session()

Después de eso, continúe con sus solicitudes como lo haría:

s.post('https://localhost/login.py', login_data)
#logged in! cookies saved for future requests.
r2 = s.get('https://localhost/profile_data.json', ...)
#cookies sent automatically!
#do whatever, s will keep your cookies intact :)

Para más información sobre sesiones: https://requests.kennethreitz.org/en/master/user/advanced/#session-objects

Anuj Gupta
fuente
4
¿Alguna forma de guardar la propia sesión entre ejecuciones de script?
Gtx
10
Puede pickle.dump cookies de sesión a un archivo como pickle.dump (session.cookies._cookies, file) y pickle.load a la sesión como sigue cookies = pickle.load (file) cj = request.cookies.RequestsCookieJar () cj._cookies = cookies y session.cookies = cj
Cyril
¿Qué pasa si involucro a un proxy?
brainLoop
1
Para las solicitudes enviadas a localhost, podría haber problemas con el inicio de sesión y otras cookies devueltas por el servidor web, si contienen un valor de propiedad de dominio incorrecto. Para localhost, el servidor web debe devolver cookies con la propiedad de dominio establecida en localhost.local, de lo contrario, la cookie no se aplicará a la sesión. En ese caso, use en 127.0.0.1lugar delocalhost
Sergey Nudnov
@SergeyNudnov Muchas gracias por su comentario. Perdí mucho tiempo tratando de averiguar por qué la sesión no maneja las cookies correctamente. Cambiar el dominio de localhost a localhost.local resolvió el problema. Gracias de nuevo.
Pulkownik
25

las otras respuestas ayudan a comprender cómo mantener dicha sesión. Además, quiero proporcionar una clase que mantenga la sesión mantenida en diferentes ejecuciones de un script (con un archivo de caché). Esto significa que un "inicio de sesión" adecuado solo se realiza cuando es necesario (tiempo de espera o no existe sesión en la caché). También es compatible con la configuración del proxy en las siguientes llamadas a "obtener" o "publicar".

Está probado con Python3.

Úselo como base para su propio código. Los siguientes fragmentos se publican con GPL v3

import pickle
import datetime
import os
from urllib.parse import urlparse
import requests    

class MyLoginSession:
    """
    a class which handles and saves login sessions. It also keeps track of proxy settings.
    It does also maintine a cache-file for restoring session data from earlier
    script executions.
    """
    def __init__(self,
                 loginUrl,
                 loginData,
                 loginTestUrl,
                 loginTestString,
                 sessionFileAppendix = '_session.dat',
                 maxSessionTimeSeconds = 30 * 60,
                 proxies = None,
                 userAgent = 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1',
                 debug = True,
                 forceLogin = False,
                 **kwargs):
        """
        save some information needed to login the session

        you'll have to provide 'loginTestString' which will be looked for in the
        responses html to make sure, you've properly been logged in

        'proxies' is of format { 'https' : 'https://user:pass@server:port', 'http' : ...
        'loginData' will be sent as post data (dictionary of id : value).
        'maxSessionTimeSeconds' will be used to determine when to re-login.
        """
        urlData = urlparse(loginUrl)

        self.proxies = proxies
        self.loginData = loginData
        self.loginUrl = loginUrl
        self.loginTestUrl = loginTestUrl
        self.maxSessionTime = maxSessionTimeSeconds
        self.sessionFile = urlData.netloc + sessionFileAppendix
        self.userAgent = userAgent
        self.loginTestString = loginTestString
        self.debug = debug

        self.login(forceLogin, **kwargs)

    def modification_date(self, filename):
        """
        return last file modification date as datetime object
        """
        t = os.path.getmtime(filename)
        return datetime.datetime.fromtimestamp(t)

    def login(self, forceLogin = False, **kwargs):
        """
        login to a session. Try to read last saved session from cache file. If this fails
        do proper login. If the last cache access was too old, also perform a proper login.
        Always updates session cache file.
        """
        wasReadFromCache = False
        if self.debug:
            print('loading or generating session...')
        if os.path.exists(self.sessionFile) and not forceLogin:
            time = self.modification_date(self.sessionFile)         

            # only load if file less than 30 minutes old
            lastModification = (datetime.datetime.now() - time).seconds
            if lastModification < self.maxSessionTime:
                with open(self.sessionFile, "rb") as f:
                    self.session = pickle.load(f)
                    wasReadFromCache = True
                    if self.debug:
                        print("loaded session from cache (last access %ds ago) "
                              % lastModification)
        if not wasReadFromCache:
            self.session = requests.Session()
            self.session.headers.update({'user-agent' : self.userAgent})
            res = self.session.post(self.loginUrl, data = self.loginData, 
                                    proxies = self.proxies, **kwargs)

            if self.debug:
                print('created new session with login' )
            self.saveSessionToCache()

        # test login
        res = self.session.get(self.loginTestUrl)
        if res.text.lower().find(self.loginTestString.lower()) < 0:
            raise Exception("could not log into provided site '%s'"
                            " (did not find successful login string)"
                            % self.loginUrl)

    def saveSessionToCache(self):
        """
        save session to a cache file
        """
        # always save (to update timeout)
        with open(self.sessionFile, "wb") as f:
            pickle.dump(self.session, f)
            if self.debug:
                print('updated session cache-file %s' % self.sessionFile)

    def retrieveContent(self, url, method = "get", postData = None, **kwargs):
        """
        return the content of the url with respect to the session.

        If 'method' is not 'get', the url will be called with 'postData'
        as a post request.
        """
        if method == 'get':
            res = self.session.get(url , proxies = self.proxies, **kwargs)
        else:
            res = self.session.post(url , data = postData, proxies = self.proxies, **kwargs)

        # the session has been updated on the server, so also update in cache
        self.saveSessionToCache()            

        return res

Un fragmento de código para usar la clase anterior puede verse así:

if __name__ == "__main__":
    # proxies = {'https' : 'https://user:pass@server:port',
    #           'http' : 'http://user:pass@server:port'}

    loginData = {'user' : 'usr',
                 'password' :  'pwd'}

    loginUrl = 'https://...'
    loginTestUrl = 'https://...'
    successStr = 'Hello Tom'
    s = MyLoginSession(loginUrl, loginData, loginTestUrl, successStr, 
                       #proxies = proxies
                       )

    res = s.retrieveContent('https://....')
    print(res.text)

    # if, for instance, login via JSON values required try this:
    s = MyLoginSession(loginUrl, None, loginTestUrl, successStr, 
                       #proxies = proxies,
                       json = loginData)
DomTomCat
fuente
6
Esta es una gran respuesta, es extrañamente difícil buscar esta solución también.
dualidad
¿No debería implementarse como parte del módulo de solicitud?
user1602
Utiliza el requestsmódulo. ¿Cómo lo implementaría como parte del módulo? o ¿cómo te refieres a @ user1602?
DomTomCat
17

Mira mi respuesta en esta pregunta similar:

python: urllib2 cómo enviar una cookie con una solicitud urlopen

import urllib2
import urllib
from cookielib import CookieJar

cj = CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
# input-type values from the html form
formdata = { "username" : username, "password": password, "form-id" : "1234" }
data_encoded = urllib.urlencode(formdata)
response = opener.open("https://page.com/login.php", data_encoded)
content = response.read()

EDITAR:

Veo que he recibido algunos votos negativos por mi respuesta, pero no hay comentarios explicativos. Supongo que es porque me refiero a las urllibbibliotecas en lugar de requests. Hago eso porque el OP pide ayuda requestso que alguien sugiera otro enfoque.

Morten Jensen
fuente
2
No soy uno de sus votantes en contra, pero como suposición, muchos lectores probablemente estén glosando la última oración del OP como "¿Puede alguien completar los puntos suspensivos en el código a continuación o sugerir otro enfoque [con la biblioteca de solicitudes que involucraría más cirugía a mi código que simplemente completar las elipses con otra cosa] ". - pero eso es solo una suposición de mi parte.
Brandon Rhodes
7
Como OP, puedo decir que su respuesta proporciona una alternativa útil. Aunque solo sea para demostrar que requestsofrece una solución simple y de alto nivel a un problema que de otro modo tomaría 3 bibliotecas para implementar.
ChrisGuest
7

La documentación dice que getincluye un cookiesargumento opcional que le permite especificar las cookies para usar:

de los documentos:

>>> url = 'http://httpbin.org/cookies'
>>> cookies = dict(cookies_are='working')

>>> r = requests.get(url, cookies=cookies)
>>> r.text
'{"cookies": {"cookies_are": "working"}}'

http://docs.python-requests.org/en/latest/user/quickstart/#cookies

dm03514
fuente
6

Después de probar todas las respuestas anteriores, descubrí que el uso de "RequestsCookieJar" en lugar del CookieJar normal para solicitudes posteriores solucionó mi problema.

import requests
import json

# The Login URL
authUrl = 'https://whatever.com/login'

# The subsequent URL
testUrl = 'https://whatever.com/someEndpoint'

# Logout URL
testlogoutUrl = 'https://whatever.com/logout'

# Whatever you are posting
login_data =  {'formPosted':'1', 
               'login_email':'[email protected]', 
               'password':'pw'
               }

# The Authentication token or any other data that we will receive from the Authentication Request. 
token = ''

# Post the login Request
loginRequest = requests.post(authUrl, login_data)
print("{}".format(loginRequest.text))

# Save the request content to your variable. In this case I needed a field called token. 
token = str(json.loads(loginRequest.content)['token'])  # or ['access_token']
print("{}".format(token))

# Verify Successful login
print("{}".format(loginRequest.status_code))

# Create your Requests Cookie Jar for your subsequent requests and add the cookie
jar = requests.cookies.RequestsCookieJar()
jar.set('LWSSO_COOKIE_KEY', token)

# Execute your next request(s) with the Request Cookie Jar set
r = requests.get(testUrl, cookies=jar)
print("R.TEXT: {}".format(r.text))
print("R.STCD: {}".format(r.status_code))

# Execute your logout request(s) with the Request Cookie Jar set
r = requests.delete(testlogoutUrl, cookies=jar)
print("R.TEXT: {}".format(r.text))  # should show "Request Not Authorized"
print("R.STCD: {}".format(r.status_code))  # should show 401
Jim Chertkov
fuente
1
¡Gracias @ jim-chertkov por esto! ¡Gran respuesta incluso años después! Pude usar y entender esto.
JayRizzo
2

fragmento para recuperar datos json, protegido con contraseña

import requests

username = "my_user_name"
password = "my_super_secret"
url = "https://www.my_base_url.com"
the_page_i_want = "/my_json_data_page"

session = requests.Session()
# retrieve cookie value
resp = session.get(url+'/login')
csrf_token = resp.cookies['csrftoken']
# login, add referer
resp = session.post(url+"/login",
                  data={
                      'username': username,
                      'password': password,
                      'csrfmiddlewaretoken': csrf_token,
                      'next': the_page_i_want,
                  },
                  headers=dict(Referer=url+"/login"))
print(resp.json())
Nulo
fuente
1

Guarde solo las cookies necesarias y reutilícelas.

import os
import pickle
from urllib.parse import urljoin, urlparse

login = '[email protected]'
password = 'secret'
# Assuming two cookies are used for persistent login.
# (Find it by tracing the login process)
persistentCookieNames = ['sessionId', 'profileId']
URL = 'http://example.com'
urlData = urlparse(URL)
cookieFile = urlData.netloc + '.cookie'
signinUrl = urljoin(URL, "/signin")
with requests.Session() as session:
    try:
        with open(cookieFile, 'rb') as f:
            print("Loading cookies...")
            session.cookies.update(pickle.load(f))
    except Exception:
        # If could not load cookies from file, get the new ones by login in
        print("Login in...")
        post = session.post(
            signinUrl,
            data={
                'email': login,
                'password': password,
            }
        )
        try:
            with open(cookieFile, 'wb') as f:
                jar = requests.cookies.RequestsCookieJar()
                for cookie in session.cookies:
                    if cookie.name in persistentCookieNames:
                        jar.set_cookie(cookie)
                pickle.dump(jar, f)
        except Exception as e:
            os.remove(cookieFile)
            raise(e)
    MyPage = urljoin(URL, "/mypage")
    page = session.get(MyPage)
user1602
fuente
0

Esto funcionará para usted en Python;

# Call JIRA API with HTTPBasicAuth
import json
import requests
from requests.auth import HTTPBasicAuth

JIRA_EMAIL = "****"
JIRA_TOKEN = "****"
BASE_URL = "https://****.atlassian.net"
API_URL = "/rest/api/3/serverInfo"

API_URL = BASE_URL+API_URL

BASIC_AUTH = HTTPBasicAuth(JIRA_EMAIL, JIRA_TOKEN)
HEADERS = {'Content-Type' : 'application/json;charset=iso-8859-1'}

response = requests.get(
    API_URL,
    headers=HEADERS,
    auth=BASIC_AUTH
)

print(json.dumps(json.loads(response.text), sort_keys=True, indent=4, separators=(",", ": ")))
Dasitha Abeysinghe
fuente