
    'j7                     F   d Z ddlZddlZddlZddlZddlZddlZddlmZ ddlZej                  j                  ej                  j                  e            Zej                  j                  ed      Zej                  j                  ed      ZdZdZdZd	Zd
ZddiddiddiddiddiddiddiddiddiddidddddddZdddddd d!d"d#d$d%d&dZ ej0                  d'ej2                        Zd( Zd) Zd* Zd+ Zd, Zd- Z d. Z!e"d/k(  r ejF                   e!              yy)0ud  
update_dnsservers.py

每小時由 cron 執行一次, 從 dnschecker.org 首頁抓取各地區節點實際使用的 DNS Server IP,
結果寫入 ddnslookup-dnsservers.json 供 ddnslookup.php 讀取。

說明:
  - dnschecker.org 受 Cloudflare 保護, 一般 curl/requests 會被擋, 故使用 cloudscraper。
  - 首頁節點清單為隨機輪替子集, 因此連抓數次取聯集以提高覆蓋率。
  - 找不到對應節點的地區 (例如 dnschecker 探測池沒有日本/英國) 使用 FALLBACK 值。
  - 若整批抓取失敗 (一個節點都沒抓到), 不覆寫舊 JSON, 保留上次良好結果。
    N)datetimezddnslookup-dnsservers.jsonzupdate_dnsservers.logzhttps://dnschecker.org/   (   i      ccjpcaruzafrgbsgcnauinuszSan Francisco)r   matchAshburn)TokyoCanadaRussianAfricaFranceUK	SingaporeChina	AustraliaIndiaSan_FranciscoNY z208.91.112.53zdns1.safedns.comzns1.gns.co.zaz7.86.145.83.rev.sfr.netzcns2.cw.netz103.86.99.100zpublic1.alidns.comzone.one.one.onezns1.phillipcapital.inzresolver3.opendns.comzrdns.dynect.netzZflags/svg/([a-z]{2})\.svg"[^>]*alt="[a-z]{2}"\s*/>([^<]+).*?data-clipboardtext="([0-9.]+)"c                  T   	 t         j                  j                  t              r*t         j                  j	                  t              t
        k  rydt        t        fz  } t         j                  j                  |       rt        j                  |        t        t        dz
  dd      D ]O  }dt        |fz  }t         j                  j                  |      s.t        j                  |dt        |dz   fz         Q t        j                  t        dt        z         y# t        $ r Y yw xY w)uL   日誌超過上限時輪替: .log -> .log.1 -> .log.2 ... 最舊的丟棄。Nz%s.%d   r   z%s.1)ospathexistsLOG_FILEgetsizeLOG_MAX_BYTESLOG_BACKUPSunlinkrangereplaceOSError)oldestisrcs      "/var/www/html/update_dnsservers.py
rotate_logr4   L   s    ww~~h'277??8+D}+THk2277>>&!IIf{Q2. 	=AXqM)Cww~~c"

38QU*; ;<	= 	

8Vh./ s    AD BD AD 	D'&D'c                    t                t        j                         j                  d      d| d}	 t	        t
        dd      5 }|j                  |       d d d        t        |d       y # 1 sw Y   xY w# t        $ r Y %w xY w)	Nz%Y-%m-%d %H:%M:%S 
autf-8encodingr!   )end)	r4   r   nowstrftimeopenr(   writer/   print)msglinefs      r3   logrE   ]   ss    L //0CDcJD(C'2 	aGGDM	 
$B		 	 s)   A; A/A; /A84A; ;	BBc                     t        j                         } i }d}t        t              D ]  }	 | j	                  t
        t              }|j                  dk7  sd|j                  v rt        d|dz   |j                  fz         Zt        j                  |j                        D ]G  \  }}}|dz  }|j                         }|j                  |g       }	||f|	vs5|	j                  ||f       I t!        j"                  d        t        d	t%        |j'                               |fz         |S # t        $ r}t        d|dz   |fz         Y d}~%d}~ww xY w)
uQ   連抓數次首頁, 回傳 {cc: [(location, ip), ...]} (依出現順序去重)。r   )timeoutzfetch #%d error: %sr#   N   zJust a momentzfetch #%d blocked (status=%s)z!scraped countries=%s raw_nodes=%d)cloudscrapercreate_scraperr-   FETCH_TIMESgetHOMEPAGEFETCH_TIMEOUT	ExceptionrE   status_codetextNODE_REfindallstrip
setdefaultappendtimesleepsortedkeys)
scrapernodes	total_rawr1   respexcr   locipbuckets
             r3   scrape_nodesrc   h   sA   ))+GEI; 	;;x;?D s"o&B/1q5$:J:J2KKL"??4995 	)KBRNI))+C%%b"-FRy&sBi(	) 	

1  +vejjl/CY.OOPL  	%Q45	s   D++	E4EEc                     |j                  |d         }|sy|j                  d      }|r.|D ])  \  }}|j                         |j                         v s'|c S  |d   d   S )u1   依地區設定挑出 IP, 找不到回傳 None。r   Nr   r   r#   )rL   lower)regioncfgr\   
candidatesr   r`   ra   s          r3   pick_ipri      sh    3t9%JGGGE! 	GC{{}		+		 a=    c                     i }t         j                         D ]H  \  }}| rt        |||       nd }|r|||<   t        |   ||<   t	        d|ddt        |   xs dd       J |S )Nzregion 13z -> fallback (zsystem-default))REGIONSitemsri   FALLBACKrE   )r\   serversrf   rg   ra   s        r3   build_serversrr      sp    G}} b,1WVS%(t GFO&v.GFO68F;K;_O_;_`ab Nrj   c                 4   t        j                  t        j                  j	                  |       d      \  }}	 t        j
                  |dd      5 }t        j                  ||d       d d d        t        j                  |d       t        j                  ||        t        j                  j                  |      rt        j                  |       y y # 1 sw Y   kxY w# t        j                  j                  |      rt        j                  |       w w xY w)	Nz.tmp)dirsuffixwr9   r:   Fensure_asciii  )tempfilemkstempr%   r&   dirnamefdopenjsondumpchmodr.   r'   r,   )r&   datafdtmprD   s        r3   write_json_atomicr      s    277??4#8HGBYYr31 	3QIIdAE2	3
e


377>>#IIcN 	3 	3
 77>>#IIcN s#   C  C*4C  CC   7Dc            	         t               } | s~t        d       t        j                  j	                  t
              ryt        D ci c]  }|t        |    }}t        t
        |       t        dt        j                  |d      z         yt        |       }t        t
        |       t        dt
        dt        j                  |d             yc c}w )	Nz4ABORT: no node scraped, keep existing JSON (if any).r#   zwrote fallback-only JSON: %sFrw   z	OK wrote z -> r   )rc   rE   r%   r&   r'   OUT_FILErn   rp   r   r}   dumpsrr   )r\   rrq   s      r3   mainr      s    NEBC77>>(#+23a1hqk>33(G,*TZZe-TTUE"Gh(xG%)PQR 4s   C__main__)$__doc__r}   r%   resysry   rW   r   rI   r&   r{   abspath__file__BASE_DIRjoinr   r(   rM   rK   rN   r*   r+   rn   rp   compileSrR   r4   rE   rc   ri   rr   r   r   __name__exit rj   r3   <module>r      sp    	 	 
    77??277??84577<<">?77<<"9:$ D\D\D\D\D\D\D\D\D\D\ ?; 95" $'$."$)&,,&  "**aDD"2
		$ zCHHTV rj   