o
    Ղig9                     @  s:  U d Z ddlmZ ddlZddlZddlZddlZddlmZm	Z	m
Z
 ddlmZ ddlmZ ddddd	d
d	d
dd	Zded< d	ZG dd dZi Zded< e Zd:ddZddddddddddd dd!d"d dd#d$ddd%d&d dd'd(ddd)dd*dgZd+d,d-d.d/d0d1d2d3d	Zd4ZG d5d6 d6Zd;d8d9ZdS )<u  反封禁策略 — 分层限流版

改造要点（相比旧版）：
  1. 按 (domain, category_id) 维护独立令牌桶（避免 8 类互相争抢/压制）
  2. 延迟从均匀分布改为正态分布（μ=delay_mid，σ=delay_std），更像真实用户
  3. User-Agent 轮换时同步附带真实 Accept-Language / Referer 头
  4. 全局异步锁保护令牌桶状态，并发安全
    )annotationsN)DictListOptional)urlparse)logger      
      )	               r	      r   	   zDict[int, int]_CATEGORY_RATEc                   @  s"   e Zd ZdZd	ddZdd ZdS )
_TokenBucketu   
    基于令牌桶的速率限制。

    每分钟最多 `rate` 个请求，超出时等待直到有令牌可用。
    使用滑动窗口（记录请求时间戳列表）实现精确控制。
    rate_per_minuteintc                 C  s   || _ g | _t | _d S N)rate_timestampsasyncioLock_lock)selfr    r   [/lsinfo/ai/hellotax_ai/data_center/backend/app/services/tax_data_processor/anti_blocking.py__init__4   s   z_TokenBucket.__init__c              
     s   | j 4 I dH k t  d fdd| jD | _t| j| jkr`| jd } |  d }|dkr`tdt| j d| j d	|d
d t	|I dH  t   fdd| jD | _| j
t  W d  I dH  dS 1 I dH syw   Y  dS )u9   等待直到获得令牌（阻塞到有配额为止）。Ng      N@c                      g | ]
} | k r|qS r   r   .0tnowwindowr   r   
<listcomp>@       z(_TokenBucket.acquire.<locals>.<listcomp>r   g?u   [rate-limit] 令牌桶满（/u   /min），等待 .2fsc                   r!   r   r   r"   r%   r   r   r(   N   r)   )r   time	monotonicr   lenr   r   debugr   sleepappend)r   oldestwaitr   r%   r   acquire9   s&   
.z_TokenBucket.acquireN)r   r   )__name__
__module____qualname____doc__r    r5   r   r   r   r   r   ,   s    
r   zDict[tuple, _TokenBucket]_bucketsdomainstrcategory_idr   returnc              
     s   | |f}t 4 I d H . |tvr+t|t}t|t|< td|  d| d| d t| W  d   I d H  S 1 I d H s?w   Y  d S )Nu$   [rate-limit] 新建令牌桶 domain=z
 category=z rate=z/min)_buckets_lockr:   r   get_DEFAULT_RATEr   r   r0   )r;   r=   keyr   r   r   r   _get_bucket[   s   0rC   zoMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36z#zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7Win32)ualangplatformzoMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36zzh-CN,zh;q=0.9,en;q=0.8zuMozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36MacIntelzuMozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36zzh-CN,zh;q=0.9zPMozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0z;zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2zTMozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0z#zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3z}Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0z/zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6zeMozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36zLinux x86_64z:https://fgk.chinatax.gov.cn/zcfgk/c100009/listflfg_fg.htmlz:https://fgk.chinatax.gov.cn/zcfgk/c100010/listflfg_fg.htmlz7https://fgk.chinatax.gov.cn/zcfgk/c102440/listflfg.htmlz<https://fgk.chinatax.gov.cn/zcfgk/c100011/list_guizhang.htmlz7https://fgk.chinatax.gov.cn/zcfgk/c102416/listflfg.htmlz7https://fgk.chinatax.gov.cn/zcfgk/c100012/listflfg.htmlz7https://fgk.chinatax.gov.cn/zcfgk/c100013/listflfg.htmlz7https://fgk.chinatax.gov.cn/zcfgk/c102424/listflfg.htmlz8https://fgk.chinatax.gov.cn/zcfgk/c100015/list_zcjd.htmlzhttps://fgk.chinatax.gov.cn/c                   @  sp   e Zd ZdZdd eD Z						d'd(ddZd)d*ddZd+d,ddZd-dd Z	d!d" Z
d#d$ Zd%d& ZdS ).AntiBlockingStrategyu   
    反封禁策略（分层限流版）

    每个实例绑定到一个 category_id，维护独立令牌桶。
    多实例共享全局令牌桶注册表，同类请求互相约束。
    c                 C  s   g | ]}|d  qS )rE   r   )r#   pr   r   r   r(      s    zAntiBlockingStrategy.<listcomp>      ?      @r   Nfgk.chinatax.gov.cn	delay_minfloat	delay_maxr=   r   
