From e55a52f20b062f46fa0dbfe72bb19381ff5e5e15 Mon Sep 17 00:00:00 2001 From: wangys <3401275564@qq.com> Date: Wed, 12 Nov 2025 19:16:50 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E8=AF=95=E4=BF=AE=E6=94=B9=E7=88=AC?= =?UTF-8?q?=E8=99=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- output/out.json | 7 - output/output.json | 324 --------------- schoolNewsCrawler/crawler/BaseCrawler.py | 6 +- schoolNewsCrawler/crawler/RmrbCrawler.py | 293 +++++++++++++- schoolNewsCrawler/crawler/RmrbHotPoint.py | 2 +- schoolNewsCrawler/crawler/RmrbSearch.py | 26 +- schoolNewsCrawler/crawler/RmrbTrending.py | 2 +- schoolNewsCrawler/crawler/test_crawler.ipynb | 245 ++--------- .../.bin/mysql/sql/createTableCrontab.sql | 2 + .../crontab/DataCollectionItemService.java | 10 + .../dto/crontab/TbDataCollectionItem.java | 26 ++ .../xyzh/common/vo/DataCollectionItemVO.java | 26 ++ .../DataCollectionItemController.java | 5 + .../xyzh/crontab/mapper/CrontabLogMapper.java | 9 + .../mapper/DataCollectionItemMapper.java | 10 + .../xyzh/crontab/scheduler/TaskExecutor.java | 8 +- .../impl/DataCollectionItemServiceImpl.java | 42 +- .../crontab/task/newsTask/ArticleStruct.java | 3 + .../task/newsTask/NewsCrawlerTask.java | 3 +- .../resources/mapper/CrontabLogMapper.xml | 14 + .../mapper/DataCollectionItemMapper.xml | 20 +- .../sql/add_execute_status_fields.sql | 9 + schoolNewsWeb/backend-api-implementation.md | 383 ++++++++++++++++++ schoolNewsWeb/src/apis/crontab/index.ts | 13 +- schoolNewsWeb/src/types/crontab/index.ts | 3 + .../resource/ResourceManagementView.vue | 82 +++- .../public/article/components/ArticleAdd.vue | 51 ++- 27 files changed, 1023 insertions(+), 601 deletions(-) delete mode 100644 output/out.json delete mode 100644 output/output.json create mode 100644 schoolNewsServ/sql/add_execute_status_fields.sql create mode 100644 schoolNewsWeb/backend-api-implementation.md diff --git a/output/out.json b/output/out.json deleted file mode 100644 index f1515f1..0000000 --- a/output/out.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "code": "0", - "message": "获取搜索结果失败", - "success": false, - "data": null, - "dataList": [] -} \ No newline at end of file diff --git a/output/output.json b/output/output.json deleted file mode 100644 index c943667..0000000 --- a/output/output.json +++ /dev/null @@ -1,324 +0,0 @@ -{ - "code": 0, - "message": "", - "success": true, - "data": null, - "dataList": [ - { - "title": "", - "contentRows": [], - "url": "http://cpc.people.com.cn/n1/2025/1109/c435113-40599647.html", - "publishTime": "", - "author": "", - "source": "人民网", - "category": "" - }, - { - "title": "习近平在广东考察", - "contentRows": [ - { - "tag": "p", - "content": "

" - }, - { - "tag": "img", - "content": "" - }, - { - "tag": "p", - "content": "

  11月7日至8日,中共中央总书记、国家主席、中央军委主席习近平在广东考察。这是7日下午,习近平在位于梅州市梅县区雁洋镇的叶剑英纪念馆,参观叶剑英生平事迹陈列。

" - }, - { - "tag": "p", - "content": "

  新华社记者 谢环驰 摄

" - }, - { - "tag": "img", - "content": "" - } - ], - "url": "http://pic.people.com.cn/n1/2025/1108/c426981-40599554.html", - "publishTime": "2025年11月08日17:22", - "author": "", - "source": "新华社", - "category": "" - }, - { - "title": "", - "contentRows": [], - "url": "http://cpc.people.com.cn/n1/2025/1031/c64094-40593715.html", - "publishTime": "", - "author": "", - "source": "人民网", - "category": "" - }, - { - "title": "习近平抵达韩国", - "contentRows": [ - { - "tag": "p", - "content": "

" - }, - { - "tag": "img", - "content": "" - }, - { - "tag": "p", - "content": "

当地时间十月三十日上午,国家主席习近平乘专机抵达韩国,应大韩民国总统李在明邀请,出席亚太经合组织第三十二次领导人非正式会议并对韩国进行国事访问。这是习近平抵达釜山金海国际机场时,韩国外长赵显等高级官员热情迎接。新华社记者 黄敬文摄

" - }, - { - "tag": "p", - "content": "

  本报韩国釜山10月30日电 (记者莽九晨、杨翘楚)当地时间10月30日上午,国家主席习近平乘专机抵达韩国,应大韩民国总统李在明邀请,出席亚太经合组织第三十二次领导人非正式会议并对韩国进行国事访问。

" - }, - { - "tag": "p", - "content": "

  习近平抵达釜山金海国际机场时,韩国外长赵显等高级官员热情迎接。礼兵分列红地毯两侧致敬,军乐团演奏行进乐,机场鸣放21响礼炮。

" - }, - { - "tag": "p", - "content": "

  蔡奇、王毅、何立峰等陪同人员同机抵达。

" - }, - { - "tag": "p", - "content": "

  先期抵达的香港特别行政区行政长官李家超、中国驻韩国大使戴兵也到机场迎接。

" - }, - { - "tag": "p", - "content": "

  中国留学生和中资企业代表挥舞中韩两国国旗,热烈欢迎习近平到访。

" - }, - { - "tag": "p", - "content": "

  本报北京10月30日电 10月30日上午,国家主席习近平乘专机离开北京,应大韩民国总统李在明邀请,赴韩国庆州出席亚太经合组织第三十二次领导人非正式会议并对韩国进行国事访问。

" - }, - { - "tag": "p", - "content": "

  陪同习近平出访的有:中共中央政治局常委、中央办公厅主任蔡奇,中共中央政治局委员、外交部部长王毅,中共中央政治局委员、国务院副总理何立峰等。

" - }, - { - "tag": "p", - "content": "

  《人民日报》(2025年10月31日 第01版)

" - }, - { - "tag": "img", - "content": "" - } - ], - "url": "http://korea.people.com.cn/n1/2025/1031/c407366-40594082.html", - "publishTime": "2025年10月31日13:38", - "author": "", - "source": "人民网-人民日报", - "category": "" - }, - { - "title": "习近平抵达韩国", - "contentRows": [ - { - "tag": "p", - "content": "

" - }, - { - "tag": "p", - "content": "

  当地时间十月三十日上午,国家主席习近平乘专机抵达韩国,应大韩民国总统李在明邀请,出席亚太经合组织第三十二次领导人非正式会议并对韩国进行国事访问。这是习近平抵达釜山金海国际机场时,韩国外长赵显等高级官员热情迎接。
  新华社记者 黄敬文摄

" - }, - { - "tag": "p", - "content": "

  本报韩国釜山10月30日电  (记者莽九晨、杨翘楚)当地时间10月30日上午,国家主席习近平乘专机抵达韩国,应大韩民国总统李在明邀请,出席亚太经合组织第三十二次领导人非正式会议并对韩国进行国事访问。

" - }, - { - "tag": "p", - "content": "

  习近平抵达釜山金海国际机场时,韩国外长赵显等高级官员热情迎接。礼兵分列红地毯两侧致敬,军乐团演奏行进乐,机场鸣放21响礼炮。

" - }, - { - "tag": "p", - "content": "

  蔡奇、王毅、何立峰等陪同人员同机抵达。

" - }, - { - "tag": "p", - "content": "

  先期抵达的香港特别行政区行政长官李家超、中国驻韩国大使戴兵也到机场迎接。

" - }, - { - "tag": "p", - "content": "

  中国留学生和中资企业代表挥舞中韩两国国旗,热烈欢迎习近平到访。

" - }, - { - "tag": "p", - "content": "

  本报北京10月30日电  10月30日上午,国家主席习近平乘专机离开北京,应大韩民国总统李在明邀请,赴韩国庆州出席亚太经合组织第三十二次领导人非正式会议并对韩国进行国事访问。

" - }, - { - "tag": "p", - "content": "

  陪同习近平出访的有:中共中央政治局常委、中央办公厅主任蔡奇,中共中央政治局委员、外交部部长王毅,中共中央政治局委员、国务院副总理何立峰等。

" - }, - { - "tag": "p", - "content": "

" - }, - { - "tag": "p", - "content": "

  《 人民日报 》( 2025年10月31日 01 版)

" - }, - { - "tag": "img", - "content": "" - } - ], - "url": "http://politics.people.com.cn/n1/2025/1031/c1024-40593454.html", - "publishTime": "2025年10月31日06:10", - "author": "", - "source": "人民网-人民日报", - "category": "" - }, - { - "title": "习近平回到北京", - "contentRows": [ - { - "tag": "p", - "content": "

" - }, - { - "tag": "p", - "content": "

本报北京11月1日电  11月1日晚,国家主席习近平结束出席亚太经合组织第三十二次领导人非正式会议和对韩国的国事访问后回到北京。

" - }, - { - "tag": "p", - "content": "

中共中央政治局常委、中央办公厅主任蔡奇,中共中央政治局委员、外交部部长王毅等陪同人员同机返回。

" - }, - { - "tag": "p", - "content": "

本报韩国釜山11月1日电  (记者王嵘、朱笑熺)当地时间11月1日晚,国家主席习近平结束出席亚太经合组织第三十二次领导人非正式会议和对韩国的国事访问返回北京。

" - }, - { - "tag": "p", - "content": "

离开釜山时,韩国外长赵显等高级官员到机场送行。

" - }, - { - "tag": "p", - "content": "

前往机场途中,中国留学生和中资企业代表在道路两旁挥舞中韩两国国旗,热烈祝贺习近平主席访问圆满成功。

" - }, - { - "tag": "img", - "content": "" - } - ], - "url": "http://gd.people.com.cn/n2/2025/1102/c123932-41398959.html", - "publishTime": "2025年11月02日11:15", - "author": "", - "source": "人民网-人民日报", - "category": "" - }, - { - "title": "习近平回到北京", - "contentRows": [ - { - "tag": "p", - "content": "

" - }, - { - "tag": "p", - "content": "

  本报北京11月1日电  11月1日晚,国家主席习近平结束出席亚太经合组织第三十二次领导人非正式会议和对韩国的国事访问后回到北京。

" - }, - { - "tag": "p", - "content": "

  中共中央政治局常委、中央办公厅主任蔡奇,中共中央政治局委员、外交部部长王毅等陪同人员同机返回。

" - }, - { - "tag": "p", - "content": "

  本报韩国釜山11月1日电  (记者王嵘、朱笑熺)当地时间11月1日晚,国家主席习近平结束出席亚太经合组织第三十二次领导人非正式会议和对韩国的国事访问返回北京。

" - }, - { - "tag": "p", - "content": "

  离开釜山时,韩国外长赵显等高级官员到机场送行。

" - }, - { - "tag": "p", - "content": "

  前往机场途中,中国留学生和中资企业代表在道路两旁挥舞中韩两国国旗,热烈祝贺习近平主席访问圆满成功。

" - }, - { - "tag": "p", - "content": "

" - }, - { - "tag": "p", - "content": "

  《 人民日报 》( 2025年11月02日 01 版)

" - }, - { - "tag": "img", - "content": "" - } - ], - "url": "http://politics.people.com.cn/n1/2025/1102/c1024-40594763.html", - "publishTime": "2025年11月02日05:46", - "author": "", - "source": "人民网-人民日报", - "category": "" - }, - { - "title": "", - "contentRows": [], - "url": "http://cpc.people.com.cn/n1/2025/1102/c64094-40594809.html", - "publishTime": "", - "author": "", - "source": "人民网", - "category": "" - }, - { - "title": "《习近平的文化情缘》《习近平经济思想系列讲读》在澳门启播", - "contentRows": [ - { - "tag": "p", - "content": "

" - }, - { - "tag": "p", - "content": "

人民网澳门9月28日电 (记者富子梅)《习近平的文化情缘》及《习近平经济思想系列讲读》两部专题片在澳门启播仪式28日举行。澳门特区行政长官岑浩辉,中宣部副部长、中央广播电视总台台长兼总编辑慎海雄,中央政府驻澳门特区联络办公室主任郑新聪出席活动并致辞。

" - }, - { - "tag": "img", - "content": "" - }, - { - "tag": "p", - "content": "

《习近平的文化情缘》《习近平经济思想系列讲读》澳门启播仪式。(澳门特区政府新闻局供图)

" - }, - { - "tag": "p", - "content": "

岑浩辉表示,《习近平的文化情缘》《习近平经济思想系列讲读》在澳门落地启播,高度契合澳门中西荟萃、内联外通的优势和功能,具有重大而且深远的意义。期待以此为契机,持续深化推动广大澳门同胞和海内外人士对习近平新时代中国特色社会主义思想的关注、理解和实践,共同讲好中国故事、促进国际交流、不断扩大“朋友圈”

" - }, - { - "tag": "p", - "content": "

慎海雄指出,两部精品节目是助力澳门各界更好学习领会领袖思想的一次生动实践,是让澳门居民深切感悟中华文明深厚底蕴和新时代伟大成就的一场文化盛宴。

" - }, - { - "tag": "p", - "content": "

郑新聪表示,两部精品节目在澳门播出,有力促进习近平文化思想、习近平经济思想的宣传普及、落地生根,将为澳门打造中西文明交流互鉴的重要窗口、推动经济适度多元发展提供精神动力和科学指引。

" - }, - { - "tag": "p", - "content": "

9月28日起,电视专题片《习近平的文化情缘》在澳门广播电视股份有限公司的澳视澳门频道、澳门有线电视股份有限公司互动新闻台、澳门莲花卫视传媒有限公司网站,以及《澳门日报》《大众报》《市民日报》《濠江日报》《正报》《澳门商报》《澳门焦点报》《莲花时报》等媒体的新媒体平台陆续上线。大型专题节目《习近平经济思想系列讲读》9月28日起在澳广视旗下电视频道及新媒体平台上线播出。

" - }, - { - "tag": "p", - "content": "

启播仪式后举行的“盛世莲开颂华章 - 中央广播电视总台与澳门各界深化合作仪式”上,双方代表分别交换《中央广播电视总台与澳门特别行政区政府深化战略合作框架协议》、《国家电影局与澳门特别行政区政府社会文化司关于电影产业合作框架协议》、《十五运会和残特奥会澳门赛区筹备办公室与中央广播电视总台合作意向书》、《中央广播电视总台与澳门广播电视股份有限公司关于整频道转播央视CCTV-5体育频道的协议》、《中央广播电视总台亚太总站与澳门大学深化战略合作框架协议》等5份合作文件。

" - }, - { - "tag": "img", - "content": "" - } - ], - "url": "http://gba.people.cn/n1/2025/0928/c42272-40573895.html", - "publishTime": "2025年09月28日16:44", - "author": "", - "source": "人民网-大湾区频道", - "category": "" - }, - { - "title": "", - "contentRows": [], - "url": "http://cpc.people.com.cn/n1/2025/0926/c64094-40572435.html", - "publishTime": "", - "author": "", - "source": "人民网", - "category": "" - } - ] -} \ No newline at end of file diff --git a/schoolNewsCrawler/crawler/BaseCrawler.py b/schoolNewsCrawler/crawler/BaseCrawler.py index f20d190..fe26be1 100644 --- a/schoolNewsCrawler/crawler/BaseCrawler.py +++ b/schoolNewsCrawler/crawler/BaseCrawler.py @@ -2,10 +2,10 @@ from typing import Callable, Dict, Optional, List, Any, Union from abc import ABC, abstractmethod import requests -from bs4 import BeautifulSoup +from bs4 import BeautifulSoup, NavigableString from loguru import logger from pydantic import BaseModel, Field, HttpUrl - +import json class UrlConfig(BaseModel): """URL配置数据模型""" @@ -49,6 +49,8 @@ class NewsItem(BaseModel): author: Optional[str] = Field(default=None, description="作者") source: Optional[str] = Field(default=None, description="来源") category: Optional[str] = Field(default=None, description="分类") + executeStatus: Optional[int] = Field(default=0, description="执行状态") + executeMessage: Optional[str] = Field(default=None, description="执行消息") class BaseCrawler(ABC): diff --git a/schoolNewsCrawler/crawler/RmrbCrawler.py b/schoolNewsCrawler/crawler/RmrbCrawler.py index b8f4fa7..701ee3d 100644 --- a/schoolNewsCrawler/crawler/RmrbCrawler.py +++ b/schoolNewsCrawler/crawler/RmrbCrawler.py @@ -6,12 +6,15 @@ from loguru import logger import re import chardet from datetime import datetime, timedelta - +from bs4 import NavigableString +from urllib.parse import urlparse +import json class RmrbCrawler(BaseCrawler): """人民日报新闻爬虫""" - + def __init__(self): + """初始化人民日报爬虫""" config = CrawlerConfig( base_url="http://www.people.com.cn", @@ -62,6 +65,12 @@ class RmrbCrawler(BaseCrawler): }, ) super().__init__(config) + self.detail_map = { + "gba": self.parse_base_news_detail, + "politics": self.parse_base_news_detail, + "finance": self.parse_base_news_detail, + "cpc": self.parse_cpc_news_detail, + } def search(self, key: str, total: int, news_type: int = 0) -> ResultDomain: """ @@ -104,17 +113,25 @@ class RmrbCrawler(BaseCrawler): records = response_json.get("data", {}).get("records", []) for record in records: news = self.parse_news_detail(record.get("url")) - if news['title'] == '': - news['title'] = record.get("title") - if news['contentRows'] == []: - news['contentRows'] = record.get("contentOriginal") - if news['publishTime'] == '': - news['publishTime'] = datetime.datetime.fromtimestamp(record.get("displayTime") / 1000).date() - if news['author'] == '': - news['author'] = record.get("author") - if news['source'] == '': - news['source'] = record.get("originName") - + if news.title == '': + news.title = record.get("title") + if news.contentRows == []: + # 如果contentOriginal是字符串,转换为列表格式 + content_original = record.get("contentOriginal") + if isinstance(content_original, str): + news.contentRows = [{"type": "text", "content": content_original}] + elif isinstance(content_original, list): + news.contentRows = content_original + if not news.contentRows: + news.executeStatus= 1 + news.executeMessage = "直接从接口响应获取" + if news.publishTime == '': + news.publishTime = str(datetime.fromtimestamp(record.get("displayTime", 0) / 1000).date()) + if news.author == '': + news.author = record.get("author") + if news.source == '': + news.source = record.get("originName") + news_list.append(news) else: resultDomain.code = response_json.get("code") @@ -259,6 +276,27 @@ class RmrbCrawler(BaseCrawler): return resultDomain def parse_news_detail(self, url: str) -> Optional[NewsItem]: + # 从 URL 中提取 category + netloc = urlparse(url).netloc + category = "gba" + if netloc.endswith('.people.com.cn'): + category = netloc.split('.')[0] + # 从 detail_map 中获取对应的解析函数 + print(category) + parser_func = self.detail_map.get(category) + + if parser_func is None: + logger.error(f"未找到对应解析器,category={category}, url={url}") + return NewsItem( + url=url, + executeStatus=0, + executeMessage=f"不支持的新闻类型: {category}" + ) + + # 调用对应的解析方法(注意:这些方法是实例方法,需通过 self 调用) + return parser_func(url) + + def parse_base_news_detail(self, url: str) -> Optional[NewsItem]: """ 解析人民日报新闻详情 @@ -277,10 +315,14 @@ class RmrbCrawler(BaseCrawler): publishTime="", author="", source="人民网", - category="" + category="", + executeStatus=1, + executeMessage="成功解析新闻" ) if not response: logger.error(f"获取响应失败: {url}") + news.executeStatus = 0 + news.executeMessage = f"获取响应失败: {url}" return news # BeautifulSoup 可以自动检测并解码编码,直接传入字节数据即可 @@ -288,18 +330,24 @@ class RmrbCrawler(BaseCrawler): soup = self.parse_html(response.content) if not soup: logger.error("解析HTML失败") + news.executeStatus = 0 + news.executeMessage = f"解析HTML失败" return news # 提取主内容区域 - main_div = soup.find("div", class_="layout rm_txt cf") + main_div = soup.select_one("div.layout.rm_txt.cf") if not main_div: logger.error("未找到主内容区域") + news.executeStatus = 0 + news.executeMessage = f"未找到主内容区域" return news # 提取文章区域 - article_div = main_div.find("div", class_="col col-1") + article_div = main_div.select_one("div.col.col-1") if not article_div: logger.error("未找到文章区域") + news.executeStatus = 0 + news.executeMessage = f"未找到文章区域" return news # 提取标题 @@ -380,4 +428,215 @@ class RmrbCrawler(BaseCrawler): except Exception as e: logger.error(f"解析新闻详情失败 [{url}]: {str(e)}") - return None \ No newline at end of file + news.executeStatus = 0 + news.executeMessage = f"解析新闻详情失败: {str(e)}" + return news + + def parse_cpc_news_detail(self, url: str) -> Optional[NewsItem]: + """ + 解析人民日报新闻详情 + """ + try: + response = self.fetch(url) + news = NewsItem( + title="", + contentRows=[], # 修复:使用 contents 而不是 content + url=url, + publishTime="", + author="", + source="人民网", + category="", + executeStatus=1, + executeMessage="成功解析新闻" + ) + if not response: + logger.error(f"获取响应失败: {url}") + news.executeStatus = 0 + news.executeMessage = f"获取响应失败: {url}" + return news + + # BeautifulSoup 可以自动检测并解码编码,直接传入字节数据即可 + # 它会从 HTML 的 标签或响应头自动检测编码 + soup = self.parse_html(response.content) + if not soup: + logger.error("解析HTML失败") + news.executeStatus = 0 + news.executeMessage = f"解析HTML失败" + return news + + # 提取主内容区域 + main_div = soup.select_one("div.text_con.text_con01") + if not main_div: + logger.error("未找到主内容区域") + news.executeStatus = 0 + news.executeMessage = f"未找到主内容区域" + return news + + # 提取文章区域 + article_div = main_div.select_one("div.text_c") + if not article_div: + logger.error("未找到文章区域") + news.executeStatus = 0 + news.executeMessage = f"未找到文章区域" + return news + + # 提取标题 + title_tag = article_div.select_one("h1") + title = title_tag.get_text(strip=True) if title_tag else "" + + # 提取作者 + author_tag = article_div.select_one("div.author.cf") + author = author_tag.get_text(strip=True) if author_tag else "" + + # 提取发布时间和来源 + channel_div = article_div.select_one("div.sou") + publish_time = "" + source = "" + + if channel_div: + # 提取时间:取第一个非空文本节点 + for child in channel_div.children: + if isinstance(child, str) and child.strip(): + publish_time = child.strip().split("来源:")[0].strip() + break + + # 提取来源 + a_tag = channel_div.find("a") + source = a_tag.get_text(strip=True) if a_tag else "" + + # 清理不可见空格 + publish_time = publish_time.replace("\xa0", " ").replace(" ", " ").strip() + + # 提取内容 + content_div = article_div.select_one('div.show_text') + contents = [] # 构建一个富文本内容 + pList = content_div.find_all("p") # 所有p标签 + # 解析p标签 变为quill富文本 + + # 遍历 show_text 下的所有直接子节点(保持顺序) + for child in content_div.children: + # 跳过纯文本节点(如换行、空格) + if isinstance(child, NavigableString): + continue + + tag_name = child.name + if tag_name is None: + continue + + # 情况1:检测是否是视频容器(根据 id 特征或内部结构) + video_tag = child.find('video') if tag_name != 'video' else child + if video_tag and video_tag.get('src'): + src = str(video_tag['src']) + p_style = video_tag.get("style", "") + if not src.startswith("http"): + src = self.config.base_url + src + contents.append({ + "tag": "video", + "content": f"" + }) + continue + img_tag = child.find('img') if tag_name != 'img' else child + if img_tag and img_tag.get('src'): + src = str(img_tag['src']) + p_style = child.get("style", "") + + if not src.startswith("http"): + src = self.config.base_url + src + contents.append({ + "tag": "img", + "content": f"" + }) + continue + + if tag_name == 'p': + p_style = child.get("style", "") + img_tag = child.find('img') + video_tag = child.find('video') + + # 情况1:存在