feat: 添加 Spider 基类 + Pipeline 配置 + 测试
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import hashlib
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class CrawlResult:
|
||||
source_code: str
|
||||
source_name: str
|
||||
total_count: int = 0
|
||||
new_count: int = 0
|
||||
announcements: list = field(default_factory=list)
|
||||
error_message: Optional[str] = None
|
||||
crawled_at: datetime = field(default_factory=datetime.now)
|
||||
duration: float = 0.0
|
||||
|
||||
@property
|
||||
def success(self) -> bool:
|
||||
return self.error_message is None
|
||||
|
||||
|
||||
@dataclass
|
||||
class PipelineConfig:
|
||||
filter_enabled: bool = True
|
||||
keywords: List[str] = field(default_factory=list)
|
||||
dedup_enabled: bool = True
|
||||
notify_mode: str = "filtered"
|
||||
mark_sent: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
class PipelineResult:
|
||||
stored: int = 0
|
||||
filtered: int = 0
|
||||
notified: int = 0
|
||||
markdown_generated: bool = False
|
||||
|
||||
|
||||
class BaseSpider(ABC):
|
||||
name: str
|
||||
source_code: str
|
||||
source_name: str
|
||||
|
||||
@abstractmethod
|
||||
async def crawl(self) -> CrawlResult:
|
||||
...
|
||||
|
||||
def get_pipeline_config(self) -> PipelineConfig:
|
||||
return PipelineConfig()
|
||||
|
||||
@staticmethod
|
||||
def generate_content_hash(title: str, publish_date: str, purchase_name: str,
|
||||
content_url: str, source_code: str) -> str:
|
||||
content = f"{title}|{publish_date}|{purchase_name}|{content_url}|{source_code}"
|
||||
return hashlib.sha256(content.encode("utf-8")).hexdigest()
|
||||
@@ -0,0 +1,13 @@
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_announcement_data():
|
||||
return {
|
||||
"title": "测试公告标题",
|
||||
"publish_date": "2026-05-09",
|
||||
"purchase_name": "测试单位",
|
||||
"content_url": "https://example.com/detail/123",
|
||||
"source_code": "ZcyAnnouncement1",
|
||||
"source_name": "采购公告",
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import pytest
|
||||
from datetime import datetime
|
||||
from app.crawler.base import BaseSpider, CrawlResult, PipelineConfig, PipelineResult
|
||||
|
||||
|
||||
class FakeSpider(BaseSpider):
|
||||
name = "test_spider"
|
||||
source_code = "test_source"
|
||||
source_name = "测试来源"
|
||||
|
||||
async def crawl(self) -> CrawlResult:
|
||||
return CrawlResult(
|
||||
source_code=self.source_code,
|
||||
source_name=self.source_name,
|
||||
total_count=5,
|
||||
new_count=3,
|
||||
crawled_at=datetime.now(),
|
||||
)
|
||||
|
||||
def get_pipeline_config(self) -> PipelineConfig:
|
||||
return PipelineConfig(
|
||||
filter_enabled=True,
|
||||
keywords=["测试"],
|
||||
dedup_enabled=True,
|
||||
notify_mode="filtered",
|
||||
mark_sent=False,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_base_spider_crawl():
|
||||
spider = FakeSpider()
|
||||
result = await spider.crawl()
|
||||
assert result.source_code == "test_source"
|
||||
assert result.total_count == 5
|
||||
assert result.new_count == 3
|
||||
assert result.success is True
|
||||
|
||||
|
||||
def test_pipeline_config_defaults():
|
||||
config = PipelineConfig()
|
||||
assert config.filter_enabled is True
|
||||
assert config.keywords == []
|
||||
assert config.dedup_enabled is True
|
||||
assert config.notify_mode == "filtered"
|
||||
assert config.mark_sent is False
|
||||
|
||||
|
||||
def test_pipeline_result_defaults():
|
||||
result = PipelineResult()
|
||||
assert result.stored == 0
|
||||
assert result.filtered == 0
|
||||
assert result.notified == 0
|
||||
assert result.markdown_generated is False
|
||||
|
||||
|
||||
def test_generate_content_hash():
|
||||
h = BaseSpider.generate_content_hash(
|
||||
"title", "2026-05-09", "unit", "https://x.com", "source"
|
||||
)
|
||||
assert len(h) == 64
|
||||
assert all(c in "0123456789abcdef" for c in h)
|
||||
|
||||
|
||||
def test_generate_content_hash_deterministic():
|
||||
h1 = BaseSpider.generate_content_hash("t", "d", "p", "u", "s")
|
||||
h2 = BaseSpider.generate_content_hash("t", "d", "p", "u", "s")
|
||||
assert h1 == h2
|
||||
|
||||
|
||||
def test_crawl_result_failure():
|
||||
result = CrawlResult(
|
||||
source_code="s", source_name="n",
|
||||
error_message="连接超时"
|
||||
)
|
||||
assert result.success is False
|
||||
Reference in New Issue
Block a user