การดูคุกกี้ที่ซ้ำกันเมื่อใช้คุกกี้ที่กู้คืนจากฐานข้อมูล

ฉันกำลังทำงานกับ API บางตัวโดยใช้คำขอ เพื่อให้บัญชีของฉันได้รับอนุญาต ฉันจะบันทึกคุกกี้ลงในฐานข้อมูลเมื่อออกและกู้คืนในครั้งถัดไป

ด้วยเหตุผลบางประการ คำขอไม่ได้แทนที่คุกกี้เก่าด้วยค่าใหม่ที่เซิร์ฟเวอร์ส่งคืน เพียงเพิ่มคุกกี้ใหม่ที่มีชื่อเดียวกันแต่มีค่าต่างกัน

รหัส:

from requests import Session
import json

local_session = Session()
local_session.cookies.update(json.loads(account.cookies))

resp = local_session.request("GET", "http://example.com/path0/")
# all cookies have been sent, server renewed some of them and return in "Set_Cookie" headers

resp = local_session.request("GET", "http://example.com/path1/")
# here requests send two cookies with same name but different values instead of one with new

คุกกี้ในฐานข้อมูล:

{
    "csrf": "abcdefgh",
    "session": "1234567890"
}

คุกกี้ที่ส่งไปในคำขอล่าสุด:

{
    "csrf": "abcdefgh",
    "csrf": "ijklmnop",
    "session": "1234567890"
}

ฉันพลาดอะไรไป เป็นไปได้ไหมที่จะแก้ไขปัญหานี้อย่างมีสติ?


อัปเดต

ฉันได้ลองในคอนโซลด้วย httpbin และ ... ผลลัพธ์ก็เหมือนกัน มันทำซ้ำคุกกี้:

from requests import Session

with Session() as sess:
    sess.cookies.update({"sessioncookie": "1234567890"})
    # Same behavior with sess.cookies.set('sessioncookie','1234567890')
    sess.get("https://httpbin.org/cookies/set/sessioncookie/0987654321")
    print(sess.cookies.items())
    # here's two pairs of cookies with same name now

person Olvin Roght    schedule 10.07.2019    source แหล่งที่มา
comment
ตัวแปรบัญชีคืออะไร?   -  person Corentin Limier    schedule 10.07.2019
comment
@CorentinLimier โมเดลฐานข้อมูล จริงๆ แล้ว มันไม่สำคัญ เพราะ account.cookies มีสตริง json ฉันแน่ใจว่าปัญหานั้นไม่ได้อยู่ที่นี่ เพราะในขั้นแรกขอให้ส่งคุกกี้ที่เหมาะสม   -  person Olvin Roght    schedule 10.07.2019
comment
คุณช่วยลอง print(local_session.cookies.items()) นี้หลังจากคำขอครั้งที่สองแล้วเขียนผลลัพธ์ลงในโพสต์ของคุณได้ไหม   -  person Corentin Limier    schedule 10.07.2019
comment
@CorentinLimier จริง ๆ แล้วฉันได้ทำมันแล้ว (คุกกี้ส่งไปในคำขอล่าสุด :) .items() มีทั้งสองคู่: มีค่าเก่าและใหม่ ปัญหาอยู่ที่การจัดเก็บคุกกี้หลังจากการร้องขอครั้งแรก   -  person Olvin Roght    schedule 10.07.2019
comment
เพราะฉันลองสิ่งนี้: s=Session();s.get('https://httpbin.org/cookies/set/sessioncookie/123456789');s.get('https://httpbin.org/cookies/set/sessioncookie/12345679');print(s.cookies.items() (httbin.org ให้ apis แก่คุณเพื่อจำลองคำขอ) และจริง ๆ แล้วมันอัปเดตคุกกี้ได้ดี คุณมีพฤติกรรมแบบเดียวกันบนเว็บไซต์นี้หรือไม่?   -  person Corentin Limier    schedule 10.07.2019
comment
@CorentinLimier ลองตั้งค่าคุกกี้ก่อนที่จะร้องขอครั้งแรก   -  person Olvin Roght    schedule 10.07.2019
comment
คุณพูดถูก ฉันคิดว่าคุณควรอัปเดตโพสต์ของคุณด้วยตัวอย่างนี้เนื่องจากสามารถทำซ้ำได้สำหรับทุกคน :) คุณได้รับปัญหาเดียวกันเมื่อคุณอัปเดตคุกกี้หลังจากการร้องขอ   -  person Corentin Limier    schedule 10.07.2019
comment
@CorentinLimier อัปเดตโพสต์ของฉัน หวังว่าจะมีทางแก้ไข   -  person Olvin Roght    schedule 10.07.2019
comment
คุณช่วยแสดงให้เราเห็นว่าคุณ บันทึก คุกกี้ลงในฐานข้อมูลได้อย่างไร   -  person Martijn Pieters    schedule 10.07.2019
comment
@MartijnPieters บางอย่างเช่น account.cookies = json.dumps(dict(session.cookies)) มีตัวอย่างที่ด้านล่างของคำถามโดยไม่มีฐานข้อมูล มีโค้ดเพียง 4 บรรทัดที่สร้างปัญหาเดียวกัน ดังนั้นฉันไม่คิดว่าจะมีปัญหาเกิดขึ้น   -  person Olvin Roght    schedule 10.07.2019


