본문 바로가기

개발 노트/Experience

Cookie에 검색 조건을 설정하는 사이트도 있더라

반응형
SMALL

지난 개발 경험에 대한 소고 차원에서, 당시 재직 중이던 회사에서 있었던 일을 기록해보고자 한다.


22년 11월 즈음, 회사 경영진에서는 Arbitrage 전략을 이용해 실제로 수익을 낼 수 있는지 검증하기 위한 시뮬레이션 환경을 구축하고 있었다. 지금도 다소 생소한 Arbitrage 전략은, 그 당시 이해한 바로는 서로 다른 세 개의 통화 간 가치 변동을 추적하여 한 통화의 가치가 하락하면 다른 통화의 가치는 상승할 것이라는 전제로 하여 특정 시장을 매개로 삼아 차익을 얻는 방식이었다.

 

해당 전략의 구현은 당시 사수님이 전담하고 계셨다. Arbitrage 전략을 기반으로 한 개발이 진행 중이라는 사실은, 어느 정도 구현이 진행된 이후에야 공유받을 수 있었다.

 

시뮬레이션에 필요한 데이터는 외화를 기준으로 수집하기로 했고 관련 데이터는 “https://www.myfxbook.com/forex-broker-swaps” 사이트를 참고하고 있었다.

 

그 시절에는 사수님과 개발과 관련된 이런저런 이야기를 주고받는 것이 일상이었다. Arbitrage 관련 내용도 마찬가지다.

Arbitrage 전략을 시뮬레이션을 위한 데이터 수집을 진행하고 계시던 사수님께서

데이터 수집에 Selenium을 쓰다 보니 느리긴 한데, 다른 업무도 있어서 일단 이대로 사용하고 나중에 개선하려 한다

 

라고 말씀하셨다.

 

그 말을 듣고 이미 구현된 코드 일부를 살펴보게 되었는데, 대략 다음과 같았다.

  def exec(self) -> dict:
      driver = self.chrome_driver.open(
          url=self.URL,
          cookies={
              'brokerSwapSymbols': json.dumps(','.join(self.symbol_codes)),
              'blockWebNotificationsModalShortTerm': '7'
          })

      # 광고 popup 닫기
      self.popup_ad_container_close(driver)

      bs = BeautifulSoup(driver.page_source, 'lxml')
      forexBrokerSwapTable = bs.find('table', id='forexBrokerSwapTable')

      parser = ForexBrokerSwapTableParser(forexBrokerSwapTable)
      return parser.get_broker()

위 코드의 구현 배경은 myfxbook.com의 UX 흐름과 유사하다. 사이트에 접속하면 가장 먼저 노출되는 광고를 닫고, 이후 옵션 박스에 있는 외국 통화쌍(이하 통화쌍)을 선택해야 한다.

브라우저에서 통화쌍은 한 번에 최대 5개까지만 선택할 수 있었기 때문에, 5개에 대한 데이터 수집이 끝나면 브라우저를 닫고 다시 다음 5개를 선택해야 하는 구조였다.

 

브라우저의 동작 방식도 그렇고, Selenium을 이용한 데이터 수집 방식도 그렇고, 설명만 들어도 전체적인 작업 시간이 오래 걸릴 수밖에 없겠다는 생각이 자연스럽게 들었다.

 

정리해보면 다음과 같은 문제점들이 보였다.

  1. Selenium 사용으로 인해 전체적인 프로세스가 느리다.
  2. 통화쌍을 5개씩 나누어 수집해야 하므로, 총 71개의 통화쌍 데이터를 수집하는 데 시간이 지나치게 걸린다.
  3. Selenium으로 브라우저 동작을 제어하기 때문에, 사이트 구조가 변경되면 코드 역시 함께 수정해야 할 가능성이 크다.

