
    Ri'                        U d Z ddlZddlZddlZddlmZmZ ddlZddl	Z	ddl
mZ  G d d      Zdedefd	Z	 	 	 dd
ededee   dee   def
dZh dh dh ddZeeee   f   ed<   d dedee   fdZh dZdedefdZdedefdZd!dededefdZ ej8                  dej:                        Z ej8                  dej:                        Zh dZ dedefdZ!y)"u   
crawl_utils.py — 크롤링 유틸리티 모음.

ProxyRotator, is_proxy_error, fetch_with_retry,
get_resource_block_types, html_to_markdown, clean_html 제공.
    N)AnyOptional)BeautifulSoupc                   Z    e Zd ZdZddee   deddfdZdee   fdZdeddfd	Z	de
fd
Zy)ProxyRotatoru   프록시 목록을 순환하는 클래스.

    Args:
        proxies: 프록시 URL 리스트.
        strategy: "round_robin" 또는 "random".
    proxiesstrategyreturnNc                 @    t        |      | _        || _        d| _        y )Nr   )list_proxies	_strategy_index)selfr   r	   s      D/home/jay/workspace/.worktrees/task-2117-dev1/scripts/crawl_utils.py__init__zProxyRotator.__init__   s    #'=!    c                    | j                   sy| j                  dk(  rt        j                  | j                         S | j                   | j                  t        | j                         z     }| xj                  dz  c_        |S )uH   다음 프록시를 반환한다. 빈 리스트면 None을 반환한다.Nrandom   )r   r   r   choicer   lenr   proxys     r   get_nextzProxyRotator.get_next#   s^    }}>>X%==//dkkC,>>?qr   r   c                    	 | j                   j                  |       | j                   rJ| j                  t        | j                         k\  r(| j                  t        | j                         z  | _        yd| _        y# t        $ r Y yw xY w)uF   특정 프록시를 목록에서 제거한다. 없으면 무시한다.r   N)r   remover   r   
ValueErrorr   s     r   r   zProxyRotator.remove.   sf    	MM  '}}DMM0B!B"kkC,>> 		s   A0A; 3A; ;	BBc                 ,    t        | j                        S )u%   남은 프록시 수를 반환한다.)r   r   )r   s    r   __len__zProxyRotator.__len__:   s    4==!!r   )round_robin)__name__
__module____qualname____doc__r   strr   r   r   r   intr     r   r   r   r      sT    S	 S T 
	(3- 	
C 
D 
" "r   r   errorr
   c                 Z    t        | dd      }|t        |d      ryt        | t              S )u   프록시 관련 오류 여부를 판별한다.

    - response.status_code 속성이 있으면 HTTP 오류로 간주 → False
    - OSError / ConnectionError / TimeoutError 계열 → True
    - 그 외 → False
    responseNstatus_codeF)getattrhasattr
isinstanceOSError)r)   r+   s     r   is_proxy_errorr1   D   s1     uj$/H- @eW%%r   urlmax_retriesproxy_rotatorfetcher_classc                    |ddl }|j                  } |       }d}d}t        |      D ]?  }||j                         }	 ||j	                  | |      }	n|j	                  |       }	|	c S  |J |# t
        $ rN}
|
}t        |
      r|||j                  |       d}||dz
  k  rt        j                  d|z         Y d}
~
d}
~
ww xY w)u  재시도 로직을 포함한 URL 페치 함수.

    Args:
        url: 가져올 URL.
        max_retries: 최대 재시도 횟수.
        proxy_rotator: ProxyRotator 인스턴스 (선택).
        fetcher_class: 페처 클래스. None이면 scrapling.Fetcher 사용.

    Returns:
        페처의 fetch() 반환값.

    Raises:
        마지막 시도까지 실패하면 마지막 예외를 raise.
    Nr   )r   r      )
	scraplingFetcherranger   fetch	Exceptionr1   r   timesleep)r2   r3   r4   r5   r8   fetcherlast_exccurrent_proxyattemptresultexcs              r   fetch_with_retryrE   W   s    ( !))oG$(H#'M% '$)224M	'( s-@ s+M'& 
N  	'Hc"}'@]E^$$]3 $q(

1g:&	's   (A11	C:ACC>   fontimagemediaother
stylesheet>   rF   rG   rH   >   rF   rG   rH   rI   manifest	websocketrJ   )defaultminimal
aggressive_RESOURCE_BLOCK_PRESETSpresetc                 &    t        t        |          S )u   리소스 차단 타입 집합을 반환한다.

    Args:
        preset: "default", "minimal", "aggressive" 중 하나.

    Returns:
        차단할 리소스 타입 집합.
    )setrP   )rQ   s    r   get_resource_block_typesrT      s     &v.//r   >   svgstylescriptnoscripthtmlc                     t        | d      }t        D ](  }|j                  |      D ]  }|j                           * t	        |      S )uY   BeautifulSoup을 사용하여 script/style/noscript/svg 태그와 내용을 제거한다.html.parser)r   _NOISE_TAGSfind_all	decomposer&   )rY   souptagelems       r   _remove_noise_tags_bs4rb      sK    }-D MM#& 	DNN	 t9r   c                     t        | d      }|j                  d      D ]2  }|j                         }|j                  |j	                  |             4 t        |      S )u   BeautifulSoup을 사용하여 script 태그를 텍스트 노드로 교체한다.

    markdownify는 script 태그 내용을 무시하므로, remove_noise=False일 때
    script 태그를 텍스트로 변환하여 내용을 보존한다.
    r[   rW   )r   r]   get_textreplace_with