คำตอบ (2)


คุกกี้มีความซับซ้อนมากกว่าคู่คีย์-ค่าเพียงเล็กน้อย เบราว์เซอร์ยังต้องติดตามว่า URL ใดที่คุกกี้ใช้งานได้ (ผ่านกฎเกี่ยวกับชื่อโฮสต์ หมายเลขพอร์ต เส้นทาง URL และการเชื่อมต่อจะถูกเข้ารหัสหรือไม่) และระยะเวลาที่จะเก็บไว้ (หมดอายุ) คุกกี้บางตัวได้รับการออกแบบให้หมดอายุทันทีที่คุณปิดเบราว์เซอร์ ดังนั้นจึงไม่มีการตั้งค่าการหมดอายุ

คุณไม่ได้จัดเก็บข้อมูลทั้งหมดไว้ในฐานข้อมูลอย่างไรก็ตาม คุณเก็บเฉพาะคีย์และค่าเท่านั้น และเมื่อคุณเพิ่มคุกกี้กลับเข้าไปในโถคุกกี้ requests.Session() ใหม่ คุกกี้เหล่านั้นจะถูกทำเครื่องหมายเป็น สากลและถาวร คุกกี้จะถูกส่งไปยัง URL ทั้งหมด ซึ่งจะไม่มีวันหมดอายุ และจะไม่ถูกทิ้งไม่ว่าด้วยเหตุผลใดก็ตาม คุกกี้ประเภทนี้บางครั้งเรียกว่า คุกกี้พิเศษ แต่เซิร์ฟเวอร์ยังคงตั้งค่าคุกกี้ปกติด้วยข้อมูลความถูกต้องของ URL และการหมดอายุที่สมบูรณ์ และคุกกี้เหล่านี้ คุกกี้ที่แตกต่างกัน เนื่องจากการตั้งค่าเหล่านั้น

คุณสามารถยืนยันสิ่งนี้ได้ด้วยการวนซ้ำออบเจ็กต์คุกกี้ในโค้ด httpbin.org ตัวอย่างของคุณ:

>>> from requests import Session
>>> sess = Session()
>>> sess.cookies.update({"sessioncookie": "1234567890"})
>>> __ = sess.get("https://httpbin.org/cookies/set/sessioncookie/0987654321")
>>> for cookie in sess.cookies: print(cookie)
...
<Cookie sessioncookie=1234567890 for />
<Cookie sessioncookie=0987654321 for httpbin.org/>

มีคุกกี้สองรายการที่แยกกันอยู่ที่นี่ คุกกี้หนึ่งสำหรับ / (ทุกเส้นทาง ทุกโดเมน) และอีกคุกกี้หนึ่งสำหรับ httpbin.org เพียงอย่างเดียว ทั้งสองจะถูกส่ง

คุณต้องคงข้อมูลเพิ่มเติมไว้ในฐานข้อมูลเพื่อสร้างคุกกี้ 'ปกติ' ขึ้นมาใหม่ หากคุณไม่ต้องการพื้นที่เก็บข้อมูลที่ อ่านได้ ไลบรารี requests จะทำให้ขวดคุกกี้เป็นแบบเลือกได้อย่างชัดเจน:

