def __init__(self,
              topic='covid_kor',
              consumer_group='my-group',
              host="localhost",
              port="9092"):
     """
     Kafka Consumer Option (not all)
      - bootstrap_servers(default=9092): 브로커(host:port)를 리스트로 나열, 노드 전부를 쓸 필요는 없음
      - auto_offset_reset(default='latest'): 0, 서버로부터 어떠한 ack도 기다리지 않음. 처리량 증가하지만 유실율도 증가
                         1, 리더의 기록을 확인 후 ack 받음. 모든 팔로워에 대해서 확인하지는 않음
                         'all', 모든 ISR(리더의 모든 팔로워)의 기록을 확인 후 ack 받음. 무손실 보장
      - enable_auto_commit(default=True): 데이터를 압축하여 보낼 포멧 (‘gzip’, ‘snappy’, ‘lz4’, or None)
      - value_deserializer(default=None): 압축된 msg를 받았을 때 이를 역직렬화할 함수(callable).
                                          (producer 의 serializer와 동일한 값으로 하지않으면 예외 발생)
     """
     try:
         self.consumer = KafkaConsumer(
             f"{topic}",
             bootstrap_servers=[f"{host}:{port}"],
             auto_offset_reset="latest",
             enable_auto_commit=True,
             group_id=consumer_group,
             value_deserializer=lambda x: loads(x.decode("utf-8")),
             consumer_timeout_ms=1000 * 60 * 5,
         )
     except KafkaError as e:
         Log.e(f"KafkaConsuemr fail. {e}")
     Log.i("KafkaConsuemr connect.")
    def on_send_error(self, exception):
        """
        send 함수의 callback 으로 데이터가 broker 에게 전달되지 못할 시 호출됨

        :param exception: 전달에 실패한 오류(kafka exception)에 대한 설명
        :return: -
        """
        Log.e(f"{exception}")
Esempio n. 3
0
 def make_days_random_data(self, origin_data, days=7, num_of_tweets=20000):
     if 'created_at' in origin_data['data']:
         if self.daily_cnt >= num_of_tweets:
             exit()
         else:
             start = t.time()
             while (num_of_tweets) > 0:
                 data = json.dumps(origin_data, ensure_ascii=False)
                 self.producer.send_data('test', data)
                 num_of_tweets -= 1
             print("elapsed :", t.time() - start)
     else:
         Log.e("KEYERROR no 'created_at' in origin_data['data'].")
Esempio n. 4
0
    def make_diff_time_data(self,
                            origin_data: dict,
                            diff_day=5,
                            diff_time='000000'):
        if 'created_at' in origin_data['data']:
            new_data = copy.deepcopy(origin_data)
            datetime_origin = self.toggle_time_format(
                origin_data['data']['created_at'])
            diff = timedelta(days=diff_day,
                             hours=int(diff_time[:2]),
                             minutes=int(diff_time[2:4]),
                             seconds=int(diff_time[4:6]))

            new_created_at = self.toggle_time_format(datetime_origin + diff)
            new_data['data']['created_at'] = new_created_at
        else:
            Log.e("KEYERROR no 'created_at' in origin_data['data'].")
Esempio n. 5
0
    def make_diff_time_data(self, origin_data: dict, diff_day=5, diff_time='000000'):
        """
        원본 tweet 을 기반으로 시간의 차이를 통해 새로운 시간의 tweet 데이터를 생성

        :param origin_data: [Tweet Data] 원본 tweet 데이터
        :param diff_day: [int] 생성하고자 하는 데이터와 원본 데이터의 날짜 차이
        :param diff_time: [str] 생성하고자 하는 데이터와 원본 데이터의 시간 차이('HHMMSS')

        :return: -
        """
        if 'created_at' in origin_data['data']:
            new_data = copy.deepcopy(origin_data)
            datetime_origin = self.toggle_time_format(origin_data['data']['created_at'])
            diff = timedelta(days=diff_day, hours=int(diff_time[:2]), minutes=int(diff_time[2:4]), seconds=int(diff_time[4:6]))

            new_created_at = self.toggle_time_format(datetime_origin + diff)
            new_data['data']['created_at'] = new_created_at
        else:
            Log.e("KEYERROR no 'created_at' in origin_data['data'].")