proxy_listOptional[List[str]]r;   r<   max_requests_per_minuteOptional[int]c                 C  sd   || _ || _|| _|pg | _|| _|| d }|| _|| d | _|| _g | _|p.t	
|t| _dS )u  
        Args:
            delay_min:              最小延迟（秒）
            delay_max:              最大延迟（秒）
            category_id:            当前采集的分类ID（0=未指定，使用默认速率）
            proxy_list:             代理列表（可选）
            domain:                 目标域名（令牌桶分组键之一）
            max_requests_per_minute: 兼容旧参数（若提供则覆盖按分类的配置）
        r   r   N)rN   rP   r=   rQ   r;   	_delay_mu_delay_sigma_override_raterequest_timesr   r@   rA   rS   )r   rN   rP   r=   rQ   r;   rS   midr   r   r   r       s   

zAntiBlockingStrategy.__init__ urlc                   sd   |rt |n| j}| j}t||I dH }| jr"|j| jkr"| j|_| I dH  |  I dH  dS )u3   请求前：先令牌桶限流，再随机延迟。N)_extract_domainr;   r=   rC   rW   r   r5   _apply_normal_delay)r   r[   r;   r=   bucketr   r   r   before_request   s   z#AntiBlockingStrategy.before_requestr>   dictc                 C  sL   t t}|dur|n| j}t|t}|d d|d d|dddd	d
ddS )u   
        获取随机请求头，包含真实的 Accept-Language / Referer。

        Args:
            category_id: 用于选择对应 Referer；None 时使用实例的 category_id
        NrE   zUtext/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8rF   zgzip, deflate, brz
keep-alive1z	max-age=0documentnavigatezsame-origin)z
User-AgentAcceptzAccept-LanguagezAccept-EncodingReferer
ConnectionzUpgrade-Insecure-RequestszCache-ControlzSec-Fetch-DestzSec-Fetch-ModezSec-Fetch-Site)randomchoice_UA_PROFILESr=   	_REFERERSr@   _DEFAULT_REFERER)r   r=   profilecidrefererr   r   r   get_headers   s   
z AntiBlockingStrategy.get_headersOptional[str]c                 C  s   | j sdS t| j S )u2   获取随机代理（无代理时返回 None）。N)rQ   rg   rh   r   r   r   r   	get_proxy  s   zAntiBlockingStrategy.get_proxyc                   s   |   I dH  dS )u9   兼容旧接口（旧代码直接调用 apply_delay）。N)r]   rq   r   r   r   apply_delay  s   z AntiBlockingStrategy.apply_delayc                   s   dS )uM   兼容旧接口 — 现在由令牌桶统一管理，此方法为空操作。Nr   rq   r   r   r   check_rate_limit  s   z%AntiBlockingStrategy.check_rate_limitc                   sV   t | j| j}t| jt| j|}t	d|dd| j
 d t|I dH  dS )u[   正态分布随机延迟（μ=中点，σ=范围/4），截断到 [delay_min, delay_max]。u   [anti-block] 延迟 r+   zs (category=)N)rg   gaussrU   rV   maxrN   minrP   r   r0   r=   r   r1   )r   delayr   r   r   r]     s
   z(AntiBlockingStrategy._apply_normal_delay)rK   rL   r   NrM   N)rN   rO   rP   rO   r=   r   rQ   rR   r;   r<   rS   rT   )rZ   )r[   r<   r   )r=   rT   r>   r`   )r>   rp   )r6   r7   r8   r9   ri   USER_AGENTSr    r_   ro   rr   rs   rt   r]   r   r   r   r   rI      s     ,

rI   r[   c                 C  s(   zt | jp| W S  ty   |  Y S w )u5   从 URL 提取域名，失败时返回原字符串。)r   netloc	Exception)r[   r   r   r   r\   *  s
   r\   )r;   r<   r=   r   r>   r   )r[   r<   r>   r<   )r9   
__future__r   r   mathrg   r-   typingr   r   r   urllib.parser   logurur   r   __annotations__rA   r   r:   r   r?   rC   ri   rj   rk   rI   r\   r   r   r   r   <module>   s    	+
- 