Запросы Python и постоянные сеансы

120

Я использую модуль запросов (версия 0.10.0 с Python 2.5). Я выяснил, как отправить данные в форму входа на веб-сайт и получить ключ сеанса, но я не вижу очевидного способа использовать этот ключ сеанса в последующих запросах. Может ли кто-нибудь заполнить многоточие в приведенном ниже коде или предложить другой подход?

>>> import requests
>>> login_data =  {'formPosted':'1', 'login_email':'me@example.com', '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
источник

Ответы:

211

Вы можете легко создать постоянный сеанс, используя:

s = requests.Session()

После этого продолжайте свои запросы, как если бы:

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

Подробнее о сессиях: https://requests.kennethreitz.org/en/master/user/advanced/#session-objects.

Анудж Гупта
источник
4
Есть ли способы сохранить сам сеанс между запусками скрипта?
Gtx
10
Можно pickle.dump сеансовые файлы cookie в файл, например pickle.dump (session.cookies._cookies, file) и pickle.load в сеанс, например, следующие файлы cookie = pickle.load (файл) cj = requests.cookies.RequestsCookieJar () cj._cookies = cookies и session.cookies = cj
Сирил
что если я задействую прокси?
brainLoop 01
1
Для отправленных запросов localhostмогут быть проблемы с логином и другими файлами cookie, возвращаемыми веб-сервером, если они содержат неверное значение свойства домена. Ибо localhostвеб-сервер должен возвращать файлы cookie с установленным свойством домена localhost.local, в противном случае cookie не будет применяться к сеансу. В таком случае используйте 127.0.0.1вместоlocalhost
Сергей Нуднов
@SergeyNudnov Большое спасибо за ваш комментарий. Я потратил много времени, пытаясь понять, почему сеанс не обрабатывает файлы cookie правильно. Смена домена с localhost на localhost.local решила проблему. Еще раз спасибо.
Pulkownik,
26

другие ответы помогают понять, как поддерживать такой сеанс. Кроме того, я хочу предоставить класс, который поддерживает сеанс при различных запусках сценария (с файлом кеша). Это означает, что правильный «вход» выполняется только при необходимости (тайм-аут или сеанс не существует в кеше). Также он поддерживает настройки прокси-сервера для последующих вызовов get или post.

Он протестирован с Python3.

Используйте его как основу для вашего собственного кода. Следующие ниже фрагменты выпущены с 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

Фрагмент кода для использования указанного выше класса может выглядеть так:

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
источник
7
Это отличный ответ, странно сложно найти и это решение.
дуальность
Не следует реализовывать как часть модуля запроса?
user1602
Он действительно использует requestsмодуль. Как бы вы реализовали его как часть модуля? или как ты имеешь ввиду @ user1602?
DomTomCat,
17

Посмотрите мой ответ на этот похожий вопрос:

python: urllib2 как отправить cookie с запросом 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()

РЕДАКТИРОВАТЬ:

Я вижу, что получил несколько отрицательных голосов за свой ответ, но никаких поясняющих комментариев. Я предполагаю это потому, что я имею в виду urllibбиблиотеки, а не requests. Я делаю это, потому что ОП просит о помощи requestsили кого-то предложить другой подход.

Мортен Йенсен
источник
2
Я не один из ваших противников, но, как предполагаю, многие читатели, вероятно, истолковывают последнее предложение OP как «Может ли кто-нибудь заполнить многоточие в приведенном ниже коде или предложить другой подход [с библиотекой запросов, который будет включать более серьезные хирургия моего кода, чем просто заполнение эллипсов чем-то еще] ». - но это всего лишь предположение с моей стороны.
Брэндон Роудс,
7
Как OP, я могу сказать, что ваш ответ представляет собой полезную альтернативу. Если только для демонстрации, это requestsпредлагает простое и высокоуровневое решение проблемы, для реализации которой в противном случае потребовалось бы 3 библиотеки.
ChrisGuest,
7

В документации говорится, что он getпринимает необязательный cookiesаргумент, позволяющий указать файлы cookie для использования:

из документов:

>>> 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
источник
6

Попробовав все приведенные выше ответы, я обнаружил, что использование «RequestsCookieJar» вместо обычного CookieJar для последующих запросов решило мою проблему.

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':'me@example.com', 
               '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
Джим Чертков
источник
1
Спасибо @ jim-chertkov за это! Отличный ответ даже спустя годы! Я смог это использовать и понять.
JayRizzo,
2

фрагмент для получения данных json, защищенный паролем

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())
NiL
источник
1

Сохраняйте только необходимые файлы cookie и используйте их повторно.

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

login = 'my@email.com'
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
источник
0

Это будет работать для вас в 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=(",", ": ")))
Даситха Абейсингхе
источник