Solicitações Python e sessões persistentes

120

Estou usando o módulo de solicitações (versão 0.10.0 com Python 2.5). Eu descobri como enviar dados para um formulário de login em um site e recuperar a chave de sessão, mas não consigo ver uma maneira óbvia de usar essa chave de sessão em solicitações subsequentes. Alguém pode preencher as reticências no código abaixo ou sugerir outra abordagem?

>>> 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
fonte

Respostas:

210

Você pode criar facilmente uma sessão persistente usando:

s = requests.Session()

Depois disso, continue com suas solicitações como faria:

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 mais informações sobre as sessões: https://requests.kennethreitz.org/en/master/user/advanced/#session-objects

Anuj Gupta
fonte
4
Alguma maneira de salvar a própria sessão entre a execução do script?
Gtx
10
Pode pickle.dump cookies de sessão para um arquivo como pickle.dump (session.cookies._cookies, file) e pickle.load para a sessão como segue cookies = pickle.load (arquivo) cj = requests.cookies.RequestsCookieJar () cj._cookies = cookies e session.cookies = cj
Cyril
e se eu envolver proxy?
brainLoop 01 de
1
Para as solicitações enviadas para localhost, pode haver problemas com o login e outros cookies retornados pelo servidor web, se eles contiverem um valor de propriedade de domínio incorreto. Para localhost, o servidor web deve retornar cookies com propriedade de domínio definida como localhost.local, caso contrário, o cookie não será aplicado à sessão. Nesse caso, use em 127.0.0.1vez delocalhost
Sergey Nudnov
@SergeyNudnov Muito obrigado pelo seu comentário Perdi muito tempo tentando descobrir por que a sessão não lida com cookies corretamente. Alterar o domínio de localhost para localhost.local resolveu o problema. Obrigado novamente.
Pulkownik
25

as outras respostas ajudam a entender como manter tal sessão. Além disso, desejo fornecer uma classe que mantenha a sessão mantida em diferentes execuções de um script (com um arquivo de cache). Isso significa que um "login" adequado só é executado quando necessário (tempo limite ou nenhuma sessão existe no cache). Também oferece suporte a configurações de proxy em chamadas subsequentes para 'obter' ou 'publicar'.

Ele é testado com Python3.

Use-o como base para seu próprio código. Os seguintes snippets são lançados com 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

Um snippet de código para usar a classe acima pode ter a seguinte aparência:

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
fonte
6
Essa é uma ótima resposta, mas também é estranhamente difícil pesquisar essa solução.
dualidade
Não deve ser implementado como parte do módulo de solicitação?
user1602 de
Ele usa o requestsmódulo. Como você faria para implementá-lo como parte do módulo? ou o que você quer dizer com @ user1602?
DomTomCat
17

Verifique minha resposta nesta pergunta semelhante:

python: urllib2 como enviar cookie com solicitação 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:

Vejo que recebi alguns votos negativos para minha resposta, mas nenhum comentário explicativo. Suponho que seja porque estou me referindo às urllibbibliotecas em vez de requests. Faço isso porque o OP pede ajuda requestsou para que alguém sugira outra abordagem.

Morten Jensen
fonte
2
Eu não sou um de seus votantes para baixo, mas como um palpite, muitos leitores provavelmente estão glosando a última frase do OP como “Alguém pode preencher as reticências no código abaixo ou sugerir outra abordagem [com a biblioteca de solicitações que envolveria mais importantes cirurgia ao meu código do que simplesmente preencher as elipses com outra coisa]. ” - mas isso é apenas um palpite da minha parte.
Brandon Rhodes
7
Como OP, posso dizer que sua resposta oferece uma alternativa útil. Nem que seja apenas para demonstrar que requestsoferece uma solução simples e de alto nível para um problema que, de outra forma, exigiria 3 bibliotecas para implementar.
ChrisGuest
7

A documentação diz que getrecebe um cookiesargumento opcional que permite especificar os cookies a serem usados:

dos 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
fonte
6

Ao tentar todas as respostas acima, descobri que usar "RequestsCookieJar" em vez do CookieJar regular para solicitações subsequentes corrigiu meu 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
fonte
1
Obrigado @ jim-chertkov por isso! Ótima resposta mesmo anos depois! Consegui usar e entender isso.
JayRizzo
2

snippet para recuperar dados json, protegido por senha

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())
Nada
fonte
1

Salve apenas os cookies necessários e reutilize-os.

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
fonte
0

Isso funcionará para você em 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
fonte