Esempio n. 6
0
    def get_stream_rules(self):
        rule_ids = []
        try:
            response = self.api.request("tweets/search/stream/rules",
                                        method_override="GET")
            Log.i(f"[{response.status_code}] RULES: {response.text}")
            if response.status_code != 200:
                raise Exception(response.text)
            else:
                for item in response:
                    if "id" in item:
                        rule_ids.append(item["id"])
                    else:
                        Log.i(json.dumps(item, ensure_ascii=False))
                return rule_ids

        except TwitterRequestError as e:
            msg_list = ["RequestError:", e]
            for msg in iter(e):
                msg_list.append(msg)
            err_msg = " ".join(msg_list)
            Log.e(err_msg)

        except TwitterConnectionError as e:
            Log.e(f"ConnectionError: {e}")
            self.prompt_reconnect_msg(2)

        except Exception as e:
            Log.e(f"BaseException: {e}")
            self.prompt_reconnect_msg(2)
Esempio n. 7
0
    def __init__(self):
        try:
            auth_info = TwitterOAuth.read_file()
            self.api = TwitterAPI(
                auth_info.consumer_key,
                auth_info.consumer_secret,
                auth_type="oAuth2",
                api_version="2",
            )
        except TwitterRequestError as e:
            msg_list = ["RequestError:", e]
            for msg in iter(e):
                msg_list.append(msg)
            err_msg = " ".join(msg_list)
            Log.e(err_msg)

        except TwitterConnectionError as e:
            Log.e(f"ConnectionError: {e}")
            self.prompt_reconnect_msg(2)

        except Exception as e:
            Log.e(f"BaseException: {e}")
            self.prompt_reconnect_msg(2)

        # comma-seperated list with no space between fields
        self.expansions = "attachments.media_keys,author_id,entities.mentions.username,in_reply_to_user_id,referenced_tweets.id,referenced_tweets.id.author_id,geo.place_id"
        self.media_fields = "preview_image_url,type,url,public_metrics"
        self.tweet_fields = "id,text,attachments,author_id,conversation_id,created_at,entities,in_reply_to_user_id,lang,public_metrics,possibly_sensitive,referenced_tweets,source"
        self.user_fields = "entities,id,name,username,profile_image_url,verified"
        self.place_fields = "full_name,id,country,country_code,name,place_type"

        self.output_file_name = "../logs/stream_result.txt"
Esempio n. 8
0
    def delete_stream_rules(self, rule_ids):
        try:
            if len(rule_ids) > 0:
                response = self.api.request("tweets/search/stream/rules",
                                            {"delete": {
                                                "ids": rule_ids
                                            }})
                Log.i(
                    f"[{response.status_code}] RULES DELETED: {json.dumps(response.json())}"
                )
                if response.status_code != 200:
                    raise Exception(response.text)

        except TwitterRequestError as e:
            msg_list = ["RequestError:", e]
            for msg in iter(e):
                msg_list.append(msg)
            err_msg = " ".join(msg_list)
            Log.e(err_msg)

        except TwitterConnectionError as e:
            Log.e(f"ConnectionError: {e}")
            self.prompt_reconnect_msg(2)

        except Exception as e:
            Log.e(f"BaseException: {e}")
            self.prompt_reconnect_msg(2)
Esempio n. 9
0
    def add_stream_rules(self, request_query):
        """
        계정에 특정 rule 을 등록

        :param request_query: [str] 문법에 맞는 쿼리문
        :return: -
        """
        try:
            response = self.api.request("tweets/search/stream/rules",
                                        {"add": [{
                                            "value": request_query
                                        }]})
            Log.i(f"[{response.status_code}] RULE ADDED: {response.text}")
            if response.status_code != 201:
                raise Exception(response.text)

        except TwitterRequestError as e:
            msg_list = []
            for msg in iter(e):
                msg_list.append(msg)
            err_msg = "RequestError: " + "|".join(msg_list)
            Log.e(err_msg)

        except TwitterConnectionError as e:
            Log.e(f"ConnectionError: {e}")
            self.prompt_reconnect_msg(2)

        except Exception as e:
            Log.e(f"BaseException: {e}")
            self.prompt_reconnect_msg(2)
