import asyncio import random from datetime import datetime import httpx from app.config import settings from app.crawler.base import BaseSpider, CrawlResult, PipelineConfig from app.crawler.parsers import parse_dahuagov_html, parse_dahuagov_detail_pubdate class DahuagovSpider(BaseSpider): name = "dahuagov" source_code = "dahuagov" source_name = "大化县政府网采购公告" BASE_URL = "http://www.gxdh.gov.cn" ANNOUNCEMENT_PATH = "/xxgk/zdlyxxgk/ggzypzly/zfcgly/cggg/" def get_pipeline_config(self) -> PipelineConfig: return PipelineConfig( filter_enabled=False, keywords=[], dedup_enabled=True, notify_mode="all", mark_sent=True, ) async def crawl(self) -> CrawlResult: start_time = datetime.now() url = self.BASE_URL + self.ANNOUNCEMENT_PATH async with httpx.AsyncClient(timeout=settings.crawler_timeout) as client: await self._delay() try: headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Accept": "text/html,application/xhtml+xml", "Accept-Language": "zh-CN,zh;q=0.9", "Referer": self.BASE_URL, } response = await client.get(url, headers=headers) if response.status_code != 200: return CrawlResult( source_code=self.source_code, source_name=self.source_name, error_message=f"HTTP {response.status_code}", crawled_at=start_time, ) html = response.text except Exception as e: return CrawlResult( source_code=self.source_code, source_name=self.source_name, error_message=str(e), crawled_at=start_time, ) announcements = parse_dahuagov_html(html, start_time) await self._fetch_detail_dates(client, announcements, headers) duration = (datetime.now() - start_time).total_seconds() return CrawlResult( source_code=self.source_code, source_name=self.source_name, total_count=len(announcements), new_count=len(announcements), announcements=announcements, crawled_at=start_time, duration=duration, ) async def _fetch_detail_dates( self, client: httpx.AsyncClient, announcements: list[dict], headers: dict, ) -> None: """并发抓取详情页,用 PubDate meta 更新精确发布时间""" sem = asyncio.Semaphore(5) async def fetch_one(ann: dict) -> None: async with sem: await asyncio.sleep(random.uniform(0.3, 0.8)) try: resp = await client.get(ann["content_url"], headers=headers) if resp.status_code == 200: pub = parse_dahuagov_detail_pubdate(resp.text) if pub: ann["publish_date"] = pub ann["is_today"] = pub.date() == datetime.now().date() except Exception: pass # 保留列表页日期作为 fallback await asyncio.gather(*[fetch_one(ann) for ann in announcements]) async def _delay(self): delay = random.uniform(1.0, 3.0) await asyncio.sleep(delay)