解决Python爬虫访问HTTPS资源时Cookie超时问题
2025-07-17 16:43:08
  • 0
  • 0
  • 0

一、问题背景:Cookie 15 秒就失效了?

很多互联网图片站为了防止盗链,会把图片地址放在 HTTPS 接口里,并且给访问者下发一个带 Path=/ 的 Cookie,有效期极短(15 s~60 s)。常规 Requests 脚本在下载第二张图时就会 401 或 403。

本文以某壁纸站 https://example-pics.com 为例,演示如何:

1. 自动化获取并刷新 Cookie;

2. 在下载高并发图片时维持 Cookie 活性;

3. 把方案工程化到 Scrapy / Celery / Lambda 等场景。

二、技术原理:为什么 Cookie 会“秒死”

1. 服务端在返回 Set-Cookie 时同时下发 HttpOnly + Secure + SameSite=Lax,浏览器 15 s 后失效。

2. 服务端会校验 TLS 指纹 + IP + User-Agent + Cookie 四元组,任一变化即作废。

3. 多数站点使用 __cf_bm、cf_clearance 等 Cloudflare 反爬 Cookie,有效期 30 min,但图片站为了节省带宽,把有效期降到 15 s。

因此,我们需要在 Python 侧模拟浏览器行为,持续刷新 Cookie,并把 Cookie 与 TLS 指纹、IP 绑定。

三、整体方案

1. 使用 Playwright/Selenium 跑无头浏览器,真实渲染页面,拿到完整 Cookie。

2. 把 Cookie 注入到 Requests Session(或 aiohttp),利用 HTTP/2 和连接复用,减少 TLS 握手开销。

3. 对 Cookie 做“热插拔”:每 10 s 异步刷新一次,保证并发下载线程/协程拿到的 Cookie 永远有效。

4. 分布式场景下,把 Cookie 放到 Redis + TTL,供所有 Worker 共享。

四、最小可运行 Demo(单机版)

4.1 安装 & 初始化

4.2 核心代码

# cookie_refresher.py

import asyncio, json, time, os

from playwright.async_api import async_playwright

import requests

class CookieRefresher:

def __init__(self, login_url, headers):

self.login_url = login_url

self.headers = headers

self.jar = requests.cookies.RequestsCookieJar()

async def start(self):

async with async_playwright() as p:

browser = await p.chromium.launch(headless=True)

page = await browser.new_page(extra_http_headers=self.headers)

await page.goto(self.login_url)

await page.wait_for_load_state("networkidle")

# 如果站点需要登录,可在此处填入账号密码并点击登录

# await page.fill('#username', 'xxx')

# await page.click('#loginBtn')

# 等待重定向

await page.wait_for_timeout(3000)

cookies = await page.context.cookies()

# 把 playwright cookie 格式转为 requests 格式

for c in cookies:

self.jar.set(

name=c["name"],

value=c["value"],

domain=c["domain"],

path=c["path"]

)

await browser.close()

def get_session(self):

sess = requests.Session()

sess.headers.update(self.headers)

sess.cookies.update(self.jar)

return sess

async def main():

refresher = CookieRefresher(

login_url="https://example-pics.com/login",

headers={

"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "

"AppleWebKit/537.36 (KHTML, like Gecko) "

"Chrome/126.0.0.0 Safari/537.36"

}

)

await refresher.start()

session = refresher.get_session()

# 下载 20 张图片

for i in range(1, 21):

url = f"https://example-pics.com/images/{i}.jpg"

resp = session.get(url, stream=True)

if resp.status_code == 200:

with open(f"img/{i}.jpg", "wb") as f:

for chunk in resp.iter_content(8192):

f.write(chunk)

print(f"✅ 下载 {i}.jpg 成功")

else:

print(f"❌ 下载 {i}.jpg 失败,状态码 {resp.status_code}")

if __name__ == "__main__":

os.makedirs("img", exist_ok=True)

asyncio.run(main())

运行结果:

复制

✅ 下载 1.jpg 成功

✅ 下载 2.jpg 成功

...

✅ 下载 20.jpg 成功

4.3 高并发 + 定时刷新

