o
    J*j(.                  	   @   sR  d Z ddlZddlZddlZddlmZ dZe Zej	ddddd	d
 dRde
dedefddZdefddZdZdZdSde
de
defddZdZdTde
dededefddZd ZdUd"e
d#e
d$edefd%d&Zh d'Zeg d(ed)d*Zd+e
de
fd,d-ZdVd/ed0edefd1d2Zd3efd4d5Zd6efd7d8Zed9kr%d:Z d;Z!d<d=gZ"e#d> ee e!d?Z$e$ree$ ne#d@ e#dA ee ddBdCZ%e%ddD D ]Z&e&dE pddd Z'e#dFe&dG  dHe' dI qe#dJ ee dddCZ(ee(dKdLZ)e)D ]Z*e#dFe*d"  dMe*dN  dO qe#dP e"D ]Z+ee+e d!dQZ,ee, e-d qdS dS )Wuh   
네이버 플레이스 크롤러 (GraphQL API 기반)
- pcmap-api.place.naver.com/place/graphql 사용
    N)datetimez/https://pcmap-api.place.naver.com/place/graphqlzoMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36z*/*zko-KR,ko;q=0.9zapplication/jsonzhttps://pcmap.place.naver.com/)z
User-AgentAcceptzAccept-LanguagezContent-TypeRefererquery	variablesreturnc              
   C   s   | |pi dg}z"t jt|dd}|  | }t|tr(|d dp&i W S i W S  tyE } zt	d|  i W  Y d }~S d }~ww )N)r   r      )jsontimeoutr   dataz[GQL ERROR] )
SESSIONpostGRAPHQL_URLraise_for_statusr	   
isinstancelistget	Exceptionprint)r   r   bodyrr   e r   %/var/www/html/bicorn/place/crawler.py_gql   s   $r   c                 C   s(   | du rdS t t| dd pdS )u   '4,460' → 4460Nr   , )intstrreplacestrip)sr   r   r   
_parse_num#   s   r"   z
query getPlaceDetail($query: String!, $display: Int) {
  places(input: {query: $query, display: $display}) {
    items {
      id name category address roadAddress phone
      visitorReviewCount blogCafeReviewCount x y
    }
  }
}zn
query getPlaceRating($id: String!) {
  placeDetail(input: {id: $id}) {
    base { visitorReviewsScore }
  }
}r   place_id	hint_namec                    s>  |p }t t|dd}|di dg }t fdd|D d}|sD|rDt t dd}|di dg }t fd	d|D d}|sPtd
  d i S t td i}|di di ddpfd}	t|dd}
t|dd} |dd|dd|dp|dd|ddt|	|
|t	 
dd	S )u   
    place_id 에 해당하는 업체 기본정보 수집
    hint_name: DB에 저장된 업체명 (검색 정확도 향상)
       r   displayplacesitemsc                 3   ,    | ]}t |d dt  kr|V  qdS idr   Nr   r   .0itr#   r   r   	<genexpr>H      * z$get_place_summary.<locals>.<genexpr>N   c                 3   r*   r+   r-   r.   r1   r   r   r2   N   r3   z[WARN] place_id u"    검색 결과에서 찾지 못함r,   placeDetailbasevisitorReviewsScoreg        visitorReviewCountr   blogCafeReviewCountnamer   categoryroadAddressaddressphone%Y-%m-%d %H:%M:%S)	r#   r:   r;   r=   r>   ratingvisitor_review_countblog_review_countcollected_at)r   PLACE_SUMMARY_Qr   nextr   PLACE_RATING_Qr"   floatr   nowstrftime)r#   r$   	query_strr   r)   matcheddata2items2rating_datar@   v_countb_countr   r1   r   get_place_summary>   s2    


rQ   a  
query getReviews($id: String!, $page: Int, $size: Int) {
  visitorReviews(input: {businessId: $id, page: $page, size: $size, sort: "RECENT"}) {
    total
    items {
      id
      author  { nickname }
      rating
      body
      visitCount
      created
    }
  }
}   2   pagesizec                 C   s   t t| ||d}|di dg }g }|D ]/}||dd|di dd|d|d	d|d
d|ddt dd q|S )N)r,   rT   rU   visitorReviewsr)   r,   r   authornicknamer@   r   
visitCountr   createdr?   )	review_idrW   r@   contentvisit_count
created_atrC   )r   REVIEW_Qr   appendr   rH   rI   )r#   rT   rU   r   r)   reviewsr0   r   r   r   get_place_reviews|   s   




	rb   z
