解决Python爬虫访问HTTPS资源时Cookie超时问题

小白学大数据
• 阅读 17

一、问题背景: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 会“秒死”
  4. 服务端在返回 Set-Cookie 时同时下发 HttpOnly + Secure + SameSite=Lax,浏览器 15 s 后失效。
  5. 服务端会校验 TLS 指纹 + IP + User-Agent + Cookie 四元组,任一变化即作废。
  6. 多数站点使用 __cf_bm、cf_clearance 等 Cloudflare 反爬 Cookie,有效期 30 min,但图片站为了节省带宽,把有效期降到 15 s。 因此,我们需要在 Python 侧模拟浏览器行为,持续刷新 Cookie,并把 Cookie 与 TLS 指纹、IP 绑定。 三、整体方案
  7. 使用 Playwright/Selenium 跑无头浏览器,真实渲染页面,拿到完整 Cookie。
  8. 把 Cookie 注入到 Requests Session(或 aiohttp),利用 HTTP/2 和连接复用,减少 TLS 握手开销。
  9. 对 Cookie 做“热插拔”:每 10 s 异步刷新一次,保证并发下载线程/协程拿到的 Cookie 永远有效。
  10. 分布式场景下,把 Cookie 放到 Redis + TTL,供所有 Worker 共享。 四、最小可运行 Demo(单机版)
  11. 1 安装 & 初始化
  12. 2 核心代码
    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 事件。
点赞
收藏
评论区
推荐文章
Stella981 Stella981
3年前
SpringBoot学习:整合shiro自动登录功能(rememberMe记住我功能)
首先在shiro配置类中注入rememberMe管理器!复制代码(https://oscimg.oschina.net/oscnet/675f5689159acfa2c39c91f4df40a00ce0f.gif)/cookie对象;rememberMeCookie()方法是设置Cookie的生成模
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Stella981 Stella981
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Easter79 Easter79
3年前
SpringBoot学习:整合shiro自动登录功能(rememberMe记住我功能)
首先在shiro配置类中注入rememberMe管理器!复制代码(https://oscimg.oschina.net/oscnet/675f5689159acfa2c39c91f4df40a00ce0f.gif)/cookie对象;rememberMeCookie()方法是设置Cookie的生成模
Easter79 Easter79
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Stella981 Stella981
3年前
Linux日志安全分析技巧
0x00前言我正在整理一个项目,收集和汇总了一些应急响应案例(不断更新中)。GitHub地址:https://github.com/Bypass007/EmergencyResponseNotes本文主要介绍Linux日志分析的技巧,更多详细信息请访问Github地址,欢迎Star。0x01日志简介Lin
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
美凌格栋栋酱 美凌格栋栋酱
5个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
小白学大数据
小白学大数据
Lv1
男 · 亿牛云 · python技术
宁为代码类弯腰,不为bug点提交!
文章
112
粉丝
5
获赞
18