def __init__(self, queue_log: Queue[Any], ctx: Dict[str, Any], limit: int = 0): worker_configurer(queue_log, logger) super(MemoryObserverThread, self).__init__() self.ctx = ctx self.limit = limit
def __init__(self, queue_log: Queue[Any], options: FirefoxOptions, ffprofile: FirefoxProfile, capabilities: Dict[str, Any]): super(GetFirefoxDriverThread, self).__init__() worker_configurer(queue_log, logger) self.options = options self.fpro = ffprofile self.capabilities = capabilities self.driver = False self.re = False
def del_and_make_achievement(path: str, queue_log: Queue[Any]): """ クローリング完了後に実行 result/result_*に分けて保存されているデータをまとめて、result/achievement/に保存 不要なtempファイルを削除する args: path: org_path /result_history/*/ """ worker_configurer(queue_log, logger) # ファイル位置を絶対パスで取得 now_dir = os.path.dirname(os.path.abspath(__file__)) del_temp_file(path, 0, 0) del_dir(path, 0, 0) logger.debug('making achievement...') make_achievement(path) logger.debug('making achievement...FIN!') logger.debug('merging server dir...') merge_server_dir(path) logger.debug('merging server dir...FIN!') cal_num_of_achievement(path) # 実行ディレクトリに戻る os.chdir(now_dir)
if __name__ == "__main__": """ save_result を実行すると終了処理を全部やってくれる なぜか最後の処理だけされないことが多発したため作った """ organization = "ritsumeikan" queue_log: Queue[Any] = Queue(-1) now_dir = os.path.dirname(os.path.abspath(__file__)) os.chdir(now_dir) log_listener = Process(target=log_listener_process, args=(now_dir, queue_log, log_listener_configure)) log_listener.start() logger = getLogger(__name__) worker_configurer(queue_log, logger) organization_path = organization_path = now_dir[0:now_dir.rfind('/')] + '/organization/' + organization print(organization_path) # result_historyディレクトリがなければ作成 if not os.path.exists(organization_path + '/result_history'): os.mkdir(organization_path + '/result_history') dir_name = str(len(os.listdir(organization_path + '/result_history')) + 1) org_arg = {'result_no': dir_name, 'org_path': organization_path} logger.info('save used ROD before overwriting the ROD directory : START') save_rod(org_arg) logger.info('save used ROD before overwriting the ROD directory : DONE') logger.info('---dealing after fact---')
def summarize_alert_main(queue_log: Queue[Any], recv_q: Queue[Union[Alert, str]], send_q: Queue[str], nth: int, org_path: str): """ Alertが出た場合に記録するためのプロセス スレッドとの通信キューに何もなければ一旦スリープすることでCPU負荷を下げる Args: - recv_q: 各プロセスからのアラートデータを受け取るためのキュー, endがきたら終了 - send_q: - nth: - org_path: organizationのパス """ worker_configurer(queue_log, logger) alert_dir_path = org_path + '/alert' # alertディレクトリを作成 if not path.exists(alert_dir_path): mkdir(alert_dir_path) # アラート受信スレッドとの通信用キュー data_list: threadQueue[Any] = threadQueue() t = Thread(target=receive_alert, args=(recv_q, data_list)) t.start() while True: if not data_list.empty(): try: temp = data_list.get() except Empty: logger.info('data_list is empty....') continue else: if temp == 'end': logger.info('receive end!!') t.join() break alert = cast(Alert, temp) file_name = alert.file_name content = alert.content label = alert.label # label と content にnthを追加 label = 'Nth,' + label content = str(nth) + ', ' + content # "falsification.cysec.cs.ritsumei.ac.jp"がURLに含まれる場合、ファイル名を変更する if ("falsification.cysec.cs.ritsumei.ac.jp" in alert.url) or ("192.168.0.233" in alert.url): file_name = "TEST_" + file_name # label と content を出力 if file_name.endswith('.csv'): if not path.exists(alert_dir_path + '/' + file_name): w_file(alert_dir_path + '/' + file_name, label + '\n', mode="a") w_file(alert_dir_path + '/' + file_name, content + '\n', mode="a") else: w_file(alert_dir_path + '/' + file_name, content + '\n', mode="a") finally: data_list.task_done() else: sleep(0.1) data_list.join() logger.info("Summarize_alert: FIN") send_q.put('end') # 親にendを知らせる
def configure_logger_for_use_extentions(queue_log: Queue[Any]): """ Loggerをセット logger の使い方が正しくないがゆえにここでセットしないといけない """ worker_configurer(queue_log, logger)
def main(organization: str): # 以下のwhileループ内で # このファイル位置のパスを取ってきてchdirする # 実行ディレクトリはこのファイル位置じゃないとバグるかも(ほぼ全て相対パスだから) now_dir = os.path.dirname(os.path.abspath(__file__)) # ファイル位置(src)を絶対パスで取得 os.chdir(now_dir) # Logger の作成 log_listener = Process(target=log_listener_process, args=(now_dir, queue_log, log_listener_configure)) log_listener.start() worker_configurer(queue_log, logger) worker_configurer_sys_command(queue_log) # 引数として与えられた組織名のディレクトリが存在するか organization_path = now_dir[0:now_dir.rfind('/')] + '/organization/' + organization if not os.path.exists(organization_path): logger.warning('You should check existing %s directory in ../organization/', organization) queue_log.put_nowait(None) log_listener.join() return 0 # 既に実行中ではないか if os.path.exists(organization_path + '/running.tmp'): logger.warning("%s's site is crawled now.", organization) queue_log.put_nowait(None) log_listener.join() return 0 else: # 実行途中ではない場合、ファイルを作って実行中であることを示す f = open(organization_path + '/running.tmp', 'w', encoding='utf-8') start_time = datetime.now().strftime('%Y/%m/%d, %H:%M:%S') f.write(start_time) f.close() # result_historyディレクトリがなければ作成 if not os.path.exists(organization_path + '/result_history'): os.mkdir(organization_path + '/result_history') dir_name = str(len(os.listdir(organization_path + '/result_history')) + 1) org_arg = {'result_no': dir_name, 'org_path': organization_path} while True: # クローラを実行 logger.info(""" --- %s : %s th crawling---""", organization, org_arg['result_no']) p = Process(target=crawler_host, args=(queue_log, org_arg)) p.start() p.join() exitcode: Optional[int] = p.exitcode if (exitcode == 255) or (exitcode < 0): # エラー落ちの場合? logger.error('operate_main ended by crawler error') break logger.info('crawling has finished.') # 孤児のchrome じゃなくてfirefoxをkill kill_chrome(process='geckodriver') kill_chrome(process='firefox-bin') logger.info('save used ROD before overwriting the ROD directory : START') save_rod(org_arg) logger.info('save used ROD before overwriting the ROD directory : DONE') logger.info('---dealing after fact---') dealing_after_fact(queue_log, org_arg) now = datetime.now() logger.info(f""" --- {organization} : {org_arg['result_no']} th crawling DONE --- {now}""") org_arg['result_no'] = str(int(org_arg['result_no']) + 1) logger.info(f"{organization} : {org_arg['result_no']} th crawling will start at 20:00") # 実行ディレクトリ移動 os.chdir(now_dir) break # 実行中であることを示すファイルを削除する if os.path.exists(organization_path + '/running.tmp'): os.remove(organization_path + '/running.tmp') # Logを集めるリスナーを終了させる queue_log.put_nowait(None) log_listener.join()
def dealing_after_fact(queue_log: Queue[Any], org_arg: Dict[str, str]): worker_configurer(queue_log, logger) dir_name = org_arg['result_no'] org_path = org_arg['org_path'] # 偽サイトの情報を削除 if '/organization/ritsumeikan' in org_path: del_falsification_RAD(org_path=org_path) # コピー先を削除 shutil.rmtree(org_path + '/ROD/url_hash_json') shutil.rmtree(org_path + '/ROD/tag_data') # 移動 logger.info('copy file to ROD from RAD : START') shutil.copytree( org_path + '/RAD/df_dict', org_path + '/ROD/df_dicts/' + str(len(os.listdir(org_path + '/ROD/df_dicts/')) + 1)) shutil.move(org_path + '/RAD/url_hash_json', org_path + '/ROD/url_hash_json') shutil.move(org_path + '/RAD/tag_data', org_path + '/ROD/tag_data') shutil.move(org_path + '/RAD/url_db', org_path + '/ROD/url_db') """ if os.path.exists('RAD/screenshots'): # スクショを撮っていたら、0サイズの画像を削除 p = Process(target=del_0size.del_0size_and_rename, args=('RAD/screenshots',)) p.start() p.join() if os.path.exists('ROD/screenshots'): shutil.rmtree('ROD/screenshots') shutil.move('RAD/screenshots', 'ROD/screenshots') """ logger.info('copy file to ROD from RAD : DONE') # make_filter_from_past_data.pyの実行 (tfidfの計算には少し時間(1分くらい)がかかるのでプロセスを分けて logger.info('Run function of tf_idf.py : START') p = Process(target=make_idf_dict_frequent_word_dict, args=( queue_log, org_path, )) # type: ignore p.start() # 立命館サイトは make_host_set()を自動で実行。 # 今回の収集データと過去のデータをマージする作業なので、自動実行すると、今回検出された外部URLが安全なURLとして登録されてしまうため # ちゃんと人が判断してから追加したほうがいい。立命館サイトは面倒なので自動でやっちゃう。 if '/organization/ritsumeikan' in org_path: p2 = Process(target=make_request_url_iframeSrc_link_host_set, args=( queue_log, org_path, )) # type: ignore p2.start() else: p2 = None p.join() if p2 is not None: p2.join() logger.info('Run function of tf_idf.py : DONE') # alertされたデータから新しいフィルタを作る (linkとrequest URL用 logger.info('Making Filter : START') make_filter(org_path=org_path) if '/organization/ritsumeikan' in org_path: # 立命館サイトは merge_filter()を自動で実行。 merge_filter(org_path=org_path) logger.info('Making Filter : DONE') # RADの削除 logger.info('delete RAD : START') shutil.rmtree(org_path + '/RAD') logger.info('delete RAD : DONE') # resultの移動 logger.info('mv result to check_result : START') result_history_path = org_path + '/result_history/' + dir_name shutil.move(src=org_path + '/result', dst=result_history_path) logger.info('mv result to check_result : DONE') # crawler_deinit.pyの実行 del_and_make_achievement(result_history_path, queue_log)
def clamd_main(queue_log: Queue[Any], recvq: Queue[str], sendq: Queue[Union[str, bool]], org_path: str): worker_configurer(queue_log, logger) # clamAVのデーモンが動いているか確認 while True: try: logger.info('Clamd Process connect...') cd = pyclamd.ClamdAgnostic() pin = cd.ping() logger.info('Clamd Process connected!!!') break except ValueError: logger.info('Clamd Process waiting for clamd start....') sleep(3) except Exception as err: pin = False logger.exception(f'Exception has occur: {err}') break sendq.put(pin) # 親プロセスにclamdに接続できたかどうかの結果を送る if pin is False: os._exit(0) # type: ignore # 接続できなければ終わる # EICARテスト eicar = cd.EICAR() # type: ignore cd.scan_stream(eicar) # type: ignore t = Thread(target=receive, args=(recvq,)) # クローリングプロセスからのデータを受信するスレッド t.setDaemon(True) t.start() while True: if not data_list: if end: break # クローリングプロセスからデータが送られていなければ、3秒待機 sleep(3) continue temp = data_list.popleft() url = temp[0] url_src = temp[1] byte = temp[2] # clamdでスキャン if len(byte) < 25 * 1000000: # Max filesize 25MB try: result = cd.scan_stream(byte) # type: ignore except Exception as err: logger.exception(f'Exception has occur, URL={url}, {err}') clamd_error.append(url + '\n' + str(err)) else: # 検知されると結果を記録 if result is not None: w_file(org_path + '/alert/warning_clamd.txt', "{}\n\tURL={}\n\tsrc={}\n".format(result, url, url_src), mode="a") if not os.path.exists(org_path + '/clamd_files'): os.mkdir(org_path + '/clamd_files') w_file(org_path + '/clamd_files/b_' + str(len(listdir(org_path + '/clamd_files'))+1) + '.clam', url + '\n' + str(byte), mode="a") logger.info('clamd have scanned: %s', url) else: logger.info("big file... save to log file") clamd_error.append(url + '\n' + "Over 25.0MB file") # エラーログが一定数を超えると外部ファイルに書き出す if len(clamd_error) > 100: text = '' for i in clamd_error: text += i + '\n' w_file('error_clamd.txt', text, mode="a") clamd_error.clear() text = '' for i in clamd_error: text += i + '\n' if clamd_error: w_file('error_clamd.txt', text, mode="a") clamd_error.clear() logger.debug("Clamd end") sendq.put('end') # 親にendを知らせる
def get_fox_driver(queue_log: Queue[Any], screenshots: bool = False, user_agent: str = '', org_path: str = '') -> Union[bool, Dict[str, Any]]: """ Firefoxを使うためのdriverをヘッドレスモードで起動 ファイルダウンロード可能 RequestURLの取得可能(アドオンを用いて) ログコンソールの取得不可能(アドオンの結果は</body>と</html>の間にはさむことで、取得する) """ worker_configurer(queue_log, logger) logger.debug("Setting FireFox driver...") # headless FireFoxの設定 options: FirefoxOptions = make_firefox_options() profile: FirefoxProfile = make_firefox_profile(org_path, user_agent) caps: Dict[str, Any] = make_firefox_caps() # Firefoxのドライバを取得。ここでフリーズしていることがあったため、スレッド化した # Todo: メモリが足りなかったらドライバーの取得でフリーズする try: t = GetFirefoxDriverThread(queue_log=queue_log, options=options, ffprofile=profile, capabilities=caps) t.start() t.join(10.0) except Exception as err: # runtime error とか logger.info(f'Faild to get Firefox Driver Thread, retrying: {err}') sleep(10.0) pass if t.re == False: # 1度だけドライバ取得をリトライする logger.info('retry to get webdriver') if type(t.driver) == WebDriver: logger.info('quit past driver') driver = cast(WebDriver, t.driver) quit_driver(driver) # 一応終了 try: t = GetFirefoxDriverThread(queue_log=queue_log, options=options, ffprofile=profile, capabilities=caps) t.start() t.join(10.0) except Exception as err: # runtime error とか logger.debug( f'Faild to get Firefox Driver Thread again, Failed: {err}') t.re = False pass if t.re == False: # ドライバ取得でフリーズする等のエラー処理 logger.info("Failed to getting driver: thread freezed") if type(t.driver) == WebDriver: driver = cast(WebDriver, t.driver) quit_driver(driver) # 一応終了させて return False if t.driver is False: # 単にエラーで取得できなかった場合 logger.info("Failed to getting driver: thread couse error") return False if type(t.driver) == WebDriver: driver = cast(WebDriver, t.driver) driver.set_window_size(1280, 1024) else: return False # 拡張機能のwindowIDを取得し、それ以外のwindowを閉じる # geckodriver 0.21.0 から HTTP/1.1 になった? Keep-Aliveの設定が5秒のせいで、5秒間driverにコマンドがいかなかったらPipeが壊れる. # 0.20.1 にダウングレードするか、seleniumを最新にアップグレードすると、 Broken Pipe エラーは出なくなる。 wait = WebDriverWait(driver, 5) watcher_window = get_watcher_window(driver, wait) if watcher_window is False: logger.warning("Fail to get watcher window, return fail") quit_driver(driver) return False watcher_window = cast(Union[str, int], watcher_window) logger.debug("Setting FireFox driver... FIN!") return {"driver": driver, "wait": wait, "watcher_window": watcher_window}
def worker_configurer_sys_command(queue_log: Queue[Any]): """ Loggerをセット """ worker_configurer(queue_log, logger)
def make_idf_dict_frequent_word_dict(queue_log: Queue[Any],org_path: str): """ クローリング後にサーバごとの tf-idf値 ページのロードに行ったリクエストのURL集合 iframeのsrc先URLの集合 をまとめて保存する また、リンク、リクエストURLの既知サーバを示すホワイトリストのフィルタを作成する """ worker_configurer(queue_log, logger) df_dict: Dict[str, Dict[str, Any]] = dict() # {file名(server.json) : {単語:df値, 単語:df値, ...}, server : {df辞書}, ... , server : {df辞書} } if not os.path.exists(org_path + '/ROD/idf_dict'): os.mkdir(org_path + '/ROD/idf_dict') n = 100 # 頻出単語上位n個を保存 if not os.path.exists(org_path + '/ROD/frequent_word_' + str(n)): os.mkdir(org_path + '/ROD/frequent_word_' + str(n)) dir_list = os.listdir(org_path + '/ROD/df_dicts/') # このフォルダ以下の全てのdfフォルダをマージしてidf辞書を作る for df_dict_dir in dir_list: pickle_file_list = os.listdir(org_path + '/ROD/df_dicts/' + df_dict_dir) for pickle_file in pickle_file_list: # 途中からpickleファイルに変更した with open(org_path + '/ROD/df_dicts/' + df_dict_dir + '/' + pickle_file, 'rb') as f: dic = pickle.load(f) # df辞書ロード if len(dic) == 0: # 中身がなければ次へ continue # df_dictはpickleファイルだが、頻出単語ファイルはjsonにするため file_name_json = pickle_file[0:pickle_file.find('.pickle')] + '.json' if file_name_json not in df_dict: df_dict[file_name_json] = dict() df_dict[file_name_json] = dict(Counter(df_dict[file_name_json]) + Counter(dic)) # 既存の辞書と同じキーの要素を足す # この時点で、df辞書のマージ終わり(同じ単語の出現文書数を足し合わせた) # 頻出単語辞書の作成 for file_name, dic in df_dict.items(): frequent_words: list[str] = list() for word, _ in sorted(dic.items(), key=lambda x: x[1], reverse=True): if word != 'NumOfPages': frequent_words.append(word) if len(frequent_words) == n: break with open(org_path + '/ROD/frequent_word_' + str(n) + '/' + file_name, 'w') as f: json.dump(frequent_words, f) # idf辞書の作成 # 前回データからのみ pickle_files = os.listdir(org_path + '/RAD/df_dict') c = 0 for pickle_file in pickle_files: idf_dict: Dict[str, float] = dict() with open(org_path + '/RAD/df_dict/' + pickle_file, 'rb') as f: dic = pickle.load(f) if len(dic) == 0: continue N = dic['NumOfPages'] # NumOfPageはそのサーバで辞書を更新した回数 = そのサーバのページ数 if N == 0: continue for word, count in dic.items(): if not word == 'NumOfPages': idf = log(N / count) + 1 idf_dict[word] = idf # 前回に出現していなかった単語のtf-idfを計算するためのidf値 idf = log(N) + 1 # 初登場の単語は、countを1でidf値を計算 idf_dict['NULLnullNULL'] = idf if idf_dict: # df_dictはpickleファイルだが、idfファイルはjsonにするため file_name_json = pickle_file[0:pickle_file.find('.pickle')] + '.json' with open(org_path + '/ROD/idf_dict/' + file_name_json, 'w') as f: json.dump(idf_dict, f) c += 1
def make_request_url_iframeSrc_link_host_set(queue_log: Queue[Any], org_path: str): """ 1. lis = RAD/tempの中のpickleファイルを順番に開いていく(ファイルの中身は辞書) 2. url_set = 過去のデータ(ROD/[object]_url/ホスト名.json)のファイルを開く 3. 辞書から特定のkey(object_list)のデータを取得 4. それぞれのデータ(pick[obj])を過去(ROD)のデータ(url_set)とマージする 5. マージしたデータ(url_set)を ROD/[object]_url/ホスト名.json に保存 """ worker_configurer(queue_log, logger) object_list = ['request', 'iframe', 'link', 'script'] # RODに保存dirがなければ作る for obj in object_list: if not os.path.exists(org_path + '/ROD/' + obj + '_url'): os.mkdir(org_path + '/ROD/' + obj + '_url') # 今回のクローリングで集めたデータのpickleファイルのリストを取得 lis = os.listdir(org_path + '/RAD/temp') global request_url, iframe_url, link_url, script_url # global変数じゃないとexec関数で未定義のエラーが出る(たしか file_name_set = set() # .pickleを.jsonに名前変更した集合 for file in lis: # 1. with open(org_path + '/RAD/temp/' + file, 'rb') as f: pick = pickle.load(f) file = file[file.find('_') + 1:file.find('.')] + '.json' file_name_set.add(file) # それぞれのデータを過去のデータとマージしてjsonで保存 for obj in object_list: # 2. if os.path.exists(org_path + '/ROD/' + obj + '_url/' + file): with open(org_path + '/ROD/' + obj + '_url/' + file, 'r') as f: url_set = set(json.load(f)) else: url_set = set() # 3. 4. if obj in pick and pick[obj]: url_set.update(pick[obj]) # 5. if url_set: exec(obj + "_url.update(url_set)") with open(org_path + '/ROD/' + obj + '_url/' + file, 'w') as f: json.dump(list(url_set), f) # 今回見つからなかったが、過去に回ったことのあるサーバのRODデータをmatome.jsonに入れるためにそれぞれの集合に追加する # 上記の処理では、tempディレクトリを参考にしているため、今回のクローリングで見つからなかったサーバのデータは # それぞれの集合(request_urlとiframe_urlとlink_url)に入っていないため、過去の情報を追加する if '/organization/ritsumeikan' in org_path: file_name_set.add('falsification-cysec-cs-ritsumei-ac-jp.json') # 偽サイト情報はmatome.jsonに入れない # ROD/request, iframe, script, linkの各JSONデータをmatome.jsonとしてまとめる for obj in object_list: file_names = os.listdir(org_path + '/ROD/' + obj + '_url') for file_name in file_names: # matome.jsonは参照しない(RODには前回使ったmatome.jsonがあるのでそれは無視) if 'matome.json' in file_name: continue # matome.json以外で、過去に見つけていたが今回見つからなかったサーバの情報を追加 # TODO: 未完成のところ if file_name not in file_name_set: with open(org_path + '/ROD/' + obj + '_url/' + file_name, 'r') as f: tmp = json.load(f) # type: ignore try: exec(obj + "_url.update(set(tmp))") except Exception as err: logger.exception(f'{err}') # 全サーバの情報をまとめた集合を保存、クローリング時にはこれを使う with open(org_path + '/ROD/' + obj + '_url/matome.json', 'w') as f: try: exec("json.dump(list(" + obj + "_url), f)") except Exception as err: logger.exception(f'{err}')