搜狗微信点选验证的会话保持与 Cookie 续期实战

简介: 搜狗微信点选验证的会话保持与 Cookie 续期实战

搜狗微信(weixin.sogou.com)是公众号文章监测的常用入口,信任状态挂在 Cookie 上。过了点选验证码之后,SNUID、SUV 这类凭证有时效,过期就重新弹验证。核心做法是 requests.Session 养长寿命会话、按实效提前续期、尽量别掉回验证那步。下面以代码为主,叙述从简。

  1. 会话与续期核心
    ```from future import annotations

import logging
import time
from dataclasses import dataclass, field
from typing import Optional

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

logger = logging.getLogger("sogou_weixin")

@dataclass
class CrawlerConfig:
base_url: str = "https://weixin.sogou.com"
search_path: str = "/weixin"
proxy: Optional[str] = None # "http://user:pass@proxy.yiniu.com:port"
max_retries: int = 3
timeout: float = 10.0
cookie_ttl: int = 600
ua_pool: list[str] = field(default_factory=list)

class SogouSession:
def init(self, config: CrawlerConfig) -> None:
self.config = config
self.session = self._build_session()
self.cookie_issued_at: float = 0.0

def _build_session(self) -> requests.Session:
    session = requests.Session()
    retry = Retry(total=self.config.max_retries, backoff_factor=0.5,
                  status_forcelist=[429, 500, 502, 503, 504])
    session.mount("https://", HTTPAdapter(max_retries=retry))
    if self.config.proxy:
        session.proxies.update({"https": self.config.proxy})
    ua = self.config.ua_pool[0] if self.config.ua_pool else (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
        "(KHTML, like Gecko) Chrome/124.0 Safari/537.36")
    session.headers.update({"User-Agent": ua, "Accept-Language": "zh-CN,zh;q=0.9"})
    return session

def ensure_session(self) -> None:
    if not self.session.cookies or \
       time.time() - self.cookie_issued_at > self.config.cookie_ttl:
        self._refresh()

def _refresh(self) -> None:
    try:
        resp = self.session.get(self.config.base_url + self.config.search_path,
                                params={"type": 2, "query": "测试"},
                                timeout=self.config.timeout)
        resp.raise_for_status()
        if self._is_captcha_page(resp.text):
            logger.warning("续期被弹验证码,需重新过验证")
            self._solve_captcha()
        self.cookie_issued_at = time.time()
        logger.info("续期成功,Cookie 数量=%d", len(self.session.cookies))
    except requests.RequestException as exc:
        logger.warning("续期失败:%s", exc)
        raise

def _is_captcha_page(self, html: str) -> bool:
    return "请输入验证码" in html or "antispider" in html

def _solve_captcha(self) -> None:
    raise NotImplementedError("接入点选验证码识别服务")

def fetch(self, query: str, page: int = 1) -> str:
    self.ensure_session()
    resp = self.session.get(self.config.base_url + self.config.search_path,
                            params={"type": 2, "query": query, "page": page},
                            timeout=self.config.timeout)
    resp.raise_for_status()
    return resp.text

续期阈值别拍脑袋设。先空跑看第几分钟被弹,那个点减一两分钟就是安全线。实测 SNUID 二十分钟出头松动,所以 cookie_ttl 设 600 秒留缓冲。
2. 返回内容校验,防静默失效
Cookie 还在但信任没了很常见,返回页看着正常实则是验证页。fetch 之后必须校验。
```import re

RESULT_ITEM_RE = re.compile(r'class="txt-box"')      # 正常结果条目特征
CAPTCHA_HINT_RE = re.compile(r"请输入验证码|antispider")

def validate_result(html: str) -> bool:
    """True 表示页面可信:含正常结果结构且无非验证提示。"""
    if CAPTCHA_HINT_RE.search(html):
        return False
    return bool(RESULT_ITEM_RE.search(html))

命中异常就 session.cookies.clear(),下一轮 ensure_session 强制重握手,代价只是多一次首页请求。

  1. UA 池与请求节奏
    别用默认 UA,准备几个轮换,翻页之间加随机休眠。
    ```import random

UA_POOL = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/124.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 "
"(KHTML, like Gecko) Version/17.0 Safari/605.1.15",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/123.0 Safari/537.36",
]

def pick_ua() -> str:
return random.choice(UA_POOL)

def human_sleep() -> None:
time.sleep(random.uniform(1.5, 4.0))


CrawlerConfig.ua_pool 填上 UA_POOL,_build_session 会随机取一个;每次 human_sleep() 后再翻页。
4. 多会话 Cookie 池
大批量关键词用单会话扛不住,起一组预热会话并行。
```from threading import Lock

