首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >搜狗微信点选验证的会话保持与 Cookie 续期实战

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

原创
作者头像
小白学大数据
发布2026-08-04 16:56:34
发布2026-08-04 16:56:34
1140
举报

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

代码语言:txt
复制
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 之后必须校验。

代码语言:txt
复制
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 强制重握手,代价只是多一次首页请求。3. UA 池与请求节奏别用默认 UA,准备几个轮换,翻页之间加随机休眠。

代码语言:txt
复制
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 池大批量关键词用单会话扛不住,起一组预热会话并行。

代码语言:txt
复制
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,解完放回,解不开丢弃换出口重建。5. 代理与出口切换搜狗看出口 IP 的稳定和干净。亿牛云的企业级代理支持独享 IP 与会话保持,同一任务绑定固定出口,续期时 IP 不变,验证码触发率更低。配置就是 CrawlerConfig 里那行 proxy,账号域名端口填进去即可,按请求计费对平时静默、偶尔爆发的采集也合适。出口断了换 IP 再握手:

代码语言:txt
复制
def rotate_proxy(config: CrawlerConfig, new_proxy: str) -> None:
    config.proxy = new_proxy   # 新出口,如亿牛云切换接口返回
    # 之后用新 config 重建 SogouSession 即生效

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

代码语言:txt
复制
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. 完整调用

代码语言:txt
复制
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 养长寿命会话,按实效提前续期,稳定干净的高匿代理守住出口。续期和返回校验做扎实,验证码求解当兜底,采集就能稳稳跑完一轮。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档