pip list에 분명 beautifulsoup4가 있는데도 불구하고 모듈을 못 찾는 에러가 발생하는 경우가 있습니다

 

 

주식 정보를 가지고 오는 FinanceDataReader 라이브러리에 있는 beautifulsoup을 찾지 못하는 에러입니다.

 

에러 메세지를 보니 python39가 sys path로 되어있어 발생하는 문제입니다

 

이전에 파이썬 3.9버전을 사용하다 프로젝트를 위해 3.7버전으로 다운그레이드를 해놓았지만, 사용하는 라이브러리는 여전히 3.9버전과 연동이 돼있어서 생기는 문제입니다

 

그럼 3.9버전에는 해당 모듈이 깔려있지 않다는 걸까요? 확인해보아야 할 것 같습니다

 

python39 폴더 안의 scripts 폴더에서 pip list를 사용해보니

 

 

bs4 모듈이 깔려있지 않은걸 알 수 있습니다

 

이곳에서 pip install beautifulsoup4 을 다운로드 받아주면?

 

 

bs4가 아닌 다른 모듈이 필요하다는 문구를 볼 수 있습니다

 

python39에서 불러오는 것이 아닌 python37을 기준으로 불러왔으면 좋겠는데,

 

일단 이 방법으로 모듈을 못 찾는 문제를 해결한 뒤, 그 방법을 알아내어 추후에 수정해야겠습니다

 

그런데 가상환경에 있음에도 불구하고 39버전의 라이브러리를 찾는건 여전히 문제입니다.

 

근본적인 원인이 해결이 되지 않은 것이죠.

 

가상환경 안에 있는 모듈을 사용해야하는데, 가상환경으로 설치한 모듈을 가져올 수 있게 하는 방법을 알아 내야할 것 같습니다.

 

***

 

uvicorn을 가상환경에 설치하지 않았다보니, uvicorn부터가 가상환경에서 실행이 되지 않았고 uvicorn을 이전에 깔았던 3.9버전에서 불러오는게 큰 문제였던 것 같습니다

 

가상환경에서 다시 uvicorn과 fastapi를 다운로드 받아주니, 가상환경에서 필요한 모듈을 불러오는 것을 볼 수 있었습니다.

 

1. Redis 설치

 

https://github.com/tporadowski/redis/releases

 

Releases · tporadowski/redis

Native port of Redis for Windows. Redis is an in-memory database that persists on disk. The data model is key-value, but many different kind of values are supported: Strings, Lists, Sets, Sorted Se...

github.com

 

이곳에 들어가 Redis를 설치해주었습니다. msi 파일을 설치해주고 port번호를 6379로 설정해줍니다.

 

그 후 Build.gradle에서 의존성을 넣어주면 스프링 부트에서 redis를 사용할 수 있게 됩니다

 

2. redisTemplate으로 redis 사용하기

 

RedisTemplate Bean을 주입받아 바로 사용해보도록 하겠습니다.

 

redisTemplate의 opsForValue 메서드를 이용하여 set과 get을 할 수 있고, 여러개의 key 패턴을 넣을 수도 있습니다.

 

사용자가 저장해둔 DB의 값을 계속적으로 확인하기 위해 RDBMS에 계속 접근하는 것은 굉장히 불편한 일이라고 생각이 들었습니다.

 

실시간으로 데이터가 날라올 때마다 DB에 접근을 한다면 CP 문제가 생길 것입니다.

 

Redis를 이용해 캐시 메모리에 저장을 해두고, DB에 접근할 필요 없이 실시간으로 데이터를 체크해 줄 수 있게 됐습니다.

 

만약 사용자가 저장해둔 DB값에 변화가 발생한다면 Redis를 업데이트 시켜주고, 그 외에는 기존에 있던 Redis를 사용한다면 꽤 그럴듯한 자동 시스템이 만들어질 것 같습니다  

 1. Spring batch를 사용해서 Job과 Step을 만들고, Tasklet을 수행하게 만들기

 

 - Job 만들기

 

JobConfiguration을 만들어 job과 step을 bean으로 만들어주도록 하겠습니다

 

Bean을 관리하는 Configuration이니 @Configuration을 붙여주저야겠죠?

 

JobBuilder를 통해 Job을 만들어주도록 합니다.

 

해당 Job의 이름은 getStockInfo2입니다. 이 이름들은 jobRepository에 의해 관리됩니다. 

 

이 repository를 통해 job의 이름들도 관리되고, 중복 실행도 방지되는 등의 역할을 하게 되는 듯 합니다.

 

start에는 해당 job이 처음으로 실행할 step을 넣어주고 build하여 반환해줍니다.

 

- Step 만들기

 

이번엔 step입니다.

 

Job과 마찬가지로 StepBuilder를 통해 만들고, jobRepository로 관리합니다.

 

해당 step은 tasklet을 수행하게 됩니다.

 

tasklet은 Transactional하게 관리되어야 합니다. 그렇기에 transactionManager도 넣어줍니다.

 

 - Tasklet 만들기

 

Step은 Tasklet을 수행합니다.

 

이 Tasklet을 구현한 구현체에 execute 메서드를 오버라이딩 해주면 이 메서드가 실행되게 됩니다.

 

return 으로 RepeatStatus.FINISHED를 반환하면 해당 Step은 일을 끝마치게 됩니다.

 

