Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
import dgu.newsee.domain.transformednews.service.TransformedNewsService;
import dgu.newsee.domain.user.entity.User;
import dgu.newsee.domain.user.repository.UserRepository;
import dgu.newsee.global.exception.NewsException;
import dgu.newsee.global.exception.UserException;
import dgu.newsee.global.payload.ResponseCode;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -30,7 +33,7 @@ public NewsOrigin crawlAndSave(NewsCrawlRequestDTO request, Long userId) {

// 사용자 조회
User user = userRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다."));
.orElseThrow(() -> new UserException(ResponseCode.USER_UNAUTHORIZED));


// 1. 이미 저장된 뉴스면 바로 반환
Expand Down Expand Up @@ -63,7 +66,7 @@ public NewsOrigin crawlAndSave(NewsCrawlRequestDTO request, Long userId) {
return newsOrigin;

} catch (Exception e) {
throw new RuntimeException("크롤링 실패: " + e.getMessage());
throw new NewsException(ResponseCode.NEWS_CRAWL_FAIL);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,35 +16,41 @@ public static ParsedNews parse(Document doc, String categoryFromCaller, String u
String title = doc.select("meta[property=og:title]").attr("content");

// 본문
// 본문 파싱 (p 태그 우선, 없으면 br 기준으로 직접 파싱)
String content = "";

Elements paragraphs = doc.select("#dic_area > p");
if (!paragraphs.isEmpty()) {
List<String> lines = new ArrayList<>();
for (Element p : paragraphs) {
String text = p.text().trim();
if (!text.isEmpty()) lines.add(text);
}
content = String.join("\n", lines);
} else {
// fallback: br 태그 기준으로 수동 파싱
Element dicArea = doc.selectFirst("#dic_area");
if (dicArea != null) {
StringBuilder builder = new StringBuilder();
for (var node : dicArea.childNodes()) {
if (node.nodeName().equals("br")) {
builder.append("\n");
} else {
builder.append(node.toString().replaceAll("<.*?>", "").trim());
Element dicArea = doc.selectFirst("#dic_area");
if (dicArea != null) {
// HTML 전체를 가져와서 <br> 두 개 이상을 기준으로 문단 나누기
String rawHtml = dicArea.html();

// <br> 태그를 통일된 형태로 바꿔 처리하기 쉽게 함
rawHtml = rawHtml.replaceAll("(?i)<br[^>]*>", "<br>");

// 연속된 <br><br>을 기준으로 문단 나누기
String[] paragraphsRaw = rawHtml.split("(<br>\\s*){2,}");

StringBuilder contentBuilder = new StringBuilder();
for (String paragraphHtml : paragraphsRaw) {
// <br> 단일은 줄바꿈, 나머지는 태그 제거
String paragraphText = paragraphHtml
.replaceAll("(<br>\\s*)+", "\n") // 단일 <br>은 줄바꿈
.replaceAll("<[^>]+>", "") // 나머지 HTML 태그 제거
.trim();

if (!paragraphText.isEmpty()) {
if (contentBuilder.length() > 0) {
contentBuilder.append("\n\n"); // 단락 구분
}
contentBuilder.append(paragraphText);
}
content = builder.toString().replaceAll("\n{2,}", "\n"); // 줄바꿈 2번 이상은 하나로 줄이기
}

content = contentBuilder.toString();
}




// 출처
String source = doc.select("meta[property=og:article:author]").attr("content");
if (source.isBlank()) {
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/dgu/newsee/global/payload/ResponseCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public enum ResponseCode implements BaseErrorCode {
NEWS_SAVE_SUCCESS(ResponseCodeType.SUCCESS, "NEWS201", "뉴스 저장에 성공했습니다.", HttpStatus.OK),
NEWS_DELETE_SUCCESS(ResponseCodeType.SUCCESS, "NEWS202", "뉴스 삭제에 성공했습니다.", HttpStatus.OK),
NEWS_SEARCH_SUCCESS(ResponseCodeType.SUCCESS, "NEWS203", "뉴스 검색 성공", HttpStatus.OK),
NEWS_CRAWL_FAIL(ResponseCodeType.ERROR, "NEWS500", "뉴스 크롤링에 실패했습니다.", HttpStatus.INTERNAL_SERVER_ERROR),

// ✅ 단어장 관련
WORD_SAVE_SUCCESS(ResponseCodeType.SUCCESS, "WORD201", "단어 저장에 성공했습니다.", HttpStatus.OK),
Expand All @@ -46,8 +47,13 @@ public enum ResponseCode implements BaseErrorCode {
INVALID_REQUEST(ResponseCodeType.ERROR, "REQ400", "잘못된 요청입니다.", HttpStatus.BAD_REQUEST),
MISSING_PARAMETER(ResponseCodeType.ERROR, "REQ401", "필수 파라미터가 누락되었습니다.", HttpStatus.BAD_REQUEST),
PARSE_ERROR(ResponseCodeType.ERROR, "REQ402", "데이터 파싱 오류입니다.", HttpStatus.BAD_REQUEST),

// ✅ AI관련
AI_SERVER_DOWN(ResponseCodeType.ERROR, "AI_001", "AI 서버가 응답하지 않습니다.", HttpStatus.SERVICE_UNAVAILABLE);




private final ResponseCodeType type;
private final String code;
private final String message;
Expand Down