import pickle

cookiedata = pickle.dumps(session.cookies, pickle.HIGHEST_PROTOCOL)

นี่คือข้อมูลไบนารี่ เก็บไว้เช่นนั้น คุณสามารถกู้คืนคุกกี้ของคุณด้วย:

session.cookies.update(pickle.loads(cookiedata))

มิฉะนั้น หากคุณต้องมี JSON คุณจะต้องจัดเก็บ Cookie คุณลักษณะทั้งหมด:

cookie_attrs = [
    "version", "name", "value", "port", "domain", "path", "secure",
    "expires", "discard", "comment", "comment_url", "rfc2109"
]
cookiedata = json.dumps([
    {attr: getattr(cookie, attr) for attr in cookie_attrs}
    for cookie in session.cookies
])

และกู้คืนจาก JSON ด้วย

for entry in json.loads(cookiedata):
    session.cookies.set(**entry)

ในทางเทคนิคแล้ว ยังมีแอตทริบิวต์ _rest ที่ติดตามแอตทริบิวต์ HttpOnly ของคุกกี้ (ในพจนานุกรมที่ซ้อนกัน) แต่แอตทริบิวต์นั้นจะไม่ถูกละเลยโดย requests เนื่องจากแอตทริบิวต์ดังกล่าวใช้กับเบราว์เซอร์ที่คุกกี้ที่มีแอตทริบิวต์นั้นตั้งค่าเป็น True ไม่ได้ เข้าถึงได้จาก JavaScript

โดยหลักการแล้ว แอตทริบิวต์ domain, path, name จะทำให้คุกกี้ ไม่ซ้ำกัน แต่ถ้าเซิร์ฟเวอร์ตั้งค่า เช่น คุกกี้ที่มีค่า port เฉพาะ และคุณทำให้คุกกี้นั้นต่อเนื่องกันและกู้คืนในภายหลัง ในตอนนี้ คุกกี้จะถูกส่งโดยไม่คำนึงถึงพอร์ตที่ใช้สำหรับ URL เป้าหมาย (ทุกอย่างเท่าเทียมกัน) และนั่นอาจทำให้พังได้หากไซต์ตัดสินใจว่าการเห็นคุกกี้ดังกล่าวที่อื่นเป็นปัญหา

person Martijn Pieters    schedule 10.07.2019
comment
ขอบคุณมาก. เท่าที่ฉันค้นคว้า ชื่อ ค่า โดเมน เส้นทาง และ หมดอายุ ก็เพียงพอแล้วที่จะสร้างคุกกี้ปกติขึ้นมาใหม่ ดังนั้นฉันจึงได้ทำ wrapper ขนาดเล็กเพื่อบันทึกและกู้คืนฟิลด์นี้ (เป็น อ่านง่ายขึ้นมากแล้ว pickle.dump()) ยังไงก็ขอบคุณมากที่ช่วยนะครับ การเลือกคำตอบของคุณ เพราะฉันพบว่ามันเป็นสากลมากขึ้น - person Olvin Roght; 10.07.2019

ในที่สุดก็พบวิธีแก้ปัญหา คุณต้องระบุโดเมน

from requests import Session

with Session() as sess:
    #sess.cookies.update({"sessioncookie": "1234567890"})
    sess.cookies.set('sessioncookie','1234567890', domain='httpbin.org')
    sess.get("https://httpbin.org/cookies/set/sessioncookie/0987654321")
    print(sess.cookies.items())
    # Only one cookie
person Corentin Limier    schedule 10.07.2019
comment
นั่นไม่ใช่วิธีแก้ปัญหาที่สมบูรณ์ คุณไม่ได้รวมข้อมูลเส้นทาง พอร์ต แฟล็กที่ปลอดภัย หรือวันหมดอายุ - person Martijn Pieters; 10.07.2019
comment
พูดให้แตกต่างออกไป: คุณยังคงสร้างคุกกี้พิเศษที่นี่ - person Martijn Pieters; 10.07.2019
comment
ขอบคุณ. หากต้องการคืนค่าคุกกี้ที่เขียนใหม่ได้ คุณต้องเพิ่ม เส้นทาง และ หมดอายุ (path=path='/', expires=None) ด้วย - person Olvin Roght; 10.07.2019