RepeatStatus.CONTINUABLE을 사용하게 되면 execute가 계속해서 실행되고, execute안에 if문을 사용하여 특정 지점까지 로직을 수행한 뒤 FINISHED하게 만들 수도 있습니다.

 

이후에 @Component를 하나 만들고 @EnableScheduling 어노테이션을 붙이면 Scheduled를 이용하여 특정 시점에 해당 Job을 실행시키게 만들 수 있습니다.

 

Job은 중복해서 실행이 불가능하므로, LocalDateTime.now를 이용해 Parameter를 계속해서 바꿔주었습니다.

 

Parameter가 달라지면 같은 JobName이더라도 중복해서 실행이 가능하게 됩니다.

 

이렇게 cron으로 1분 단위로 runJob을 실행시키거거나 매일 아침 6시에 해당 Job이 실행되게 만들 수도 있습니다.

 

하지만 이 Tasklet은 Transactional하게 관리가 된다고 하였습니다.

 

즉, 10000개의 데이터를 넣는 작업을 할 경우 해당 tasklet을 진행하던 도중 9999번째에서 에러가 나게 되면 이전의 모든 데이터는 유실되게 됩니다.

 

해당 문제를 해결하기 위해 같은 로직이라 하더라도 chunk로 단위를 쪼개어 실행하는 것이 일반적이라고 합니다.

 

하지만, 저희는 50개의 종목 데이터만 받아올 것이기 때문에 간단하게 batch를 돌려보았습니다.

주식을 자동매매하기 위해서 한국 투자증권 API를 사용하기로 했습니다.

 

해당 API에서 WebSocket을 열어주는 기능을 찾았고, 실시간 호가의 변화를 계속 받을 수 있었습니다.

 

실시간으로 호가가 변할 때마다, DB에 저장해둔 값과 비교를 하고, 

 

해당 가격과 일치하게 된다면 매수/매도 를 진행하게 된다면 자동매매가 이루어 질 수 있을 것이라는 설계를 하고, 

 

자바스크립트가 아닌, 스프링에서 웹소켓을 열어 한국투자증권 서버로부터 데이터를 받아와보겠습니다.

 

1. 클라이언트가 되어 Request를 요청할 준비

 

 

Config 파일을 클래스를 하나 만들어줍니다.

 

해당 객체는 WebSocketConfigurer의 구현체가 되어 빈으로 주입이 될 예정입니다.

 

해당 인터페이스는 registerWebSocketHandlers 하나만 구현해주면 되는 인터페이스네요.

 

해당 메서드를 구현해주면 앱이 실행될 때 해당 웹소켓을 열게 됩니다.

 

WebSocketClient 객체는 WebSocket Request를 보낼 때 필요한 객체입니다.

 

WebSocketConnectionManager에 webSocketClient를 넣고, 응답을 받았을 때 메세지를 처리해줄 Handler를 하나 만들어 넣습니다. 그리고 url을 보내면 WebSocket Request를 할 준비가 완료됐습니다.

 

 

여기서 잠깐, ConnectionManager 객체를 확인해보고 가겠습니다.

 

WebSocketClient는 저희가 이미 넣어줬고, WebSocketHandler는 이제 만들어서 넣어줘야할텐데요.

 

이 핸들러 인터페이스의 구현체는 이미 만들어져있습니다.

 

이렇게 추상 클래스로 구현이 되어있고,

 

그 추상클래스는 이 두 클래스에서 상속을 받고 있네요.

 

저희는 실시간 호가 Text Data가 필요하니 TextWebSocketHandler를 상속받아 WebSocketHandler 구현체를 만들어 줄 수 있습니다.

 

상속받은 클래스의 afterConnectionEstablished를 오버라이딩 해주어 저희가 원하는 작업을 해주도록 합니다.

 

해당 메서드는 윗단에서 자동으로 호출되며 session을 매개변수로 받게됩니다.

 

해당 session을 저희가 만든 handler에 저장시키고, TextWebSocketHandler가 가지고 있는 afterConnectionEstablished도 실행시킨 뒤, 저희가 원하는 로직을 추가로 작성합니다.

 

이렇게 로직을 작성한 뒤 session의 sendMessage 메서드를 실행하게 되면 웹소켓 요청이 실행되게 됩니다.

 

 

또한 handleTextMessage를 통해 response값도 받아올 수 있게 됩니다.

 

웹소켓은 REST API와 다르게 header와 body의 구분이 없다고 합니다 .

 

하지만 한국투자증권은 header와 body를 나눠서 데이터를 입력받고 있기 때문에 JSON타입으로 보내야할텐데,

 

자바로 이걸 어떻게 처리할 수 있을까요?

 

2. JAVA에서 JSON만들기

 

한국투자증권 API 서버에 JSON으로 request를 만들기 위해 json과 비슷한 map을 사용하였습니다.

 

 

header와 input은 Map<String, String>, input을 담은 body는 Map<String, Map<String, String>>로 설정해줍니다.

 

그리고 이 두개를 합쳐야하는데...

 

 상위 클래스인 Object로 모두 받아주도록 하였습니다.

 

이렇게 모아놓은 Map을 json으로 바꿔주어야 합니다.

 

objectMapper을 사용해 String으로 바꿀 수가 있게 됩니다.

 

해당 문자를 TextMessage이 담아 보내면 Websocket Request가 성공하게 됩니다.

+ Recent posts