-
Notifications
You must be signed in to change notification settings - Fork 1
[REFAC#100] 파싱 규칙 DB 일원화 — 단일 page parser engine (모든 웹페이지 도메인 중립) #145
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
7f9cd31
[FEAT]: parsing_rules 테이블 추가 — 사이트별 파싱 규칙 DB 일원화 (이슈 #100)
juhy0987 9425c80
[FEAT]: ParsingRuleRepository — Rule struct + Postgres 구현 (이슈 #100)
juhy0987 f843a45
[FEAT]: rule.Resolver — URL/host → ParsingRule lookup with TTL cache …
juhy0987 87ce20c
[FEAT]: rule.Parser — NewsArticleParser + NewsListParser 단일 engine (이…
juhy0987 1324d21
[FEAT]: rule.Resolver / rule.Parser 단위 테스트 (이슈 #100)
juhy0987 2f1c81b
[REFAC]: 도메인 일반화 — news → 모든 웹페이지 (parser 모듈, Page/LinkItem 모델)
juhy0987 d922f84
[FIX]: 피드백 반영, parser/resolver 견고성 보강
juhy0987 309c4cb
[CHORE]: 피드백 반영, debug log / scheduler lock 추적 제외 + .gitignore 보강
juhy0987 24748ad
[REFAC]: 피드백 반영, ContentParser/LinkListParser 인터페이스에 ctx 추가 (Go 컨벤션)
juhy0987 237fed0
[FIX]: 피드백 반영, Resolver MaxEntries OOM 방어 + 미사용 log 필드 정리
juhy0987 544e18c
[FIX]: 피드백 반영, .vscode 추적 제외 + selector CSS 검증 강화 + 자연키 정렬
juhy0987 61ba04d
[FIX]: 피드백 반영, ParseLinks 진단 분기 + validateRaw whitespace 처리
juhy0987 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| // Package parser 은 모든 웹페이지 (뉴스 / 블로그 / 일반 문서) 의 핵심 내용을 | ||
| // 추출하기 위한 도메인 중립 인터페이스와 모델을 제공합니다 (이슈 #100). | ||
| // | ||
| // Package parser defines domain-agnostic interfaces and models for extracting | ||
| // the main content of any web page. 사이트별 hardcode 파서를 대체하여, DB 기반 rule | ||
| // (storage.ParsingRuleRecord) 만 다른 단일 engine 이 모든 웹페이지를 처리합니다. | ||
| // | ||
| // 두 핵심 인터페이스: | ||
| // - ContentParser : 단일 웹페이지 → Page (핵심 본문 + 메타데이터) | ||
| // - LinkListParser : 카테고리/목록/링크-허브 페이지 → []LinkItem | ||
| // | ||
| // 뉴스 도메인의 NewsArticleParser/NewsListParser 는 본 인터페이스의 도메인 어댑터로 | ||
| // 표현 가능합니다 (Page → NewsArticle 변환은 호출자 책임). | ||
| package parser | ||
|
|
||
| import ( | ||
| "context" | ||
| "time" | ||
|
|
||
| "issuetracker/internal/crawler/core" | ||
| ) | ||
|
|
||
| // Page 는 임의 웹페이지에서 추출한 핵심 내용입니다. | ||
| // | ||
| // Page represents the extracted main content of a web page (article, blog post, | ||
| // product page, etc). 모든 필드는 optional 이며 (URL/Title/MainContent 외에는 빈 값 | ||
| // 허용), 사이트의 rule selectors 가 비어있으면 그 필드는 zero 값으로 남습니다. | ||
| // | ||
| // 뉴스 도메인 사용 시 호출자가 NewsArticle 로 변환: | ||
| // | ||
| // news.NewsArticle{ | ||
| // Title: page.Title, | ||
| // Body: page.MainContent, | ||
| // PublishedAt: page.PublishedAt, | ||
| // ... | ||
| // } | ||
| type Page struct { | ||
| URL string | ||
| Title string | ||
| MainContent string // 페이지 핵심 본문 (article body, blog post, product description 등) | ||
| Summary string // optional — meta description 또는 별도 요약 영역 | ||
| Author string // optional — 게시자/저자 (기사 / 블로그 등) | ||
| PublishedAt time.Time // optional — zero 면 미추출 | ||
| Language string // optional — html lang 또는 메타 (ISO 639-1) | ||
| Category string // optional — 카테고리/섹션 (블로그 카테고리, 제품 카테고리 등) | ||
| Tags []string | ||
| Images []string // optional — page 내 핵심 이미지 URL | ||
| Metadata map[string]string // 확장 — canonical_url / og:* / twitter:* 등 임의 메타 | ||
| } | ||
|
|
||
| // LinkItem 은 목록/링크-허브 페이지에서 추출한 단일 링크입니다. | ||
| // | ||
| // LinkItem represents a single link extracted from a list/category/hub page. | ||
| // URL 은 항상 절대 URL 로 정규화되어야 합니다 (LinkListParser 구현체가 base URL 기준 변환). | ||
| type LinkItem struct { | ||
| URL string | ||
| Title string // anchor text 또는 추출한 제목 | ||
| Snippet string // optional — 짧은 요약/설명 (있을 때) | ||
| } | ||
|
|
||
| // ContentParser 는 웹페이지의 RawContent 를 Page 로 파싱하는 인터페이스입니다. | ||
| // | ||
| // ContentParser parses a single web page's RawContent into a Page. | ||
| // 구현체는 goroutine-safe 해야 합니다. | ||
| // | ||
| // ctx 는 호출자의 cancellation / timeout / trace metadata 전파에 사용됩니다. | ||
| // rule resolver lookup 등 I/O 가 수반되므로 ctx 를 인터페이스에 명시 (Go 컨벤션). | ||
| type ContentParser interface { | ||
| ParsePage(ctx context.Context, raw *core.RawContent) (*Page, error) | ||
| } | ||
|
|
||
| // LinkListParser 는 목록/링크-허브 페이지에서 LinkItem 들을 추출하는 인터페이스입니다. | ||
| // | ||
| // LinkListParser extracts LinkItem entries from a list/category page. | ||
| // 구현체는 goroutine-safe 해야 합니다. | ||
| type LinkListParser interface { | ||
| ParseLinks(ctx context.Context, raw *core.RawContent) ([]LinkItem, error) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| package rule | ||
|
|
||
| import "fmt" | ||
|
|
||
| // ErrorCode 는 rule 패키지의 정규화된 에러 분류입니다. | ||
| // | ||
| // ErrorCode classifies failures from Resolver / Parser. 호출자는 errors.As 로 *Error 를 | ||
| // 추출해 Code 로 분기합니다 (예: ErrNoRule → LLM 자동 생성 fallback). | ||
| type ErrorCode string | ||
|
|
||
| const ( | ||
| // ErrInvalidURL: URL parse 실패 / host 미존재. 호출자가 입력 검증 책임. | ||
| ErrInvalidURL ErrorCode = "invalid_url" | ||
|
|
||
| // ErrNoRule: host + target_type 매칭 활성 rule 없음. | ||
| // 향후 LLM 자동 생성 fallback 진입점 — 호출자가 errors.Is 로 분기 가능. | ||
| ErrNoRule ErrorCode = "no_rule" | ||
|
|
||
| // ErrEmptySelector: rule 의 selector 가 핵심 필드에 대해 비어있음. | ||
| // 예: article 인데 Title selector 없음 → 무의미한 결과 회피 위해 명시 실패. | ||
| ErrEmptySelector ErrorCode = "empty_selector" | ||
|
|
||
| // ErrParseFailure: HTML 파싱 / selector 매칭 실패 (필드 0건 추출 등). | ||
| ErrParseFailure ErrorCode = "parse_failure" | ||
| ) | ||
|
|
||
| // Error 는 rule 패키지의 공통 에러 타입입니다. | ||
| type Error struct { | ||
| Code ErrorCode | ||
| Message string | ||
| Host string // 진단용 (resolver) — 비어있을 수 있음 | ||
| URL string // 진단용 (resolver) — 비어있을 수 있음 | ||
| TargetType string // 진단용 — 비어있을 수 있음 | ||
| Err error // wrap 된 원본 | ||
| } | ||
|
|
||
| func (e *Error) Error() string { | ||
| parts := fmt.Sprintf("[rule:%s] %s", e.Code, e.Message) | ||
| if e.Host != "" { | ||
| parts += fmt.Sprintf(" (host=%s)", e.Host) | ||
| } | ||
| if e.URL != "" { | ||
| parts += fmt.Sprintf(" (url=%s)", e.URL) | ||
| } | ||
| if e.TargetType != "" { | ||
| parts += fmt.Sprintf(" (type=%s)", e.TargetType) | ||
| } | ||
| if e.Err != nil { | ||
| parts += fmt.Sprintf(": %v", e.Err) | ||
| } | ||
| return parts | ||
| } | ||
|
|
||
| // Unwrap 은 errors.As / errors.Is 가 wrap chain 을 따라가도록 합니다. | ||
| func (e *Error) Unwrap() error { return e.Err } | ||
|
|
||
| // Is 는 errors.Is 호환 비교입니다 (Gemini code review 피드백 반영). | ||
| // | ||
| // target 의 비어있지 않은 모든 필드에 대해 AND 비교를 수행 — 호출자가 부분 매칭으로 | ||
| // 분기 가능 ("Code=='no_rule' 인 모든 Error" / "Host=='naver.com' 인 ErrParseFailure" 등). | ||
| // target 의 모든 식별 필드가 비어있으면 모든 Error 와 매칭됨 (errors.Is 의 일반적 의미). | ||
| func (e *Error) Is(target error) bool { | ||
| t, ok := target.(*Error) | ||
| if !ok { | ||
| return false | ||
| } | ||
| if t.Code != "" && e.Code != t.Code { | ||
| return false | ||
| } | ||
| if t.Host != "" && e.Host != t.Host { | ||
| return false | ||
| } | ||
| if t.URL != "" && e.URL != t.URL { | ||
| return false | ||
| } | ||
| if t.TargetType != "" && e.TargetType != t.TargetType { | ||
| return false | ||
| } | ||
| return true | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.