Esempio n. 10
0
    def make_days_random_data(self, origin_data, days=6, num_of_tweets=20000):
        """
        원본 tweet 을 기반으로 원하는 날짜, 랜덤한 시간의 새로운 tweet 데이터를 생성하여 broker 에게 전송
        생성하고자 하는 수만큼의 데이터가 생성되었다면 프로그램 종료

        :param origin_data: [Tweet Data] 원본 tweet 데이터
        :param days: [int] 생성하고자 하는 데이터와 원본 데이터의 날짜 차이. 해당하는 일 수만큼의 전날에서 하루 전날까지 포함
        :param num_of_tweets: [int] 범위 내의 모든 날짜마다 생성하고자 하는 tweet 데이터의 수

        :return: -
        """
        if 'created_at' in origin_data['data']:
            if self.daily_cnt >= num_of_tweets:
                exit()
            else:
                datetime_origin = self.toggle_time_format(origin_data['data']['created_at'])
                if datetime_origin.month == 2 and datetime_origin.day > 4:
                    # 특정 날짜를 제거하기 위해 filtering
                    with open("logs/random_data_list", "a", encoding="utf-8") as output_file:
                        # 생성한 데이터 기록
                        for day_ago in range(-days, 0):
                            # 중첩 dictionary의 복제를 위해 deepcopy 사요
                            new_data = copy.deepcopy(origin_data)
                            datetime_post = datetime_origin + timedelta(days=day_ago)
                            random_hms = time(hour=random.randint(0, 23), minute=random.randint(0, 59), second=random.randint(0, 59))
                            # 특정 날짜와 랜덤한 시간을 합친 datetime 생성
                            datetime_post = datetime.combine(datetime_post.date(), random_hms)

                            new_created_at = self.toggle_time_format(datetime_post)
                            # print("new_created_at", new_created_at)
                            new_data['data']['created_at'] = new_created_at
                            new_data = json.dumps(new_data, ensure_ascii=False)

                            self.producer.send_data('test', new_data)
                            print(new_data, file=output_file, flush=True)
                    self.daily_cnt += 1
                    print(self.daily_cnt)
        else:
            Log.e("KEYERROR no 'created_at' in origin_data['data'].")
 def __init__(self, host="localhost", port="9092"):
     """
     Kafka Producer Option (not all)
      - bootstrap_servers(default=9092): 브로커(host:port)를 리스트로 나열, 노드 전부를 쓸 필요는 없음
      - acks(default=1): 0, 서버로부터 어떠한 ack도 기다리지 않음. 처리량 증가하지만 유실율도 증가
                         1, 리더의 기록을 확인 후 ack 받음. 모든 팔로워에 대해서 확인하지는 않음
                         'all', 모든 ISR(리더의 모든 팔로워)의 기록을 확인 후 ack 받음. 무손실 보장
      - compression_type(default=None): 데이터를 압축하여 보낼 포멧 (‘gzip’, ‘snappy’, ‘lz4’, or None)
      - value_serializer(default=None): 유저가 보내려는 msg를 byte의 형태로 변환할 함수(callable). 여기서는 변환 후 인코딩
     """
     try:
         self.producer = KafkaProducer(
             api_version=(2, 7, 0),
             bootstrap_servers=[f"{host}:{port}"],
             acks=-1,
             compression_type="gzip",
             value_serializer=lambda x: x.encode("utf-8"),
             batch_size=1024*64,
             linger_ms=10,
         )
     except KafkaError as e:
         Log.e(f"KafkaProducer fail. {e}")
     Log.i("KafkaProducer connect.")
