-
[뉴스파이프라인 #1] Spring Boot + Spring Batch + JSOUP을 활용한 뉴스 크롤링프로젝트 노트 2023. 11. 6. 16:15
📌 만드는 이유
우선 저는 경제관련 뉴스 보는 것을 굉장히 즐깁니다.
그간 뉴스를 보려면 사이트에 접속을 하고 제가 직접 찾아봐야 함이 너무 귀찮더군요..
누군가 제게 재깍재깍 알맞은 시간에 알려줬으면 좋겠다는 생각을 했습니다.
추가적으로 회사의 업무용 메신저로 텔레그램을 활용합니다.
오케이, 그럼 뉴스를 긁어와서 제 텔레그램으로 전송해주는 어플리케이션을 만들면 되겠군요?
해봅시다.
📌 아키텍쳐랄 것도 없지만..
원대한 꿈을 꾸고 있습니다. 하지만 지금 스텝에서는 거기까지 얘기를 하진 않으려 해요.
그래도 아키텍쳐라는 것을 한 번이라도 그려보고 가는게 낫지 않을까요?
예상하셨겠지만, 크롤러에서 긁어와서 News라는 Record를 만들고 Telegram으로 전송하면 끝입니다.
📌 프로젝트 생성
'NewsPipeline'이라는 이름으로 프로젝트를 만들어줍니다.
Spring Web과 Spring Batch, Lombok정도 추가해주면 될 것 같습니다.
프로젝트명에서 힌트를 얻으셨을 수도 있겠지만 DB는 추후에 사용할 것이고 뿐만 아니라 데이터 파이프라인을 구축하여 검색까지 해볼것이라 꿈꾸고 있습니다. 가능하다면..요.
Group은 건드리지 않겠습니다. 귀찮으니까요😁
빌드 툴은..Maven이 선택되어있군요. Java는..17이 깔려있으니 17로 쓰겠습니다. 귀찮으니까요😆
부트 3.1.5와 앞서 말씀드린 라이브러리들을 추가하고 프로젝트 구성을 완료합니다.
📌 프로젝트 구성하기
가장 먼저 진입점인 NewsPipelineApplication을 열어봅시다.
@SpringBootApplication @EnableScheduling // Scheduling을 위한 어노테이션 // @EnableBatchProcessing --> Spring Boot 3.x 이하에선 어노테이션을 추가해주세요 public class NewsPipelineApplication { public static void main(String[] args) { SpringApplication.run(NewsPipelineApplication.class, args); } }
Spring Boot 3.x 이하버전에서는 Spring Batch를 사용하기 위해서 @EnableBatchProcessing을 추가해야 합니다.
하지만 저희는 Spring Boot 3.1.5버전이기에 @EnableBatchProcessing을 추가할 필요가 없습니다.
자동 구성으로 쉽게 사용할 수 있도록 변경되었다고 합니다. 자세한 이유는 여기(클릭)를 참고해주세요.
사용할 준비는 끝났습니다. 그럼 이제 가볍게 News정보를 담을 수 있는 News Record를 만들어주죠.
domain 패키지를 하나 만들어준 후 그 안에 만들어주면 더욱 구분이 쉽겠지요.
package com.example.newspipeline.domain; public record News(String type, String url, String title, Long newsId) { }
Record가 무엇인지 모르시는 분들 께서는 아래 포스팅의 'Record Class'(클릭) 부분을 참고해주시면 됩니다.
크롤링을 하기 위해서는 JSOUP이라는 라이브러리가 필요합니다.
pom.xml에 다음 라이브러리를 추가하시어 구성하시면 됩니다.
버전은 달라졌을 수 있으니, 상황에 맞게 최신으로 사용하시면 됩니다~
<dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.16.2</version> </dependency>
그리고 나서 서버를 띄워보면 에러가 날겁니다.
Spring Batch를 사용하면서 db관련 설정이 필요하며, h2 database를 우선 선언해주었습니다.
<dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>2.2.224</version> </dependency>
그리고 application.yml에 batch와 datasource에 대한 설정을 해줍니다.
spring: batch: job: enabled: false # 서버 재시작시 배치 자동실행 방지 datasource: # batch를 사용하기 위해 h2 database 사용 driver-class-name: org.h2.Driver url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;
이제 준비는 정말 끝났습니다.
📌 텔레그램 채널 준비하기
텔레그램에서 BotFather를 검색하면 위와 같이 검색이 됩니다.
그럼 그 BotFather에게 생성하고자 하는 봇의 이름을 입력하고 HTTP API Token을 받으면 준비는 끝납니다.
이제 채널에 해당 봇을 등록해야 하는데, 자세한 내용은 아래 블로그에서 잘 정리해주셨으니 참고 부탁드립니다.
📌 기능 구현
우선 전부 다 구현한 프로젝트 구조는 다음과 같습니다.
하나하나 확인해보도록 하겠습니다.
먼저 JobConfiguration 클래스입니다.
기존에는 JobBuilderFactory를 사용하였으나, Spring Batch 5.x로 넘어오면서 BuilderFactory는 Duplicated 되었습니다.
자세한 내용은 Spring Batch Github(클릭)에 가면 확인할 수 있습니다.
그래서 JobBuilder를 직접 사용해야 하며, JobRepository로 구현을 하면 됩니다.
@Configuration @RequiredArgsConstructor public class JobConfiguration { private final JobRepository jobRepository; private final PlatformTransactionManager platformTransactionManager; private final JobLauncher jobLauncher; private final JobRegistry jobRegistry; @Bean public Job sendNewsJob() { return new JobBuilder("sendNewsJob", jobRepository) .start(crawlingStep(jobRepository, platformTransactionManager)) .build(); } private Step crawlingStep(JobRepository jobRepository, PlatformTransactionManager platformTransactionManager) { return new StepBuilder("sendNewsStep", jobRepository) .tasklet(new CrawlingTasklet(new TelegramSendService()), platformTransactionManager) .build(); } @Bean public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor() { JobRegistryBeanPostProcessor jobProcessor = new JobRegistryBeanPostProcessor(); jobProcessor.setJobRegistry(jobRegistry); return jobProcessor; } @Scheduled(fixedDelay = 60 * 3000L) // 3분마다 public void runJob() { try { jobLauncher.run(jobRegistry.getJob("sendNewsJob"), new JobParametersBuilder().addString("datetime", LocalDateTime.now().toString()).toJobParameters()); } catch (NoSuchJobException e) { throw new RuntimeException(e); } catch (JobInstanceAlreadyCompleteException | JobExecutionAlreadyRunningException | JobParametersInvalidException | JobRestartException e) { throw new RuntimeException(e); } } }
그 외에는 일반적인 방식과 동일합니다
가장 먼저 Job을 생성한 후에 StepBuilder를 통해 Tasklet을 선언해줍니다.
Job에 대한 내용은 위 두 메소드이고, 아래 두 메소드는 Job의 실행에 대한 Configuration입니다.
@Scheduled 어노테이션으로 3분마다 구동되는 Job Scheduler를 쉽게 만들 수 있지요.
위 Step에서 구현하고 있는 CrawlingTasklet의 코드는 아래와 같습니다. 실제 비즈니스가 tasklet에 선언되어 구현됩니다.
@Slf4j @RequiredArgsConstructor public class CrawlingTasklet implements Tasklet { private final TelegramSendService telegramSendService; @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { log.debug("executed tasklet"); telegramSendService.send(); // Send 비즈니스 수행 return RepeatStatus.FINISHED; } }
Tasklet interface를 상속받아 execute를 구현합니다.
Telegram으로 메시지를 보내기 위한 TelegramSendService도 보이는군요.
해당 서비스는 실제로 크롤러에게 데이터를 긁어오도록 시키고, 그 긁어온 데이터를 파싱하는 로직이 추가되어 있습니다.
크롤링은 Jsoup 라이브러리를 활용하면 굉장히 쉽게 구현이 가능합니다.
Document doc = Jsoup.connect(URL).get(); Elements elements = doc.select("div.cont");
Document 형식으로 전송되기 때문에 Elements를 파싱해서 원하는 데이터를 사용하면 됩니다.
마치며
이번에는 Spring Boot와 Spring Batch, 그리고 JSOUP 라이브러리를 활용해 웹페이지 크롤링 방식을 스터디 해보았습니다.
소스 코드는 아래 Github에서 확인하시면 됩니다.
최종적인 목표는 Kafka Streams를 통한 데이터 가공과 OpenSearch를 통한 검색 최적화까지 가보려 하는데, 노력이 매우 많이 필요할 것 같네요..
'프로젝트 노트' 카테고리의 다른 글