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
112 changes: 56 additions & 56 deletions cmd/issuetracker/main.go

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions cmd/processor/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func main() {

ctx = log.ToContext(ctx)

// ── Metrics endpoint (이슈 #165) ──────────────────────────────────────────
// ── Metrics endpoint ──────────────────────────────────────────
metricsCfg, err := config.LoadMetrics()
if err != nil {
log.WithError(err).Fatal("failed to load metrics config")
Expand Down Expand Up @@ -95,7 +95,7 @@ func main() {
defer producer.Close()

// ── 5. Validate Worker 시작 ───────────────────────────────────────────────
// validator 결과 (passed/rejected) 는 contentSvc.UpdateValidationStatus 로 contents 에 기록 (이슈 #135 / #161).
// validator 결과 (passed/rejected) 는 contentSvc.UpdateValidationStatus 로 contents 에 기록.
// processor 단독 실행은 dev/test 시나리오 — Redis wiring 없이 NoopProcessingLock 사용.
// 다중 인스턴스 운영은 cmd/issuetracker 통합 바이너리에서 Redis 기반 ProcessingLock 공유.
worker := validate.NewWorker(consumer, producer, contentSvc, locks.NoopProcessingLock{}, validateWorkerCount, validateCfg)
Expand All @@ -112,7 +112,7 @@ func main() {
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)

<-sigChan
// 셧다운 시작 시점부터 logger 에 shutting_down=true 를 부여합니다 (이슈 #72 TODO #4).
// 셧다운 시작 시점부터 logger 에 shutting_down=true 를 부여합니다.
//
// 적용 범위 (중요):
// - 본 변수 'log' 와 shutdownCtx 를 통해 전달되는 로그에만 부착됩니다 (Stop 경로).
Expand All @@ -124,7 +124,7 @@ func main() {
log.Warn("shutdown signal received, draining workers...")
cancel()

// WithoutCancel(ctx) 로 parent cancellation 은 분리하되 ctx values (logger 등) 보존 (PR #273 리뷰).
// WithoutCancel(ctx) 로 parent cancellation 은 분리하되 ctx values (logger 등) 보존.
shutdownCtx, shutdownCancel := context.WithTimeout(context.WithoutCancel(ctx), shutdownCfg.Timeout)
defer shutdownCancel()
shutdownCtx = log.ToContext(shutdownCtx)
Expand Down
4 changes: 2 additions & 2 deletions cmd/rule-validator/main.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// rule-validator 는 parsing_rules 의 특정 row 를 의미 검증하는 수동 운영 CLI 입니다 (이슈 #257).
// rule-validator 는 parsing_rules 의 특정 row 를 의미 검증하는 수동 운영 CLI 입니다.
//
// 사용법:
//
Expand Down Expand Up @@ -82,7 +82,7 @@ func main() {

fmt.Printf("검증 결과: valid=%v\nreason: %s\n", res.Valid, res.Reason)

// CLI 는 수동 운영 도구 — API 오류(verr)도 실패로 처리하여 운영자가 인지 가능하도록 (이슈 #265 리뷰).
// CLI 는 수동 운영 도구 — API 오류(verr)도 실패로 처리하여 운영자가 인지 가능하도록.
// best-effort 통과는 자동 파이프라인(validator.Pool) 에서만 적용.
if verr != nil {
fmt.Printf("검증 API 오류: %v\n", verr)
Expand Down
6 changes: 3 additions & 3 deletions internal/locks/ingestion_lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const (
DefaultIngestionLockTTL = 24 * time.Hour
)

// IngestionLock 은 URL 이 파이프라인에 진입한 상태를 marker 로 표시합니다 (이슈 #178).
// IngestionLock 은 URL 이 파이프라인에 진입한 상태를 marker 로 표시합니다.
//
// 의도:
//
Expand All @@ -39,7 +39,7 @@ type IngestionLock interface {
// 두 URL 이 다른 marker 를 갖지 않도록.
Acquire(ctx context.Context, url string) (acquired bool, err error)

// AcquireWithTTL 은 Acquire 와 동일하되 호출별 TTL 을 지정합니다 (이슈 #285).
// AcquireWithTTL 은 Acquire 와 동일하되 호출별 TTL 을 지정합니다.
//
// 용도: PipelineGuard 가 target type 에 따라 Article (24h) / Category (단명) 로 다른
// TTL 적용. ttl<=0 이면 default TTL fallback.
Expand Down Expand Up @@ -88,7 +88,7 @@ func (l *RedisIngestionLock) Acquire(ctx context.Context, url string) (bool, err
return l.locker.AcquireLock(ctx, ingestionKey(url), l.ttl)
}

// AcquireWithTTL 은 호출별 TTL 로 marker 를 set 시도합니다 (이슈 #285).
// AcquireWithTTL 은 호출별 TTL 로 marker 를 set 시도합니다.
//
// ttl<=0 이면 RedisIngestionLock 의 default TTL 사용. nil receiver / nil locker 보호는 Acquire 와 동일.
func (l *RedisIngestionLock) AcquireWithTTL(ctx context.Context, url string, ttl time.Duration) (bool, error) {
Expand Down
6 changes: 3 additions & 3 deletions internal/locks/pipeline_guard.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"issuetracker/internal/processor/fetcher/core"
)

// DefaultCategoryTTL 은 Category target 의 pipeline guard TTL default 입니다 (이슈 #285).
// DefaultCategoryTTL 은 Category target 의 pipeline guard TTL default 입니다.
//
// Category 는 정기 갱신이 본질이므로 Article (24h) 보다 훨씬 짧은 TTL.
// fetch + ParseLinks 한 사이클이 완료될 때까지만 marker 가 유지되도록 — 운영 환경의
Expand All @@ -17,7 +17,7 @@ import (
const DefaultCategoryTTL = 60 * time.Second

// PipelineGuard 는 publish 진입점에서 \"이 URL 이 현재 파이프라인에 있는가\" 를 체크하는 통합
// 게이트입니다 (이슈 #285).
// 게이트입니다.
//
// 의도:
//
Expand Down Expand Up @@ -55,7 +55,7 @@ func NewPipelineGuard(lock IngestionLock, categoryTTL time.Duration) *PipelineGu
// - acquired=false : 이미 pipeline 안 (다른 publish 가 marker 점유) — 호출자가 skip
// - err != nil : Redis 일시 장애 등 — 호출자 정책 (보통 fail-open) 으로 처리
//
// targetType 별 TTL (PR #286 gemini 리뷰 — 명시적 switch 로 안전성 강화):
// targetType 별 TTL:
// - core.TargetTypeCategory : categoryTTL (단명, default 60s)
// - core.TargetTypeArticle : IngestionLock 의 default TTL (24h)
// - 그 외 (Sitemap 등 미래 target type) : IngestionLock default TTL fallback
Expand Down
6 changes: 3 additions & 3 deletions internal/locks/processing_lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

const (
// processingLockKeyPrefix 는 단계별 URL 단위 처리 lock 의 키 접두사입니다 (이슈 #178).
// processingLockKeyPrefix 는 단계별 URL 단위 처리 lock 의 키 접두사입니다.
// 형식: "processing:<stage>:url:<sha256(normalized_url)>" — Redis 운영자가 grep / SCAN 으로
// 다른 namespace (ingestion:url:..., lock:job:... 등) 와 구분 가능.
processingLockKeyPrefix = "processing:"
Expand All @@ -20,7 +20,7 @@ const (
DefaultProcessingLockTTL = 10 * time.Minute
)

// ProcessingLock 은 파이프라인 단계별 URL 중복 처리를 방지하는 분산 락 인터페이스입니다 (이슈 #178).
// ProcessingLock 은 파이프라인 단계별 URL 중복 처리를 방지하는 분산 락 인터페이스입니다.
//
// \"각 프로세스마다 작업 중인 URL 을 다른 동일 프로세스 단계의 워커가 건들지 못하도록 막는 dedup.\"
//
Expand Down Expand Up @@ -49,7 +49,7 @@ const (
StageValidator = "validator"
)

// ProcessingKey 는 (stage, normalized_url) 페어로 ProcessingLock 의 Redis 키를 생성합니다 (이슈 #178).
// ProcessingKey 는 (stage, normalized_url) 페어로 ProcessingLock 의 Redis 키를 생성합니다.
//
// 호출자 책임: url 은 pkg/links.Normalizer 로 정규화된 상태로 전달 — 동일 컨텐츠를 가리키는
// 두 URL 이 다른 키를 갖지 않도록.
Expand Down
2 changes: 1 addition & 1 deletion internal/processor/fetcher/core/http2_metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
// log.Printf("%s: %d", errType, count)
// }
//
// 본 카운터는 이슈 #71 의 'received DATA after END_STREAM' 같은 protocol error 의
// 본 카운터는 'received DATA after END_STREAM' 같은 protocol error 의
// 재발 빈도 추적을 목표로 합니다. 빈도 추이가 충분히 누적되면 후속 단계
// (ForceHTTP1 옵션화, 풀 제거 hook) 도입 여부를 결정합니다.
type HTTP2ErrorCounter struct {
Expand Down
2 changes: 1 addition & 1 deletion internal/processor/fetcher/core/http_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const (
// defaultHTTPClient는 기본 HTTP 클라이언트를 생성합니다.
// Connection pooling과 timeout을 적용합니다.
//
// HTTP/2 stream/connection error 모니터링 (이슈 #71/#117):
// HTTP/2 stream/connection error 모니터링:
// - http2.ConfigureTransports 로 base transport 에 http2 transport 를 명시 구성
// - http2.Transport.CountError hook 으로 에러 유형별 카운트 (DefaultHTTP2ErrorCounter 누적)
// - 에러 발생 시 로그로 가시화 — 운영 모니터링/알림에서 빈도 추적 가능
Expand Down
2 changes: 1 addition & 1 deletion internal/processor/fetcher/core/http_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "net/http"
// CheckHTTPStatus 는 HTTP 응답 상태 코드를 검사하여 4xx/5xx 에러를 적절한
// CrawlerError 로 변환합니다. 정상(2xx/3xx) 인 경우 nil 을 반환합니다.
//
// 분기 정책 (이슈 #75 — fetcher 공통 추출):
// 분기 정책:
// - 404 (Not Found) → NewNotFoundError (retry 불가)
// - 429 (Too Many Requests) → NewRateLimitError (retry 가능, code "HTTP_429")
// - 5xx (Server Error) → NewHTTPServerError (retry 가능)
Expand Down
4 changes: 2 additions & 2 deletions internal/processor/fetcher/core/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ type RawContent struct {
}

// NewRawContent 는 fetcher 들이 공통으로 사용하는 RawContent 조립 패턴을
// 일원화한 생성자입니다 (이슈 #75 — fetcher 공통 추출).
// 일원화한 생성자입니다.
//
// 인자:
// - name : crawler 이름 (ID prefix 로 사용)
Expand Down Expand Up @@ -120,7 +120,7 @@ type RawContentRef struct {
URL string `json:"url"`
FetchedAt time.Time `json:"fetched_at"`
SourceInfo SourceInfo `json:"source_info"`
LLMRetryCount int `json:"llm_retry_count,omitempty"` // 이슈 #237: LLM validate 실패 재큐 횟수
LLMRetryCount int `json:"llm_retry_count,omitempty"` // LLM validate 실패 재큐 횟수
Comment thread
juhy0987 marked this conversation as resolved.
}

// ContentRef는 contents 테이블에 저장된 Content의 경량 참조입니다.
Expand Down
4 changes: 2 additions & 2 deletions internal/processor/fetcher/core/worker_id.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package core

import "context"

// 이슈 #229 — KafkaConsumerPool 의 worker goroutine 별 인덱스를 ctx 로 전달하기 위한 helper.
// ChromedpJobHandler 가 per-worker Semaphore 슬롯을 lookup 하는 데 사용. 이슈 #230 에서
// KafkaConsumerPool 의 worker goroutine 별 인덱스를 ctx 로 전달하기 위한 helper.
// ChromedpJobHandler 가 per-worker Semaphore 슬롯을 lookup 하는 데 사용.
// general.ChainHandler 의 chromedpChains slice lookup 에도 동일 키 재사용 — 이를 위해
// worker 패키지 외부 (사이클 없는 core) 에 위치.
//
Expand Down
26 changes: 13 additions & 13 deletions internal/processor/fetcher/domain/general/chain_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
"issuetracker/pkg/queue"
)

// ChainHandler 는 fetcher worker 의 책임을 담당하는 handler.Handler 어댑터입니다 (이슈 #134).
// ChainHandler 는 fetcher worker 의 책임을 담당하는 handler.Handler 어댑터입니다.
//
// 분리 후 책임 (Claim Check 패턴):
// 1. Chain (GoQuery / Browser) 으로 raw HTML fetch
Expand All @@ -26,13 +26,13 @@ import (
// consumer group) 의 책임으로 이전됨. 본 핸들러는 nil Content slice 를 반환하여 worker pool 의
// publishNormalized 단계를 스킵하도록 함.
//
// host 단위 fetcher 정책 (이슈 #175 단계 1):
// host 단위 fetcher 정책:
// - Resolver 가 nil 이거나 룰 미등록 host: DefaultChain 사용 (gq → browser fallback)
// - 룰 = 'chromedp': ChromedpChains 사용 (browser only) — goquery 시도 skip
// - 룰 = 'goquery': DefaultChain 사용 (gq → browser fallback) — 현재로선 default 와 동일.
// 단계 3 의 자동 downgrade 정책이 도입되면 GoQueryOnly chain 으로 분기 가능.
//
// 이슈 #230 — ChromedpChains 를 worker_id 별 slice 로 분리하여 worker:Chrome 1:1 매핑 활성화:
// ChromedpChains 를 worker_id 별 slice 로 분리하여 worker:Chrome 1:1 매핑 활성화:
// - 각 chain 인스턴스는 자기 전용 RemoteURL 의 ChromedpCrawler 를 보유
// - HandleChromedpOnly 가 ctx 의 worker_id 로 자기 chain 선택 → worker:Chrome 격리
// - chain 길이는 chromedp pool 의 WorkerCount 와 일치 (main.go wiring 시 보장)
Expand All @@ -41,11 +41,11 @@ import (
// - parser 가 무거워져도 (chromedp 가 큰 HTML 처리, LLM 호출 등) fetcher worker 슬롯 점유 X
// - parser worker 인스턴스 수를 fetcher 와 독립으로 스케일 가능
// - raw HTML 이 DB 에 보존되어 worker crash 시에도 복구 가능
// - 파싱 실패한 raw 가 잔존 → LLM 으로 새 rule 생성 (이슈 #149) 후 재처리 가능
// - 파싱 실패한 raw 가 잔존 → LLM 으로 새 rule 생성 후 재처리 가능
type ChainHandler struct {
Crawler SourceCrawler
DefaultChain Handler // gq → browser (현재 동작 — 룰 미등록 host 의 기본)
ChromedpChains []Handler // worker_id 별 browser only chain (이슈 #230)
ChromedpChains []Handler // worker_id 별 browser only chain
Resolver rule.Resolver // optional. nil 이면 항상 DefaultChain 사용
RawSvc service.RawContentService
Producer queue.Producer
Expand All @@ -57,7 +57,7 @@ type ChainHandler struct {
// ChromedpChains 가 nil/empty 이면 룰 = 'chromedp' 매칭이어도 DefaultChain fallback (warn 로그).
// Resolver 가 nil 이면 룰 조회 없이 항상 DefaultChain — 기존 동작 100% 보존.
//
// 이슈 #230 — chromedpChains 길이는 chromedp pool 의 WorkerCount 와 일치해야 함 (호출자 책임).
// chromedpChains 길이는 chromedp pool 의 WorkerCount 와 일치해야 함 (호출자 책임).
// 단일 Chrome 운영 호환 모드 (sub-issue #229 머지 직후 시점) 에서는 길이 1 의 slice 로 호출.
func NewChainHandler(
crawler SourceCrawler,
Expand Down Expand Up @@ -120,7 +120,7 @@ func (h *ChainHandler) resolveChromedpChain(ctx context.Context) Handler {
// - isChromedp=true: 호출자 (Handle) 가 republish 분기로 진입 (chain 자체는 사용 안 함)
// - isChromedp=false: chain 직접 호출
//
// 이슈 #230 — ChromedpChains 가 worker_id 별 slice 가 되면서 selectChain 단계에서는 어느 슬롯을
// ChromedpChains 가 worker_id 별 slice 가 되면서 selectChain 단계에서는 어느 슬롯을
// 쓸지 결정하지 않음 (republish 후 ChromedpJobHandler 가 slot 선택). Handle 의 분기 식별을
// chain 포인터 비교 대신 isChromedp 플래그로 안전하게 처리.
//
Expand Down Expand Up @@ -205,11 +205,11 @@ func extractHost(rawURL string) string {
//
// 항상 nil Content slice 를 반환 — 파싱은 parser worker 가 TopicFetched 를 consume 하여 처리.
//
// 이슈 #218: chromedp 처리는 별도 worker pool 로 격리. selectChain 결과가 chromedp 매칭이거나
// chromedp 처리는 별도 worker pool 로 격리. selectChain 결과가 chromedp 매칭이거나
// DefaultChain 의 GoQuery 가 lazy detect 시 sentinel 반환하면 직접 호출 대신 TopicCrawlChromedp
// 로 republish — Chrome 자원 동시 호출량을 chromedp pool 의 semaphore 로 제어.
//
// 이슈 #230 — selectChain 이 (chain, isChromedp) tuple 반환. ChromedpChains slice 모델로
// selectChain 이 (chain, isChromedp) tuple 반환. ChromedpChains slice 모델로
// 변경되어 chain 포인터 비교가 부적합 → isChromedp 플래그로 분기.
func (h *ChainHandler) Handle(ctx context.Context, job *core.CrawlJob) ([]*core.Content, error) {
if h.DefaultChain == nil || h.Log == nil || h.RawSvc == nil || h.Producer == nil {
Expand All @@ -218,7 +218,7 @@ func (h *ChainHandler) Handle(ctx context.Context, job *core.CrawlJob) ([]*core.

chain, isChromedp := h.selectChain(ctx, job)

// 이슈 #218: chromedp 매칭 (force_fetcher 또는 Resolver 룰) → 직접 호출 안 하고 republish.
// chromedp 매칭 (force_fetcher 또는 Resolver 룰) → 직접 호출 안 하고 republish.
if isChromedp {
if err := h.republishToChromedpQueue(ctx, job); err != nil {
return nil, fmt.Errorf("republish to chromedp queue for %s: %w", job.Target.URL, err)
Expand All @@ -228,7 +228,7 @@ func (h *ChainHandler) Handle(ctx context.Context, job *core.CrawlJob) ([]*core.

raw, err := chain.Handle(ctx, job)
if err != nil {
// 이슈 #218: GoQuery 의 lazy detect sentinel — chromedp pool 로 republish 후 정상 종료.
// GoQuery 의 lazy detect sentinel — chromedp pool 로 republish 후 정상 종료.
if errors.Is(err, ErrLazyContentNeedsBrowser) {
if pubErr := h.republishToChromedpQueue(ctx, job); pubErr != nil {
return nil, fmt.Errorf("republish to chromedp queue (lazy detect) for %s: %w", job.Target.URL, pubErr)
Expand Down Expand Up @@ -279,7 +279,7 @@ func (h *ChainHandler) processFetchedRaw(ctx context.Context, job *core.CrawlJob
return nil
}

// HandleChromedpOnly 는 ChromedpChains 중 worker_id 슬롯을 호출하여 chromedp 단독 fetch 를 수행합니다 (이슈 #218, #230).
// HandleChromedpOnly 는 ChromedpChains 중 worker_id 슬롯을 호출하여 chromedp 단독 fetch 를 수행합니다.
//
// chromedp pool 의 ChromedpJobHandler 가 같은 ChainHandler 인스턴스를 Registry 에서 lookup 하여
// 본 메소드 호출 — Resolver / force_fetcher / republish 분기 skip 하고 ctx 의 worker_id 로
Expand Down Expand Up @@ -313,7 +313,7 @@ func (h *ChainHandler) HandleChromedpOnly(ctx context.Context, job *core.CrawlJo
return nil, h.processFetchedRaw(ctx, job, raw, "chromedp")
}

// republishToChromedpQueue 는 본 job 을 TopicCrawlChromedp 로 다시 발행합니다 (이슈 #218).
// republishToChromedpQueue 는 본 job 을 TopicCrawlChromedp 로 다시 발행합니다.
//
// chromedp 처리 책임을 본 worker (goquery pool) 에서 분리 — 별도 chromedp worker pool 이 receive
// 후 semaphore 로 Chrome 자원 보호. force_fetcher metadata + token 은 보존하여 chromedp pool 의
Expand Down
4 changes: 2 additions & 2 deletions internal/processor/fetcher/domain/general/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type Handler interface {
SetNext(h Handler)
}

// ErrLazyContentNeedsBrowser 는 GoQueryFetchHandler 가 lazy-load 감지 시 반환하는 sentinel 입니다 (이슈 #218).
// ErrLazyContentNeedsBrowser 는 GoQueryFetchHandler 가 lazy-load 감지 시 반환하는 sentinel 입니다.
//
// 기존 동작 — chain next 위임으로 같은 worker 에서 chromedp 호출 — 은 단일 Chrome 자원 풀
// 고갈을 초래. 본 sentinel 을 받은 ChainHandler 는 next 위임 대신 TopicCrawlChromedp 로
Expand Down Expand Up @@ -75,7 +75,7 @@ func (h *GoQueryFetchHandler) Handle(ctx context.Context, job *core.CrawlJob) (*
}

if h.hasLazyContent(raw.HTML) {
// 이슈 #218: chain next 위임 대신 sentinel 반환. ChainHandler 가 받아 TopicCrawlChromedp
// chain next 위임 대신 sentinel 반환. ChainHandler 가 받아 TopicCrawlChromedp
// 로 republish — chromedp 호출이 별도 worker pool 에서 격리되어 Chrome 자원 안정.
h.log.WithFields(map[string]interface{}{
"handler": "goquery",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Package sources 는 fetcher_rules DB 에서 모든 사이트 크롤러를 읽어 Registry 에 등록합니다 (이슈 #246).
// Package sources 는 fetcher_rules DB 에서 모든 사이트 크롤러를 읽어 Registry 에 등록합니다.
//
// 기존 사이트별 kr.Register / us.Register 를 RegisterAll 하나로 통합.
// SourceInfo·RequestsPerHour 는 fetcher_rules 테이블 (migration 014) 에서 조회.
Expand All @@ -25,11 +25,11 @@ import (
// defaultLazyKeywords 는 대부분 뉴스 사이트가 사용하는 lazy-load 감지 attr 목록입니다.
// chromedp 가 활성화된 경우에만 BuildChain 에 전달합니다 — pool 이 꺼진 환경에서
// lazy sentinel 이 발생하면 TopicCrawlChromedp 로 republish 되지만 consumer 가 없어
// 메시지가 유실됩니다 (이슈 #218, Copilot 피드백 반영).
// 메시지가 유실됩니다 .
var defaultLazyKeywords = []string{"data-lazy-src", "lazyload", "data-lazy"}

// RegisterAll 은 fetcher_rules 테이블에서 source_name 이 채워진 모든 source 를 읽어
// Registry 에 등록합니다 (이슈 #246).
// Registry 에 등록합니다.
//
// 각 source_name 별로 goquery + (optional) chromedp ChainHandler 를 생성.
// chromedpRemoteURLs 가 empty 이면 chromedp chain 없이 goquery-only 로 등록.
Expand Down
2 changes: 1 addition & 1 deletion internal/processor/fetcher/domain/general/types.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Package general 은 임의 웹페이지 (뉴스/블로그/제품/일반 문서) 크롤링·파싱을 위한
// 도메인 중립 추상화를 제공합니다 (이슈 #100 / #139 통합).
// 도메인 중립 추상화를 제공합니다.
//
// Package general provides domain-neutral abstractions for crawling arbitrary web pages
// (news, blogs, product pages, generic documents). 기존 news 도메인을 흡수합니다 —
Expand Down
Loading
Loading