Esempio n. 12
0
    def __init__(self):
        """
        twitter API 를 사용하기 위해 API KEY를 이용해 연결하고, 요청에 필요한 파라미터를 초기화
        실시간 데이터를 응답받기 위해선 twitter 에서 정해놓은 query 형식에 따라 rule 을 등록한 후 요청해야 함
        모든 rule 은 계정(API KEY)에 연동되고 각가 id로 구분되며, 추가적인 등록/삭제가 없다면 변경되지 않음
        여기서 rule 이란 특정한 조건을 만족하는 streaming data 를 받기위한 filtering 방법
        """
        try:
            auth_info = TwitterOAuth.read_file()
            self.api = TwitterAPI(
                auth_info.consumer_key,
                auth_info.consumer_secret,
                auth_type="oAuth2",
                api_version="2",
            )
        except TwitterRequestError as e:
            msg_list = []
            for msg in iter(e):
                msg_list.append(msg)
            err_msg = "RequestError: " + "|".join(msg_list)
            Log.e(err_msg)

        except TwitterConnectionError as e:
            Log.e(f"ConnectionError: {e}")
            self.prompt_reconnect_msg(2)

        except Exception as e:
            Log.e(f"BaseException: {e}")
            self.prompt_reconnect_msg(2)

        # twitter conifg 파일
        twitter_config = config.Config('configs/twitter_config.conf')

        self.query_string = twitter_config.load_config('API_QUERY', 'rules')
        self.output_file_name = twitter_config.load_config(
            'OUTPUT_FILES', 'stream_result')

        # comma-seperated list with no space between fields
        self.expansions = twitter_config.load_config('CONTENTS', 'expansions')
        self.media_fields = twitter_config.load_config('CONTENTS',
                                                       'media_fields')
        self.tweet_fields = twitter_config.load_config('CONTENTS',
                                                       'tweet_fields')
        self.user_fields = twitter_config.load_config('CONTENTS',
                                                      'user_fields')
        self.place_fields = twitter_config.load_config('CONTENTS',
                                                       'place_fields')
Esempio n. 13
0
 def send_analytic(self, tweetWrapper):
     try:
         StatusAnalyticsSender.post_analytic(tweetWrapper)
     except Exception as e:
             Log.e("EXCEPTION", str(e))
Esempio n. 14
0
    def start_stream(self, producer, topic):
        total_cnt = [0]  # 스케줄러 함수의 인자로, 값이 변경될 수 있도록 mutable 객체인 list 로 선언
        while True:
            try:
                response = self.api.request(
                    "tweets/search/stream",
                    {
                        "expansions": self.expansions,
                        "media.fields": self.media_fields,
                        "tweet.fields": self.tweet_fields,
                        "user.fields": self.user_fields,
                        "place.fields": self.place_fields,
                    },
                )
                Log.i(f"[{response.status_code}] START...")
                Log.i(response.get_quota())  # API connect 회수 조회
                if response.status_code != 200 and response.status_code != 429:
                    raise Exception(response)

                elif response.status_code != 200:
                    # 그 외의 경우 예외처리 및 재연결 시도
                    raise Exception(response)

                with open(self.output_file_name, "a",
                          encoding="utf-8") as output_file, open(
                              "../logs/data_count.txt", "a") as cnt_file:
                    print(f"[{datetime.datetime.now()}] file re-open",
                          file=cnt_file)
                    scheduler = BackgroundScheduler(
                    )  # 5초마다 실행을 위해 백그라운드 스케줄러 사용
                    batch_cnt = [
                        0
                    ]  # 5초 간격으로 함수 내에서 reset 하기 위해 mutable 객체인 list 로 선언
                    scheduler.add_job(
                        self.print_periodically,
                        "cron",
                        second="*/5",
                        args=[batch_cnt, total_cnt, cnt_file],
                    )
                    scheduler.start()

                    for item in response:
                        self.check_error_response(item)
                        data = json.dumps(item, ensure_ascii=False)
                        print(data, file=output_file, flush=True)
                        producer.send_data(topic=topic, data=data)

                        batch_cnt[0] += 1
                        total_cnt[0] += 1

            except TwitterRequestError as e:
                msg_list = ["RequestError:", e]
                for msg in iter(e):
                    msg_list.append(msg)
                err_msg = " ".join(msg_list)
                Log.e(err_msg)
                if e.status_code >= 500:
                    self.prompt_reconnect_msg(2)
                elif e.status_code == 429:
                    self.prompt_reconnect_msg(60)
                else:
                    exit()

            except TwitterConnectionError as e:
                Log.e(f"ConnectionError: {e}")
                self.prompt_reconnect_msg(2)

            except Exception as e:
                Log.e(f"Exception: {e}")
                self.prompt_reconnect_msg(2)
