update for mcp
This commit is contained in:
@@ -5,6 +5,12 @@ from __future__ import annotations
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
import httpx
|
||||
import trafilatura
|
||||
from loguru import logger
|
||||
|
||||
from app.config.settings import settings
|
||||
|
||||
|
||||
@dataclass
|
||||
class RawEvent:
|
||||
@@ -21,7 +27,10 @@ class RawEvent:
|
||||
effective_at: str | None
|
||||
category: str
|
||||
tags: list[str] = field(default_factory=list)
|
||||
raw_text: str = "" # full crawled text for hashing + LLM
|
||||
# Whatever text the list page yields. CrawlService upgrades this by calling
|
||||
# fetch_full_text(full_text_url); this value is the fallback when that
|
||||
# fails. Used for change hashing and for the version diff.
|
||||
raw_text: str = ""
|
||||
|
||||
|
||||
class BaseCrawler(ABC):
|
||||
@@ -30,3 +39,37 @@ class BaseCrawler(ABC):
|
||||
@abstractmethod
|
||||
def fetch(self, limit: int = 50) -> list[RawEvent]:
|
||||
"""Fetch up to `limit` recent events from the data source."""
|
||||
|
||||
def fetch_full_text(self, url: str) -> str:
|
||||
"""Download a regulation detail page and extract its body text.
|
||||
|
||||
Change detection is only as good as the text it compares, and list
|
||||
pages carry nothing but a standard code and a title. This default
|
||||
implementation serves all current sources; a source that needs PDF
|
||||
extraction or authentication overrides this one method.
|
||||
|
||||
Returns an empty string on any failure rather than raising, so one
|
||||
unreachable page cannot abort a whole crawl run. The caller decides how
|
||||
to degrade.
|
||||
"""
|
||||
if not url:
|
||||
return ""
|
||||
try:
|
||||
response = httpx.get(
|
||||
url,
|
||||
timeout=settings.perception_crawl_timeout_seconds,
|
||||
follow_redirects=True,
|
||||
)
|
||||
response.raise_for_status()
|
||||
except Exception as exc: # noqa: BLE001 - any transport error degrades the same way
|
||||
logger.warning("Full-text fetch failed url={} err={}", url, exc)
|
||||
return ""
|
||||
|
||||
# trafilatura scores 0.92 F1 on government pages against 0.78 for
|
||||
# readability-lxml, and handles CJK content; include_tables matters
|
||||
# because regulatory limits are frequently tabulated.
|
||||
extracted = trafilatura.extract(response.text, include_tables=True)
|
||||
if not extracted:
|
||||
logger.warning("Full-text extraction returned nothing url={}", url)
|
||||
return ""
|
||||
return extracted.strip()
|
||||
|
||||
@@ -8,6 +8,7 @@ import httpx
|
||||
from bs4 import BeautifulSoup
|
||||
from loguru import logger
|
||||
|
||||
from app.config.settings import settings
|
||||
from app.infrastructure.perception.crawlers.base import BaseCrawler, RawEvent
|
||||
from ._utils import extract_tags, parse_date
|
||||
|
||||
@@ -33,7 +34,11 @@ class CatarcCrawler(BaseCrawler):
|
||||
while len(events) < limit and page <= max_pages:
|
||||
url = f"{_BASE_URL}?page={page}"
|
||||
try:
|
||||
resp = httpx.get(url, timeout=30, follow_redirects=True)
|
||||
resp = httpx.get(
|
||||
url,
|
||||
timeout=settings.perception_crawl_timeout_seconds,
|
||||
follow_redirects=True,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
except Exception as exc:
|
||||
logger.warning("CATARC fetch failed page={} err={}", page, exc)
|
||||
|
||||
@@ -9,6 +9,7 @@ import httpx
|
||||
from bs4 import BeautifulSoup
|
||||
from loguru import logger
|
||||
|
||||
from app.config.settings import settings
|
||||
from app.infrastructure.perception.crawlers.base import BaseCrawler, RawEvent
|
||||
from ._utils import parse_date
|
||||
|
||||
@@ -53,7 +54,11 @@ class EurlexCrawler(BaseCrawler):
|
||||
if len(events) >= limit:
|
||||
break
|
||||
try:
|
||||
resp = httpx.get(rss_url, timeout=30, follow_redirects=True)
|
||||
resp = httpx.get(
|
||||
rss_url,
|
||||
timeout=settings.perception_crawl_timeout_seconds,
|
||||
follow_redirects=True,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
except Exception as exc:
|
||||
logger.warning("EUR-Lex RSS fetch failed url={} err={}", rss_url, exc)
|
||||
|
||||
@@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
import httpx
|
||||
from loguru import logger
|
||||
|
||||
from app.config.settings import settings
|
||||
from app.infrastructure.perception.crawlers.base import BaseCrawler, RawEvent
|
||||
from ._utils import extract_tags, parse_date
|
||||
|
||||
@@ -22,7 +23,12 @@ def _fetch_page(std_type: int, page: int, page_size: int) -> list[dict]:
|
||||
"p.p7": page_size,
|
||||
}
|
||||
try:
|
||||
resp = httpx.get(_BASE_URL, params=params, headers=_HEADERS, timeout=30)
|
||||
resp = httpx.get(
|
||||
_BASE_URL,
|
||||
params=params,
|
||||
headers=_HEADERS,
|
||||
timeout=settings.perception_crawl_timeout_seconds,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
return data.get("rows", []) or []
|
||||
|
||||
Reference in New Issue
Block a user