class SogouSessionPool:
    def __init__(self, config: CrawlerConfig, size: int = 4) -> None:
        self.config = config
        self._lock = Lock()
        self._pool: list[SogouSession] = []
        for _ in range(size):
            s = SogouSession(config)
            s._refresh()          # 启动即握手预热
            self._pool.append(s)

    def acquire(self) -> SogouSession:
        with self._lock:
            s = self._pool.pop()
        s.ensure_session()
        if not validate_result(s.fetch("测试")):
            s.session.cookies.clear()
            s._refresh()
        return s

    def release(self, s: SogouSession) -> None:
        with self._lock:
            self._pool.append(s)

被弹验证码的会话移出池子走 _solve_captcha,解完放回,解不开丢弃换出口重建。

  1. 代理与出口切换
    搜狗看出口 IP 的稳定和干净。亿牛云的企业级代理支持独享 IP 与会话保持,同一任务绑定固定出口,续期时 IP 不变,验证码触发率更低。配置就是 CrawlerConfig 里那行 proxy,账号域名端口填进去即可,按请求计费对平时静默、偶尔爆发的采集也合适。
    出口断了换 IP 再握手:
    ```def rotate_proxy(config: CrawlerConfig, new_proxy: str) -> None:
    config.proxy = new_proxy # 新出口,如亿牛云切换接口返回

    之后用新 config 重建 SogouSession 即生效

    ```

  2. 验证码钩子示例
    _solve_captcha 真实环境接打码平台或自研模型,这里给个签名示例。
    ```
    from dataclasses import dataclass

@dataclass
class ClickPoint:
x: int
y: int

def _solve_captcha(self) -> None:

# 1. 拉取验证图片与提示文字
# 2. 识别点击序列(模型 / 打码平台)
points: list[ClickPoint] = recognize(self.session)
# 3. 按坐标回提交接口
submit(self.session, points)
self.cookie_issued_at = time.time()

7. 完整调用
```def parse(html: str) -> None:
    """从结果页抽取文章条目,按业务入库。"""
    # ...

def main() -> None:
    config = CrawlerConfig(proxy="http://user:pass@proxy.yiniu.com:port",
                           ua_pool=UA_POOL)
    pool = SogouSessionPool(config, size=4)
    keywords = ["双减政策", "研学旅行", "课后服务"]
    for kw in keywords:
        s = pool.acquire()
        try:
            for page in range(1, 11):
                html = s.fetch(kw, page)
                if not validate_result(html):
                    s.session.cookies.clear()
                    s._refresh()
                    continue
                parse(html)
                human_sleep()
        finally:
            pool.release(s)

if __name__ == "__main__":
    main()

骨架就三件事:Session 养长寿命会话,按实效提前续期,稳定干净的高匿代理守住出口。续期和返回校验做扎实,验证码求解当兜底,采集就能稳稳跑完一轮。

相关文章
|
6月前
|
数据采集 JSON API
Python 进阶爬虫:解析知识星球 API
Python 进阶爬虫:解析知识星球 API
|
7月前
|
数据采集 JSON Java
Java 异步爬虫高效获取小红书短视频内容
Java 异步爬虫高效获取小红书短视频内容
|
2月前
|
数据采集 前端开发 JavaScript
Scrapling:极简高效的 Python 智能爬虫框架
Scrapling:极简高效的 Python 智能爬虫框架
|
8月前
|
数据采集 文字识别 JavaScript
基于文本检测的 Python 爬虫弹窗图片定位与拖动实现
基于文本检测的 Python 爬虫弹窗图片定位与拖动实现
|
4月前
|
数据采集 Web App开发 存储
Selenium+Python 爬虫:动态加载头条问答爬取
Selenium+Python 爬虫:动态加载头条问答爬取
Selenium+Python 爬虫:动态加载头条问答爬取
|
4月前
|
存储 API 数据安全/隐私保护
Python 自动化爬取网易云音乐歌手歌词实战教程
Python 自动化爬取网易云音乐歌手歌词实战教程
|
3月前
|
数据采集 数据可视化 数据挖掘
均线选股策略研究:基于 Python 数据分析实现
均线选股策略研究:基于 Python 数据分析实现
|
3月前
|
数据采集 JSON 数据安全/隐私保护
Python 爬虫爬取应用商店数据:请求构造与数据解析
Python 爬虫爬取应用商店数据:请求构造与数据解析
|
4月前
|
数据采集 存储 数据安全/隐私保护
Python 爬取图片攻略:告别水印,批量保存高清图片
Python 爬取图片攻略:告别水印,批量保存高清图片
|
3月前
|
数据采集 Web App开发 JSON
基于大模型的Python智能爬虫:语义识别与数据清洗实践
基于大模型的Python智能爬虫:语义识别与数据清洗实践