query getPlaceRank($query: String!, $display: Int) {
  places(input: {query: $query, display: $display}) {
    total
    items {
      id name category visitorReviewCount
    }
  }
}   keywordmy_place_idmax_rankc                 C   s   t t| |d}|di }|dg }|dd}d }g }t|dD ]2\}	}
t|
dd}||	||
d	d|
d
dt|
dd|t|kd |t|krU|	}q#| |t||t	 
ddS )Nr&   r(   r)   totalr   rR   r,   r   r:   r;   r8   )rankr#   r:   r;   review_countis_my_placer?   )rd   my_ranktotal_foundcompetitorssearched_at)r   RANK_Qr   	enumerater   r`   r"   lenr   rH   rI   )rd   re   rf   r   resultr)   rg   rh   rm   ir0   pidr   r   r   get_place_rank   s2   


ru   >6      것   곳   더   때   또   번   수   잘   좀   한   갔어   같은   그냥   너무   다시   다음   되게   매우   아주   없는   예요   완전   왔어   이었   있고   있는   정말   진짜   하고   하는   했어	   같아요	   없어요	   있어요   가   과   나   는   도   로   를   에   와   은   을   의   이   까지   부터   에서   으로   이고   이나	   이에요).r   u	   이고요u	   이라고u	   이라서u	   이었어u	   이에서u   어요u   아요u   이에r   u   이며r   u   이라u   이다r   r   r   u   이랑u   처럼u   만큼u   한테u   에게r   u	   습니다u   니다u   어서u   아서u   네요u   군요r   r   r   r   r   r   r   r   r   r   r   r   r   u   랑u   게u   고u   서Tkeyreversewordc                 C   sB   t D ]}| |rt| t| dkr| dt|    S q| S )uR   조사/어미 제거 → 어근 반환 (어근이 2자 미만이면 원형 유지)   N)_KR_SUFFIXESendswithrq   )r   suffixr   r   r   _normalize_kr   s
   r   r%   ra   top_nc                 C   s   i }| D ]1}| ddpd}td|D ] }|tv rqt|}|tv s)t|dk r*q| |dd ||< qqt| dd d	d
}dd |d | D S )Nr\   r   u   [가-힣]{2,}r   r   rR   c                 S   s   | d S )NrR   r   )xr   r   r   <lambda>   s    z)analyze_review_keywords.<locals>.<lambda>Tr   c                 S   s   g | ]	\}}||d qS ))rd   countr   )r/   wcr   r   r   
<listcomp>   s    z+analyze_review_keywords.<locals>.<listcomp>)r   refindall	STOPWORDSr   rq   sortedr)   )ra   r   
word_countreviewr\   r   stemsorted_wordsr   r   r   analyze_review_keywords   s   r   dc                 C   s   t d t d| d  t d| d  t d| d  t d| d	  t d
| d d t d| d d t d| d  t d d S )Nz3
==================================================u     업체명    : r:   u     카테고리  : r;   u     주소      : r=   u     전화      : r>   u     별점(평균): r@   u    ⭐u     방문자리뷰: rA      개u     수집시간  : rC   z2==================================================)r   r   )r   r   r   r   print_summary   s   r   r   c              
   C   s   t d| d d | d}t d|rd|nd d| d	 d
 t d | dg d d D ] }|d r;dnd}t d|d  d|d  d|d  d
|  q3d S )Nu   
검색어: [rd   ]rk   u     내 업체: u   {}위u   순위권 밖u
    / 전체 rl   r   u     상위 5개:rm   r4   rj   u    ← 내 업체r   z    rh   u   위 r:   u
    | 리뷰 ri   )r   r   format)r   rh   r   markr   r   r   
print_rank   s   
*.r   __main__11830966u   소들녘 광명점u   광명 소고기u   광명 갈비u   📍 기본정보 수집...)r$   u   수집 실패u   
📝 최신 리뷰 수집...r4   )rT   rU      r\   z  rW   z | z...u   
🔑 키워드 분석...
   )r   z: r   u   회u   
📊 순위 확인...)rf   )N)r   )rR   rS   )rc   )r%   ).__doc__r   timerequestsr   r   Sessionr   headersupdater   dictr   r   r"   rD   rF   rQ   r_   r   rb   ro   ru   r   r   rq   r   r   r   r   r   __name__MY_PLACE_IDMY_PLACE_NAMEKEYWORDSr   summaryra   r   r   all_reviewskwskkw	rank_datasleepr   r   r   r   <module>   sr    	

/
!		

 