Żądania Pythona i trwałe sesje

120

Używam modułu żądań (wersja 0.10.0 z Pythonem 2.5). Dowiedziałem się, jak przesłać dane do formularza logowania na stronie internetowej i odzyskać klucz sesji, ale nie widzę oczywistego sposobu wykorzystania tego klucza sesji w kolejnych żądaniach. Czy ktoś może wypełnić wielokropek w poniższym kodzie lub zaproponować inne podejście?

>>> 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
źródło

Odpowiedzi:

210

Możesz łatwo utworzyć trwałą sesję za pomocą:

s = requests.Session()

Następnie kontynuuj swoje żądania, tak jak:

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 :)

Więcej informacji o sesjach: https://requests.kennethreitz.org/en/master/user/advanced/#session-objects

Anuj Gupta
źródło
4
Czy są jakieś sposoby na zapisanie samej sesji między uruchomieniami skryptu?
Gtx
10
Może pickle.dump cookie sesji do pliku takiego jak pickle.dump (session.cookies._cookies, file) i pickle.load do sesji jak następuje cookies = pickle.load (plik) cj = request.cookies.RequestsCookieJar () cj._cookies = pliki cookie i session.cookies = cj
Cyril
co jeśli wykorzystam proxy?
brainLoop
1
W przypadku żądań wysyłanych na localhostadres mogą wystąpić problemy z logowaniem i innymi plikami cookie zwracanymi przez serwer WWW, jeśli zawierają one nieprawidłową wartość właściwości domeny. W przypadku localhostserwera WWW powinien zwrócić pliki cookie z właściwością domeny ustawioną na localhost.local, w przeciwnym razie plik cookie nie zostanie zastosowany do sesji. W takim przypadku użyj 127.0.0.1zamiastlocalhost
Sergey Nudnov
@SergeyNudnov Wielkie dzięki za komentarz Zmarnowałem dużo czasu, próbując zrozumieć, dlaczego sesja nie obsługuje poprawnie plików cookie. Zmiana domeny z localhost na localhost.local rozwiązała problem. Dzięki jeszcze raz.
Pulkownik
25

inne odpowiedzi pomagają zrozumieć, jak prowadzić taką sesję. Dodatkowo chcę zapewnić klasę, która utrzymuje sesję w różnych uruchomieniach skryptu (z plikiem pamięci podręcznej). Oznacza to, że prawidłowe „logowanie” jest wykonywane tylko wtedy, gdy jest to wymagane (przekroczenie limitu czasu lub brak sesji w pamięci podręcznej). Obsługuje również ustawienia proxy dla kolejnych wywołań „get” lub „post”.

Jest testowany z Python3.

Użyj go jako podstawy dla własnego kodu. Następujące fragmenty są wydane z 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

Fragment kodu do użycia powyższej klasy może wyglądać następująco:

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
źródło
6
To świetna odpowiedź, dziwnie trudno jest też szukać tego rozwiązania.
dwoistość
Nie powinno być realizowane jako część modułu żądania?
user1602
Używa requestsmodułu. Jak zabrałbyś się za wdrożenie go jako części modułu? lub jak masz na myśli @ user1602?
DomTomCat
17

Sprawdź moją odpowiedź na to podobne pytanie:

python: urllib2 jak wysłać ciasteczko z prośbą 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()

EDYTOWAĆ:

Widzę, że otrzymałem kilka głosów przeciw mojej odpowiedzi, ale żadnych komentarzy wyjaśniających. Domyślam się, że to dlatego, że mam na myśli urllibbiblioteki, a nie requests. Robię to, ponieważ PO prosi o pomoc requestslub kogoś, kto zasugeruje inne podejście.

Morten Jensen
źródło
2
Nie jestem jednym z twoich słabych wyborców, ale przypuszczam, że wielu czytelników prawdopodobnie lśni ostatnie zdanie OP jako „Czy ktoś może wypełnić wielokropek w poniższym kodzie lub zasugerować inne podejście [z biblioteką wniosków, które wymagałyby większego operacja mojego kodu, a nie tylko wypełnienie elips czymś innym]. ” - ale to tylko przypuszczenie z mojej strony.
Brandon Rhodes
7
Jako OP mogę powiedzieć, że twoja odpowiedź stanowi użyteczną alternatywę. Choćby po to, by zademonstrować, że requestsoferuje to proste i wysokopoziomowe rozwiązanie problemu, który w innym przypadku wymagałby wdrożenia 3 bibliotek.
ChrisGuest,
7

Dokumentacja mówi, że getprzyjmuje opcjonalny cookiesargument umożliwiający określenie plików cookie do użycia:

z dokumentów:

>>> 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
źródło
6

Po wypróbowaniu wszystkich powyższych odpowiedzi stwierdziłem, że użycie „RequestsCookieJar” zamiast zwykłego pliku CookieJar do kolejnych żądań rozwiązało mój problem.

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
źródło
1
Dziękuję @ jim-chertkov za to! Świetna odpowiedź nawet po latach! Byłem w stanie to wykorzystać i zrozumieć.
JayRizzo
2

fragment do pobierania danych JSON, chroniony hasłem

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())
Zero
źródło
1

Zapisz tylko wymagane pliki cookie i używaj ich ponownie.

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
źródło
0

To zadziała w Pythonie;

# 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
źródło