new_stringr&   )rY   r_   
script_tagtexts       r   _expand_script_tags_to_textri      sY     }-DmmH- 7
""$ 567 t9r   remove_noisec                     |rt        |       } nt        |       } t        j                  | d      }t        j                  dd|      }|S )u   HTML을 Markdown으로 변환한다.

    Args:
        html: 변환할 HTML 문자열.
        remove_noise: True이면 script/style/noscript/svg를 먼저 제거.

    Returns:
        변환된 Markdown 문자열.
    atx)heading_stylez\n{3,}z

)rb   ri   markdownifyresub)rY   rj   mds      r   html_to_markdownrr      sH     %d+ +40%%d%@B 
	62	&BIr   z-\s+on\w+\s*=\s*(?:"[^"]*"|\'[^\']*\'|[^\s>]*)z%\s+style\s*=\s*(?:"[^"]*"|\'[^\']*\')c           	      j   | sy	 t         j                  j                  |       }t        D ]?  }|j	                  d|       D ]&  }|j                         }||j                  |       ( A |j                         D ]b  }|j                  D cg c]7  }|j                         j                  d      s|j                         dk(  s6|9 }}|D ]  }|j                  |=  d t         j                  j                  |d      }t        |t              r|}|S |j                  d      }|S c c}w # t        $ r{ t        D ]A  }t!        j"                  d| d	| d
d| t         j$                  t         j&                  z        } C t(        j#                  d|       } t*        j#                  d|       } | cY S w xY w)u  HTML에서 노이즈 태그와 위험 속성을 제거한다.

    - script/style/noscript/svg 태그 + 내용 제거
    - onclick, onload, onmouseover 등 이벤트 핸들러 속성 제거
    - style 인라인 속성 제거
    - href, src, class, id 등 유용한 속성은 유지

    Args:
        html: 정리할 HTML 문자열.

    Returns:
        정리된 HTML 문자열.
     z//onrV   unicode)encodingzutf-8<z
[\s\S]*?</>)flags)lxmlrY   
fromstring_CLEAN_TAGSxpath	getparentr   iterattriblower
startswithtostringr/   r&   decoder<   ro   rp   
IGNORECASEDOTALL_EVENT_HANDLER_PATTERN_STYLE_ATTR_PATTERN)	rY   docr`   ra   parentattrattribs_to_removerawrC   s	            r   
clean_htmlr      s    !ii""4(  	(C		Bse*- ()%MM$'(	( HHJ 	&D!%!

0G0G0MQUQ[Q[Q]ahQh! ! * &KK%&		& ii  y 9'S1c 8;zz'7J!   	C66SEC5*mmbii/	D	 &))"d3"&&r40s8   AD. 6D. 7D)D)	AD. D. )D. .BF21F2)   NN)rM   )T)"r%   r   ro   r=   typingr   r   	lxml.htmlr{   rn   bs4r   r   r<   boolr1   r&   r'   rE   rP   dictrS   __annotations__rT   r\   rb   ri   rr   compiler   r   r   r}   r   r(   r   r   <module>r      sb    	      &" &"\&) & &* #'#'	2	22 C=2 C=	2
 	2t A)0 c3s8m, 	0S 	0S 	0  5  
c 
c 
3 d c 6 $4MM  !bjj,MM 
 52S 2S 2r   