크롤링을 하면서 네트워크 패킷을 확인하고 프록시를 열어 데이터를 관찰해본 경험 덕분인지, 어딘가에 이 과정을 우회할 수 있는 방법이 있을 것 같다는 생각이 들었다. 그래서 사수님께 말씀드리고, 사이트의 동작 방식을 하나하나 뜯어보기 시작했다.

조사를 진행해보았지만, 처음에는 딱히 특이한 점을 발견하지 못했다. 그러던 중 사수님께서 작성해두신 코드 일부에서 묘하게 이상한 부분이 눈에 들어왔다.

 

바로 쿠키에 외화 통화 정보를 저장하는 로직이었다.

  cookies={
      'brokerSwapSymbols': json.dumps(','.join(self.symbol_codes)),
      'blockWebNotificationsModalShortTerm': '7'
  })

당시에 이 부분이 이상하게 느껴졌던 이유는 단순했다.

“서버 입장에서 필터링 처리를 원래 이런 방식으로 구현하나?”

이후 브라우저 상에서 외화 정보가 변경될 때 어떤 방식으로 요청이 전달되는지 유심히 관찰했고, 다음과 같은 사실을 확인할 수 있었다.

  1. 외화 통화 정보는 각각 숫자 인덱스로 관리되고 있었다.
  2. 옵션 박스에서 통화쌍을 변경할 때, 쿠키에 저장된 값도 함께 변경되고 있었다.

이 두 가지 사실을 근거로 다음과 같은 가설을 세울 수 있었다.

“브라우저에서 통화쌍 선택 제한은 화면 단에서만 막혀 있고, 실제로는 HTTP Request를 직접 보내면 해결되는 게 아닐까?”

 

해당 가설을 토대로 실험을 진행했고, 결과를 공유하자 사수님께서는 Selenium을 사용하며 겪던 여러 문제를 한 번에 해결할 수 있었다고 하셨다. 아래는 그 과정에서 수정된 코드다.

cookies = {
      'brokerSwapSymbols': json.dumps(','.join(self.symbol_codes)),
      'blockWebNotificationsModalShortTerm': '7',
  }
  res = requests.get(self.URL, cookies=cookies)

  bs = BeautifulSoup(res.content, 'lxml')
  forexBrokerSwapTable = bs.find('table', id='forexBrokerSwapTable')

  parser = ForexBrokerSwapTableParser(forexBrokerSwapTable)
  return parser.get_broker()

 

마치며

돌아보면, 접근 방식만 달랐어도 더 수월하게 풀 수 있었던 또 하나의 크롤링 사례였다. 또한 Selenium을 사용하던 기존 방식으로는 20 ~25초가 걸리던 시간을 5초 수준까지 단축할 수 있었다.

 

이 경험을 성과의 관점에서만 보면 가장 먼저 떠오르는 것은 단연 성능 개선이다. 하지만 시간이 지나 다시 떠올려보면, 그보다 더 오래 기억에 남는 것은 함께 일하던 방식과 분위기였다. 당시 사수님과는 개발 이야기뿐 아니라 업무에 대한 관점과 조직문화에 대해서도 많은 대화를 나눴고, 자연스럽게 일에서 어려움이 생기면 도움을 요청하고 또 서로 도와주기도 했다. 지금 돌아보면, 여러 회사를 거치면서 이런 문화를 가진 조직을 흔히 만나기 어렵다는 점이 더욱 아쉽게 느껴진다.

 

흔히들 회사는 학교가 아니라고 말한다. 그 말에는 동의한다. 하지만 같은 목표를 가지고 함께 일하는 사람들 사이에서만큼은, 지성인으로서의 의견 교환과 문제 제기에 배척이나 강압이 있어서는 안 된다고 생각한다. 개발에 대한 몰입과 책임감, 그리고 일에 대한 애착은 그런 환경 속에서 자연스럽게 만들어지는 것이 아닐까. 이 경험은 단순히 한 번의 성능 개선을 넘어, 내가 어떤 환경에서 얼마나 더 잘 일할 수 있는 사람인지를 다시 생각해보게 만든 계기였다.

반응형
LIST