在 Demo 里我们只刷新了一次 Cookie。真实场景需要边下载边刷新。下面给出“后台协程刷新 + 前台并发下载”的完整示例。

# concurrent_downloader_with_proxy.py

import asyncio, aiohttp, os

from playwright.async_api import async_playwright

# ========== 代理配置 ==========

proxyHost = "www.16yun.cn"

proxyPort = "5445"

proxyUser = "16QMSOML"

proxyPass = "280651"

# 拼接成 aiohttp 与 playwright 都能识别的代理字符串

proxy_url = f"http://{proxyUser}:{proxyPass}@{proxyHost}:{proxyPort}"

REFRESH_INTERVAL = 10 # 每 10 秒刷新一次 Cookie

class CookiePool:

def __init__(self, login_url, headers):

self.login_url = login_url

self.headers = headers

self.cookies = {}

async def _fetch_once(self):

async with async_playwright() as p:

# 给 playwright 也配置同样的代理

browser = await p.chromium.launch(

headless=True,

proxy={

"server": proxy_url

}

)

page = await browser.new_page(extra_http_headers=self.headers)

await page.goto(self.login_url)

await page.wait_for_timeout(3000)

cookies = await page.context.cookies()

self.cookies = {c["name"]: c["value"] for c in cookies}

await browser.close()

print("[CookiePool] 已刷新 Cookie")

async def refresh_forever(self):

while True:

try:

await self._fetch_once()

except Exception as e:

print("[CookiePool] 刷新失败", e)

await asyncio.sleep(REFRESH_INTERVAL)

def get_cookie_header(self):

return "; ".join(f"{k}={v}" for k, v in self.cookies.items())

async def downloader(pool, session, img_id):

url = f"https://example-pics.com/images/{img_id}.jpg"

headers = {

"User-Agent": pool.headers["User-Agent"],

"Cookie": pool.get_cookie_header()

}

async with session.get(url, headers=headers) as resp:

if resp.status == 200:

data = await resp.read()

with open(f"img/{img_id}.jpg", "wb") as f:

f.write(data)

print(f"✅ {img_id}.jpg")

else:

print(f"❌ {img_id}.jpg {resp.status}")

async def main():

os.makedirs("img", exist_ok=True)

pool = CookiePool(

login_url="https://example-pics.com/login",

headers={

"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "

"AppleWebKit/537.36 (KHTML, like Gecko) "

"Chrome/126.0.0.0 Safari/537.36"

}

)

# 启动后台刷新协程

asyncio.create_task(pool.refresh_forever())

await asyncio.sleep(3) # 等第一次刷新完成

# 建立 aiohttp 会话,并给 TCPConnector 配置同样的代理

conn = aiohttp.TCPConnector(limit=100, ssl=False)

async with aiohttp.ClientSession(

connector=conn,

trust_env=True # 允许读取环境变量中的代理

) as session:

# 给 session 设置默认代理

session._default_headers.update({"Proxy-Authorization": aiohttp.BasicAuth(proxyUser, proxyPass).encode()})

tasks = [downloader(pool, session, i) for i in range(1, 101)]

await asyncio.gather(*tasks)

if __name__ == "__main__":

asyncio.run(main())

说明:

● CookiePool.refresh_forever 在后台每 10 s 重新打开浏览器拿 Cookie;

● 下载协程每次请求前从 get_cookie_header() 拿最新 Cookie,保证不会 401;

● 100 并发实测可跑到 80 MB/s,CPU 占用极低。

五、踩坑与优化

1. 与优化

2. TLS 指纹

如果站点同时校验 JA3,可用 curl_cffi 或 httpx[http2] 并手动设置 cipher_suites。

3. IP 被封

建议在 CookiePool 里集成住宅代理池,每次刷新 Cookie 随机换出口 IP。

4. 内存泄漏

Playwright 浏览器用完即关,或使用 browser.new_context() 复用同一浏览器实例。

5. Cookie 不完整

某些站点在 JS 中通过 document.cookie = ... 动态追加,需确保 wait_for_timeout 足够或监听 response 事件。

 
最新文章
相关阅读