def __init__(self): '''実行時点での最新のmessage.idをセットする''' with Messages(role='slave') as m: item_latest = m.get_latest() r = Connect().open() r.set(app.config['MSG_LAST_PULLED'], item_latest['id'])
def run(self): ''' @return None ''' if not self.items['x'] or not self.items['y']: raise Exception('Must be called set_datasets before Train.train') app.config.from_object('config.Words') vect = TfidfVectorizer(analyzer='word', max_df=0.5, min_df=5, max_features=1280, stop_words=app.config['STOP_WORDS'], ngram_range=(1, 1)) tfidf_fit = vect.fit(self.items['x']) # 分類器が理解できる形式に変換 tfidf_transform = vect.transform(self.items['x']) # TruncatedSVDはKernelPCAと違って標準化する必要がない # n_componentsが次元数を表している # LSAは100次元でも1000次元でも大して精度は変わらない。 # 1000次元にすると処理時間がいっきに長くなる lsa = TruncatedSVD(n_components=128, random_state=0) lsa_fit = lsa.fit(tfidf_transform) lsa_transform = lsa.transform(tfidf_transform) # Grid searchで導き出したパラメータたち clf = PassiveAggressiveClassifier(C=0.1, class_weight=None, fit_intercept=True, loss='hinge', n_iter=5, n_jobs=-1, random_state=0, shuffle=True, verbose=0, warm_start=False) clf_fit = clf.fit(lsa_transform, self.items['y']) # Convert to binary tfidf_pickle = gzip.compress( pickle.dumps(tfidf_fit, pickle.HIGHEST_PROTOCOL)) lsa_pickle = gzip.compress( pickle.dumps(lsa_fit, pickle.HIGHEST_PROTOCOL)) clf_pickle = gzip.compress( pickle.dumps(clf_fit, pickle.HIGHEST_PROTOCOL)) r = Connect().open() with r.pipeline(transaction=False) as pipe: pipe.set(self.tfidf_key, tfidf_pickle) pipe.set(self.lsa_key, lsa_pickle) pipe.set(self.clf_key, clf_pickle) pipe.execute()
def _set(self): # MSG_LAST_PULLEDに最新のmessage.idをセットする with Mysql_messages(role='slave') as m: msg = m.get_latest() r = Connect(host='api', role='master').open() r.set(app.config['MSG_LAST_PULLED'], msg['id'])
def launch(self, url): ''' app.config['URLS']が空の場合は当メソッドを呼び出すこと :param str url ''' self._start(url) r = Connect().open() urls = r.lrange(app.config['URLS'], 0, -1) print('{0} urls are pushed.'.format(len(urls)))
def dump(self): ''' # Redisからデータセットを取り出し、pickle >> gzip >> ファイル書き出し Convert to pickle ''' path = self.directory + self.file con = Connect(host='api', role='slave') r = con.open() ds_pjt_mlm_pos = r.hgetall(app.config['DATASETS_PJT_MLM_POS']) ds_pjt_mlm_neg = r.hgetall(app.config['DATASETS_PJT_MLM_NEG']) ds_msg_pos = r.hgetall(app.config['DATASETS_MSG_POS']) ds_msg_neg = r.hgetall(app.config['DATASETS_MSG_NEG']) url_blacklist = r.smembers(app.config['URL_BLACKLIST']) ds_pjt_mlm_pos_pickle = pickle.dumps( ds_pjt_mlm_pos, pickle.HIGHEST_PROTOCOL) ds_pjt_mlm_neg_pickle = pickle.dumps( ds_pjt_mlm_neg, pickle.HIGHEST_PROTOCOL) ds_msg_pos_pickle = pickle.dumps( ds_msg_pos, pickle.HIGHEST_PROTOCOL) ds_msg_neg_pickle = pickle.dumps( ds_msg_neg, pickle.HIGHEST_PROTOCOL) url_blacklist_pickle = pickle.dumps( url_blacklist, pickle.HIGHEST_PROTOCOL) try: with gzip.open(path.format('ds_pjt_mlm_pos'), 'wb') as f: f.write(ds_pjt_mlm_pos_pickle) with gzip.open(path.format('ds_pjt_mlm_neg'), 'wb') as f: f.write(ds_pjt_mlm_neg_pickle) with gzip.open(path.format('ds_msg_pos'), 'wb') as f: f.write(ds_msg_pos_pickle) with gzip.open(path.format('ds_msg_neg'), 'wb') as f: f.write(ds_msg_neg_pickle) with gzip.open(path.format('url_blacklist'), 'wb') as f: f.write(url_blacklist_pickle) except Exception as e: app.sentry.captureException(str(e)) app.logger.error(str(e)) return app.logger.debug('Export finished')
def _remove(self, item): ''' @param tuple item e.g. (id, body) ''' with Redis_objects() as r: items_c = r.get(self.key_name, reversed_flag=False) if os.environ.get('ENVIRONMENT') == 'development': items_c_len = len(items_c) # 全て表示させると遅くなるので、3の倍数の時だけ出力 if (items_c_len % 3) == 0: print('{0}, Laps: {1}/{2}'.format(self.key_name, str(items_c_len), self.items_len)) del_items = set([]) for item_c in items_c: # 自分自身の場合はスキップ # 対処済みitemの場合はスキップ if item[0] == item_c[0] or int(item[0]) > int(item_c[0]): continue s = difflib.SequenceMatcher(None, item[1], item_c[1]) if s.ratio() > 0.9: del_items.add(item_c[0]) # 一周ごとにまとめて削除 if del_items: Connect().open().hdel(self.key_name, *del_items)
def _send(self, room_id, body): ''' @param int room_id @param string body @return int 正常に完了すれば200が返る。それ以外は500を返すようにしている ''' url = self.url + 'rooms/{0}/messages'.format(room_id) payload = {'body': body} try: res = requests.post(url, headers=self.headers, data=payload) return res.status_code except Exception as e: app.sentry.captureException(str(e)) # chatworkに接続できない場合はjsonで1つの文字列にしてキューに入れる # 接続できない場合はConnectionErrorが発生する Connect().open().rpush( app.config['QUEUE_CHATWORK_MSG'], json.dumps({ 'room_id': room_id, 'body': body })) app.logger.error( 'Chawtwork _send is failed. Reason: {0}'.format(e)) return 500
def _import(self): '''Import from pickle''' path = self.directory + self.file # データセットを取り出して、dumpして、Redisにrestore try: with gzip.open(path.format('ds_pjt_mlm_pos'), 'rb') as f: ds_pjt_mlm_pos_pickle = f.read() with gzip.open(path.format('ds_pjt_mlm_neg'), 'rb') as f: ds_pjt_mlm_neg_pickle = f.read() with gzip.open(path.format('ds_msg_pos'), 'rb') as f: ds_msg_pos_pickle = f.read() with gzip.open(path.format('ds_msg_neg'), 'rb') as f: ds_msg_neg_pickle = f.read() with gzip.open(path.format('url_blacklist'), 'rb') as f: url_blacklist_pickle = f.read() except Exception as e: app.logger.error(e) return ds_pjt_mlm_pos = pickle.loads(ds_pjt_mlm_pos_pickle) ds_pjt_mlm_neg = pickle.loads(ds_pjt_mlm_neg_pickle) ds_msg_pos = pickle.loads(ds_msg_pos_pickle) ds_msg_neg = pickle.loads(ds_msg_neg_pickle) url_blacklist = pickle.loads(url_blacklist_pickle) r = Connect(host='api', role='master').open() with r.pipeline(transaction=True) as pipe: pipe.delete(app.config['DATASETS_PJT_MLM_POS']) pipe.delete(app.config['DATASETS_PJT_MLM_NEG']) pipe.delete(app.config['DATASETS_MSG_POS']) pipe.delete(app.config['DATASETS_MSG_NEG']) pipe.delete(app.config['URL_BLACKLIST']) pipe.hmset(app.config['DATASETS_PJT_MLM_POS'], ds_pjt_mlm_pos) pipe.hmset(app.config['DATASETS_PJT_MLM_NEG'], ds_pjt_mlm_neg) pipe.hmset(app.config['DATASETS_MSG_POS'], ds_msg_pos) pipe.hmset(app.config['DATASETS_MSG_NEG'], ds_msg_neg) for url in url_blacklist: pipe.sadd(app.config['URL_BLACKLIST'], url) pipe.execute()
def _notify_msg(self, item, debug=False): ''' 通知済みのユーザは2度通知しない @param dict item @return int 200 | None ''' if not debug: r = Connect(role='slave').open() # 検知済みuserの取得 # @return set型 spam_user_ids = r.smembers(app.config['MSG_DETECTED_USER_ID']) # 通知済みのuserの場合は通知しない if str(item['user_id']) in spam_user_ids: return None # 違反判定されたmessageの作成者の場合は追加して、次から通知しないようにする r_m = Connect().open() r_m.sadd(app.config['MSG_DETECTED_USER_ID'], item['user_id']) chatwork = Chatwork() res = chatwork.post( app.config['ROOM_ID_MSG'], '''---{0}-----------------------------------\nScore: {1}\nVocabulary: {2}\nBoard Url: {3}\nUser Edit Url: {4}''' .format( datetime.now( pytz.timezone('Asia/Tokyo')).strftime("%Y-%m-%d %H:%M:%S"), item['score'], item['vocabulary'], app.config['URL_BOARD_ADMIN'].format(item['board_id']), app.config['URL_USER_ADMIN'].format(item['user_id']))) return res
def _get(self): ''' 最後に取得した以降に作成されたデータを取得 @return list ''' r = Connect().open() last_pulled_id = r.get(app.config['MSG_LAST_PULLED']) with Messages(role='slave') as m: items = m.get_for_local(last_pulled_id) if not items: return [] r.set(app.config['MSG_LAST_PULLED'], items[-1]['id']) return items
def prepare_urls_pos(self): ''' blacked判定されたユーザのメッセージからurlを全て抜き出し、Redisに保存 本番環境では使わないメソッド http:// がないケースも抜き出してみる。goo.gl/xxx/xxxとか。 @return int ''' # まず消す self.r.delete(app.config['URL_BLACKLIST']) # blackedユーザのmessageを全て取得 with Messages(role='slave') as m: messages = m.get_pos() # 各メッセージからurlを抜き出し、セット urls = [] for msg in messages: url_extract = self.extract_url(msg['description'].decode('utf-8')) if url_extract: urls.extend(url_extract) urls_pos = [] urls_google = [] for url in urls: # 対象にしないurlたち if self._filter_url(url): continue # goo.gl短縮URLの場合は本来のURLを抽出 if 'goo.gl' in url: urls_google.append(url) else: urls_pos.append(url) # set型に変換することで重複しているurlを消す urls_pos = set(urls_pos) urls_google = set(urls_google) urls_google_original = [] for url in urls_google: url_original = self._extract_href_short_google(url) if url_original: if self._filter_url(url_original): continue urls_google_original.append(url_original) #print('Google url: {0} 実際のurl: {1}'.format(url, url_original)) # set型に変換することで重複しているurlを消し、urls_posと連結 urls_last = urls_pos.union(set(urls_google_original)) for url in urls_last: self.add_url_blacklist(url) count = Connect(role='slave').open().scard(app.config['URL_BLACKLIST']) print('URL_BLACKLISTの数: {0} 個のurlをsaddした'.format(str(count)))
def post(self, room_id, body): ''' @param int room_id @param string body @return int 正常に完了すれば200が返る chatworkにメッセージを送る ''' r = Connect().open() # キューに入っているのがあればまとめてpostする # lrangeは値がなければ空のlistを返す items_len = r.llen(app.config['QUEUE_CHATWORK_MSG']) if items_len >= 1: for i in range(items_len): item = r.lpop(app.config['QUEUE_CHATWORK_MSG']) item = json.loads(item) self._send(item['room_id'], item['body']) return self._send(room_id, body)
def emit_spam_update_message(self, args): ''' spam_update_messageイベントをemitする @param dict args @return int Returns the number of subscribers the message was delivered to. ''' r = Connect(host='pubsub', role='master').open() channel_name = 'socket.io#/#{0}#'.format(args['board_id']) # socket.io-redisのイベント名、渡すデータはこのような形である。 data_packed = msgpack.packb([ 'emitter', { # socket.ioはv1からバイナリ形式のデータ、つまり、画像・動画も送信できる # ようになった。文字列型と区別するためにtypeキーがある。 # 文字列型は2で、バイナリ型は5を指定する 'type': 2, 'data': [ 'spam_update_message', { 'board_id': args['board_id'], 'message_id': args['message_id'], 'feedback_from_admin': args['feedback_from_admin'], 'feedback_from_user': args['feedback_from_user'], 'predict': args['predict'], } ], # namespaceのこと 'nsp': '/' }, { 'rooms': [args['board_id']], 'flags': [] } ]) return r.publish(channel_name, data_packed)
def run(self, obj): ''' Queueからmessage_idを取得してspamかどうかを予測する @param str obj msg or pjt @return None ''' r = Connect().open() msg_id = r.lpop(app.config['QUEUE_BASE_MSG']) # キューが空であれば何もしない if not msg_id: return None try: self._detect_msg(int(msg_id)) except Exception as e: app.sentry.captureException(str(e)) app.logger.error( 'predict message is failed. Reason: {0}'.format(e)) # 失敗した場合はキューに戻す r.rpush(app.config['QUEUE_BASE_MSG'], msg_id) raise
def run(self): ''' ''' app.logger.info('run_crawler start') r = Connect().open() while True: url = r.rpop(app.config['URLS']) if not url: app.logger.info('Url is empty. Loop has been done.') print('url is empty. Loop has been done.') break p = Process(target=self._start, args=(url,)) p.start() # 5秒経過しても終了しない場合はTimeout # 例えば、4GBのファイルダウンロードなどに当たるといつまでたっても終わらないので、 # 強制終了させる。本当はrequets側で終了させたいが、そういう設定が無いようなので # ここで終了させる p.join(5) # 生成された子プロセスを終了させる。 p.terminate()
def _predict(self, body): ''' @param str body @return dict ''' # decode_responses=Falseにするためにメソッド内で呼び出している con = Connect(role='slave', decode_responses=False) r_s = con.open() with r_s.pipeline(transaction=False) as pipe: pipe.get(self.vect) pipe.get(self.lsa) pipe.get(self.clf) pickles = pipe.execute() # バイナリーに戻す vect = pickle.loads(gzip.decompress(pickles[0])) lsa = pickle.loads(gzip.decompress(pickles[1])) clf = pickle.loads(gzip.decompress(pickles[2])) # TFIDFはiterableな値しか受けつけないので、リストで渡す tfidf = vect.transform([body]) lsa_reduced = lsa.transform(tfidf) predict = clf.predict(lsa_reduced) score = self._get_score(clf, lsa_reduced) vocabulary = self._get_vocabulary(vect, tfidf) # 1はspam、0はspamではない return { # 1度に1つの対象をpredictすることを想定しているので、0番目を返す # predict[0]は <class 'numpy.int64'>型になっているのでintにする 'predict': int(predict[0]), 'score': score, 'vocabulary': vocabulary }
def run(self): ''' @param string key_name @return None ''' con = Connect(role='slave') r_s = con.open() if not r_s.exists(self.key_name): raise Exception('The key dose not exist in Redis') # GET DATA if self.obj_type == 'pjt_mlm': items = self._get_pjt(work_type='mlm') if self.obj_type == 'pjt_vl': items = self._get_pjt(work_type='vl') if self.obj_type == 'msg': items = self._get_msg() with Pool(processes=app.config['POOL_PROCESS_NUM']) as pool: pool.map(self._add, items)
def _add(self, item): ''' @param tuple e.g. (b_id, [b_body, b_body]) @return dict ''' if self.obj_type == 'pjt_mlm' or self.obj_type == 'pjt_vl': # 比較対象のitemsをセット # 追加しようとしているデータ集合の中に重複しているものが全然あるので、追加するたびに、 # redisから全部取得 >> 追加しようとしているデータとの重複チェック >> セット # redisから全部取得 >> ... というredisとのi/oが無駄に多い処理を行う with Redis_objects() as r: items_c = r.get(self.key_name, reversed_flag=False) wakati = Wakati() item_tup = (item[0], wakati.parse(' '.join(item[1])),) if self.obj_type == 'msg': with Redis_objects() as r: items_c = r.get(self.key_name, reversed_flag=False) # descriptionを連結して、分かち書きにする # ('board_id', 'dec1 dec2 dec3') wakati = Wakati() item_tup = (item[0], wakati.parse(' '.join(item[1])),) overlap = Overlap() is_overlap = overlap.is_overlap(item_tup, items_c) if os.environ.get('ENVIRONMENT') == 'development': # False/:board_idだと重複していないテキストだということ print('{0}, Overlap: {1}/{2}'.format( self.key_name, str(is_overlap), str(item[0]) )) if not is_overlap: # ここでhmset実行 Connect().open().hmset(self.key_name, { item_tup[0]: item_tup[1] })
class Objects(): ''' コンテキストマネージャで呼び出すこと。 ''' def __init__(self, role='slave'): self.role = role def __enter__(self): self.con = Connect(role=self.role) self.r = self.con.open() return self def __exit__(self, exc_type, exc_value, exc_tb): ''' redisの場合はcloseに値するメソッドが用意されていない。 ただ、将来の拡張性を想定して、コンテキストマネージャ型にしておく ''' if exc_type is None: pass else: app.logger.warning('Message Redis connection closing is failed') return False def get(self, key_name, reversed_flag=True): ''' hgetallで取得するデータを逆順で取れるようにするためにこのメソッドを用意している @param string key_name @param bool reversed_flag @return list of tuple e.g. [(id1, 'body1',...}] ''' objects = self.r.hgetall(key_name) # Trueが指定されたら逆順にする if reversed_flag: # 小難しいが、key=lambda x: int(x[0]) こうすることで、id部分で逆順に並べ変えてくれる # redisには数値もstringで入っているので、int()変換してあげる必要がある o_sorted = [(o_id, o_body) for o_id, o_body in sorted( objects.items(), key=lambda x: x[0], reverse=True)] else: o_sorted = [(o_id, o_body) for o_id, o_body in objects.items()] return o_sorted
def _save(self, now, scheme, netloc, path, pwa, urls_external): ''' :return int ''' if scheme == 'http': scheme = 0 elif scheme == 'https': scheme = 1 else: app.logger.info( 'scheme is not http or https. scheme is {0}'.format(scheme)) return 0 host, domain = self._extract_host_domain(netloc) with Urls() as m: if not m.is_exist(netloc): m.add({ 'datetime': now, 'scheme': scheme, 'netloc': netloc, 'host': host, 'domain': domain, 'path': path, 'pwa': pwa, 'urls_external': json.dumps(urls_external) }) if urls_external: with Connect().open().pipeline(transaction=False) as pipe: pipe.lpush(app.config['URLS'], *urls_external) pipe.execute() return 1
class Scraper(): ''' spam messagesには80%以上の確率でurlが含まれる。 url関連の処理は当classにまとめる ''' def __init__(self): '''''' self.r = Connect().open() self.urls_black = self.r.smembers(app.config['URL_BLACKLIST']) def _filter_url(self, url): ''' 対象外にするurlが含まれるかをチェックする @param str url @return bool ''' if 'chatwork.com' in url: return True elif 'lancers.jp' in url: return True #elif 'youtube.com' in url: # return True #elif 'youtu.be' in url: # return True elif 'drive.google.com' in url: return True elif 'accounts.google.com' in url: return True elif 'retrip.jp' in url: return True elif 'mozilla.org' in url: return True elif 'bitcoinlab.jp' in url: return True elif 'www.google.co.jp/chrome/browser' in url: return True else: return False def extract_url(self, body): ''' messageの本文からurlを抜き出す。 @body str body @return list 抜き出したurlをlist形式で返す re.findallは、検索文字列内にパターンがなければ空のlistを返す ''' url_res = [] urls_find = re.findall(app.config['URL_PATTERN'], body) if urls_find: for url_find in urls_find: if not self._filter_url(url_find): url_res.append(url_find) return url_res def _extract_href_short_google(self, url): ''' goo.glの短縮URLサービスのために、リダイレクトページを挟んでいるページのURLを取得する goo.gl以外にも http://bit.ly/2xX9hPB などの短縮URLは存在するが、大抵、200番が 返ってきてrequestsが適切に対処できないので、goo.glだけ対応する @param str url @return str ''' text = self._request_url(url, allow_redirects=False) if not text: return '' href = self._extract_href(text) # hrefが存在しないということはあり得ないが念のため if not href: return '' # goo.glのページに含まれるURLは必ず1つである return href[0] def _extract_href(self, text): ''' htmlエレメントの中か<a>タグのhref属性を全て取得する @param <class 'bs4.element.ResultSet'> _retrieve_url()の返り値を渡すこと @return list href属性の値、つまり、urlをlistでまとめて返す。存在しなければ空のlistを返す ''' soup = BeautifulSoup(text, "lxml") # <body>タグ内の<a>タグを全て取得する # 存在しない場合は空のlistを返す aa = soup.body.find_all('a') if not aa: return [] # <a>タグのhref属性を全て取得する href = [] try: for a in aa: # http(s)で始まるもののみを選別する # 選別しないと相対パスやjavascript:void(0);とかが結構混ざってくる url = self._parse_url(a['href']) if url: href.append(url) # <a>タグがあるにも関わらず、href属性がない場合はスキップ except KeyError as e: pass return href def _parse_url(self, url): ''' @param str url @return str ''' # '//' で始まる場合にのみ netloc を認識する。それ以外の場合は、 # 入力は相対URLであると推定され、path 部分で始まることになる url_parse = urlsplit(url) if not url_parse.netloc: return '' # 末尾に ) が混ざることが多々あるので取り除く if not url_parse.path: path = '' elif url_parse.path[-1] == ')': path = url_parse.path[0:-2] else: path = url_parse.path if url_parse.query: query = '?' + url_parse.query else: query = '' return url_parse.scheme + '://' + url_parse.netloc + path + query def _find_url(self, url): ''' ブラックリストとして登録しているURLが含まれるかを探す @param str url List形式でurlを渡す @return str 見つけたらそのurlを返す。なければ空の文字列を返す ''' url_parsed = self._parse_url(url) # urlの形式ではなければ、空の文字列を返す if not url_parsed: return '' for url_black in self.urls_black: if url_parsed == url_black: return url_parsed # 該当するものがなければ空の文字列を返す return '' def _request_url(self, url, allow_redirects=True): ''' 特定のURLのテキストデータをリクエストして取得する 3.0秒以上経っても返ってこないレスポンスは無視 @param str url @param bool allow_redirects 301が返ってきたときに自動的にそのurlへリダイレクトするか否か。 @return str ''' try: r = requests.get(url, timeout=3.0, headers=app.config['HEADERS'], allow_redirects=allow_redirects) # 3秒以上経過してもレスポンスが返ってこない場合 except requests.exceptions.ConnectTimeout as e: app.sentry.captureException(str(e)) return '' # 何かしらの例外が発生した場合 except: app.sentry.captureException(str(e)) return '' if r.status_code == 404: return '' if r.status_code >= 400: app.sentry.captureMessage( 'スクレイピングでエラーが返ってきた。なぜだ!? status: {0}, url: {1}'.format( r.status_code, url)) return '' return r.text def _find_keyword(self, text): ''' htmlエレメントの中からブラックリストとして登録しているキーワードが含まれるかをチェックする 大文字・小文字を区別しない @param <class 'bs4.element.ResultSet'> _request_url()の返り値を渡すこと @return list キーワードが見つかればその文字列を返す ''' soup = BeautifulSoup(text, "lxml") # <body>タグ内に調べたい文字列があるか調べる # Listで返る。なければ空のListが返る。 # e.g. [' LINE ID ', ' LINE', 'TwitterLine', 'Facebookline'] | [] keywords = soup.body.find_all(text=re.compile( app.config['PATTERN_BLACKLIST_KEYWORDS_COMPILE'], re.IGNORECASE)) if not keywords: return [] # ' LINE ID 'こんな感じに前後にスペースが入っているケースが多いので # strip関数を挟んでもう一度検索する # また、onlineこういうのも抽出してしまうので、ここでもう一度検索し直す texts = [] for keyword in keywords: m = re.search(app.config['PATTERN_BLACKLIST_KEYWORDS_SEARCH'], keyword.strip(), re.IGNORECASE) if m: texts.append(m.group(0)) return texts def prepare_urls_pos(self): ''' blacked判定されたユーザのメッセージからurlを全て抜き出し、Redisに保存 本番環境では使わないメソッド http:// がないケースも抜き出してみる。goo.gl/xxx/xxxとか。 @return int ''' # まず消す self.r.delete(app.config['URL_BLACKLIST']) # blackedユーザのmessageを全て取得 with Messages(role='slave') as m: messages = m.get_pos() # 各メッセージからurlを抜き出し、セット urls = [] for msg in messages: url_extract = self.extract_url(msg['description'].decode('utf-8')) if url_extract: urls.extend(url_extract) urls_pos = [] urls_google = [] for url in urls: # 対象にしないurlたち if self._filter_url(url): continue # goo.gl短縮URLの場合は本来のURLを抽出 if 'goo.gl' in url: urls_google.append(url) else: urls_pos.append(url) # set型に変換することで重複しているurlを消す urls_pos = set(urls_pos) urls_google = set(urls_google) urls_google_original = [] for url in urls_google: url_original = self._extract_href_short_google(url) if url_original: if self._filter_url(url_original): continue urls_google_original.append(url_original) #print('Google url: {0} 実際のurl: {1}'.format(url, url_original)) # set型に変換することで重複しているurlを消し、urls_posと連結 urls_last = urls_pos.union(set(urls_google_original)) for url in urls_last: self.add_url_blacklist(url) count = Connect(role='slave').open().scard(app.config['URL_BLACKLIST']) print('URL_BLACKLISTの数: {0} 個のurlをsaddした'.format(str(count))) def add_url_blacklist(self, url): ''' url_blacklistにurlを追加する Set型を採用しているので、重複の心配はない @return int ''' url = self._parse_url(url) if not url: return 0 self.r.sadd(app.config['URL_BLACKLIST'], url) def run(self, body): ''' @param str body messageのdescriptionを渡せば良い decode()でstr型に変換してから渡すこと @return dict ''' # メッセージの本文からurlを抜き出す # @return list urls = self.extract_url(body) # bodyにurlが含まれなければ終了 if not urls: return {'url': {'urls': [], 'url_blacklist': []}} # urlが存在すればblacklistのurlかどうかをチェック urls_blacklist = [] for url in urls: if self._filter_url(url): continue res_find = self._find_url(url) if res_find: urls_blacklist.append(res_find) return {'url': {'urls': urls, 'url_blacklist': urls_blacklist}}
def __init__(self): '''''' self.r = Connect().open() self.urls_black = self.r.smembers(app.config['URL_BLACKLIST'])
def __enter__(self): self.con = Connect(role=self.role) self.r = self.con.open() return self