Esempio n. 15
0
    def start_stream(self, producer, topic):
        """
        계정에 등록된 rule 에 따라 filtered streaming data 를 받아옴
        data를 정상적으로 받아오고 있다면 오류/종료 전까지 반복문에서 빠져나오지 않음
        에러 원인에 따라 재연결을 시도하여 서버가 유지될 수 있도록 함
        rule 과 별개로 하나의 tweet 에서 받아오고자 하는 정보를 쿼리에 포함시킬 수 있음

        kafka 의 prodcer 역할을 하는 부분으로 받아오는 data 를 producer 와 연결된 broker 로 전달함

        :param producer: [kafka.producer] kafka 의 produce 객체
        :param topic: [str] 데이터를 전달하고자하는 broker 의 특정 topic

        :return: -
        """
        total_cnt = 0
        while True:
            try:
                response = self.api.request(
                    "tweets/search/stream",
                    {
                        "expansions": self.expansions,
                        "media.fields": self.media_fields,
                        "tweet.fields": self.tweet_fields,
                        "user.fields": self.user_fields,
                        "place.fields": self.place_fields,
                    },
                )
                Log.i(f"[{response.status_code}] START...")
                Log.i(response.get_quota())  # API connect 회수 조회

                if (response.status_code != 200 and response.status_code != 400
                        and response.status_code != 429):
                    # 에러 원인별 다른 처리를 위해 응답 코드로 구분
                    raise Exception(response)

                with open(self.output_file_name, "a",
                          encoding="utf-8") as output_file, open(
                              "logs/data_count.txt", "a") as cnt_file:
                    # data_count.txt : 유실을 확인하기위해 count
                    # [데이터를 받아온 시간] 해당 tweet 의 게시 시간 (파일이 open 된 후 받아온 데이터의 수 / 프로그램이 실행된 후 받아온 데이터의 수)
                    print(f"[{datetime.datetime.now()}] file re-open",
                          file=cnt_file)
                    for no, item in enumerate(response):
                        self.check_error_response(item)
                        data = json.dumps(item, ensure_ascii=False)
                        print(data, file=output_file, flush=True)
                        producer.send_data(topic=topic, data=data)
                        print(
                            f"[{datetime.datetime.now()}] {item['data']['created_at']} ({no} / {total_cnt})",
                            file=cnt_file,
                            flush=True,
                        )
                        total_cnt += 1

            except TwitterRequestError as e:
                # ConnectionException will be caught here

                msg_list = []
                for msg in iter(e):
                    msg_list.append(msg)
                err_msg = "RequestError: " + "|".join(msg_list)
                Log.e(err_msg)

                if e.status_code >= 500:
                    self.prompt_reconnect_msg(3)
                elif e.status_code == 429:
                    self.prompt_reconnect_msg(63)
                else:
                    exit()

            except TwitterConnectionError as e:
                Log.e(f"ConnectionError: {e}")
                self.prompt_reconnect_msg(3)

            except Exception as e:
                Log.e(f"Exception: {e}")
                self.prompt_reconnect_msg(3)
Esempio n. 16
0
 def send_analytic(self, tweetWrapper):
     try:
         StatusAnalyticsSender.post_analytic(tweetWrapper)
     except Exception as e:
         Log.e("EXCEPTION", str(e))