当前位置: 首页 > article >正文

新闻发布时间抽取分析

前言

gne是一款对新闻网页(主要针对中文网页)的信息抽取Python库github仓库。本文基于gne进行新闻发布时间抽取实验,并对gne抽取规则进行完善,同时补充实现了基于JSON-LD数据的时间抽取,提高了抽取的召回率和准确率。

1. 统计结果

主要抽取方法:
① gne原版meta规则://meta/@name 前缀匹配
② gne修改meta规则://meta/@name 后缀匹配
③ gne原版文本抽取:主要支持中文日期和纯数字日期
④ gne添加抽取规则:添加英文规则
⑤ JSON-LD抽取规则
在这里插入图片描述

2. gne源码分析

gne项目采用了模块化设计,源码较为简单。主要组件包括:
① MetaExtractor 元数据抽取,对应meta信息提取
② TitleExtractor 标题抽取
③ AuthorExtractor 作者抽取
④ TimeExtractor 发布时间抽取
⑤ ContentExtractor 正文抽取
⑥ ListExtractor 列表内容抽取

其中TimeExtractor时间抽取逻辑为:用户xpath匹配、meta标签xpath匹配、文本正则匹配。核心代码如下:

    def extractor(self, element: HtmlElement, publish_time_xpath: str = '') -> str:
        publish_time_xpath = publish_time_xpath or config.get('publish_time', {}).get('xpath')
        publish_time = (self.extract_from_user_xpath(publish_time_xpath, element)  # 用户指定的 Xpath 是第一优先级
                        or self.extract_from_meta(element)   # 第二优先级从 Meta 中提取
                        or self.extract_from_text(element))  # 最坏的情况从正文中提取
        return publish_time

其抽取规则统一配置在defaults.py文件中。

3.抽取方法

3.1.基于标签的时间抽取

原版gne基于<meta>标签的名字属性进行前缀匹配,xpath规则如下:

    '//meta[starts-with(@property, "rnews:datePublished")]/@content',
    '//meta[starts-with(@property, "article:published_time")]/@content',
    '//meta[starts-with(@property, "og:published_time")]/@content',
    '//meta[starts-with(@property, "og:release_date")]/@content',
    '//meta[starts-with(@itemprop, "datePublished")]/@content',
    '//meta[starts-with(@itemprop, "dateUpdate")]/@content',
    '//meta[starts-with(@name, "OriginalPublicationDate")]/@content',
    '//meta[starts-with(@name, "article_date_original")]/@content',
    '//meta[starts-with(@name, "og:time")]/@content',
    '//meta[starts-with(@name, "apub:time")]/@content',
    '//meta[starts-with(@name, "publication_date")]/@content',
    '//meta[starts-with(@name, "sailthru.date")]/@content',
    '//meta[starts-with(@name, "PublishDate")]/@content',
    '//meta[starts-with(@name, "publishdate")]/@content',
    '//meta[starts-with(@name, "PubDate")]/@content',
    '//meta[starts-with(@name, "pubtime")]/@content',
    '//meta[starts-with(@name, "_pubtime")]/@content',
    '//meta[starts-with(@name, "weibo: article:create_at")]/@content',
    '//meta[starts-with(@pubdate, "pubdate")]/@content'

可以看出大多数围绕publish-date/time的同义词或变体词在进行。根本问题在于<meta>标签没有统一规范造成的问题。

经过实验发现,采用后缀匹配方式能够匹配更多,且规则配置上更加简单。
设计如下规则:

["property","datePublished"],
["property","published_time"],
["property","release_date"],
["itemprop","datePublished"],
["itemprop","dateUpdate"],
["name","OriginalPublicationDate"],
["name","article_date_original"],
["name","publication_date"],
["name","PublishDate"],
["name","publishdate"],
["name","PubDate"],
["name","pubtime"],
["name","pubdate"],
["name","create_at"],
["name","date"],
["name","time"]

每条规则为一个数组,第一项表示meta标签的属性名称,第二项表示该属性值应匹配的后缀。可以看出来,规则顺序是从精确到宽泛。

由于gne库使用的lxml库不支持XPATH 2.0,不支持ends-with语法,所以通过代码实现:

    def extract_from_meta_end(self, element: HtmlElement) -> str:
        for prop, ends in META_NAME_ENDS.items():
            tags = element.xpath(f"//meta[@{prop}]")
            if tags:
                for tag in tags:
                    prop_val = tag.get(prop)
                    for end in ends:
                        if prop_val.endswith(end):
                            return tag.get("content")
        return ''

通过修改后的meta匹配规则,召回率约为57.9%

3.2.基于ld+json元数据的时间抽取

原版gne组件不支持JSON-LD(即scripttype属性为"application/ld+json")元数据的抽取,增加相关逻辑进行处理。

首先,需要查找全部类型“application/ld+json”的script标签,可能有多个。对于每个script标签需要先去掉一些不规范的JSON字符;获取的对象可能是数组,也可能是dict,最后都转成dict的数组。

    def get_ld_data(self):
        all_ld = []
        scripts = self.soup.find_all('script', type='application/ld+json')
        for script in scripts:
            # 提取script标签中的内容
            json_content = script.get_text().strip()
            # 移除json中不合法的字符
            json_content = re.sub(r'[\n\r\t]+', '', json_content).strip()
            if not json_content:
                continue
            try:
                data = json.loads(json_content)
                if isinstance(data, list):
                    all_ld.extend([v for v in data if isinstance(v, dict)])
                elif isinstance(data, dict):
                    all_ld.append(data)
            except:
                traceback.print_exc()
                print("invalid application/ld+json", json_content)
        return all_ld

进行属性查找时,需要兼容多种情况:顶层对象的字段;是某个下层对象字段的字段,逻辑如下所示:

    def find_value_from_ld(self, key: str):
        if not self.ld_data:
            return None
        for row in self.ld_data:
            if row.get('@type') in ['NewsArticle', 'Article', 'WebPage']:
                if key in row:
                    return row[key]
            else:
                r = self.deep_search(row, key)
                if r:
                    return r
        return None

注意:通过@type属性指定元数据类型,通常日期在NewsArticleArticle对象中。
深度查找逻辑如下:

    def deep_search(self, row: dict, key: str):
        if key in row:
            return row[key]
        for val in row.values():
            if isinstance(val, dict):
                r = self.deep_search(val, key)
                if r:
                    return r
            elif isinstance(val, list):
                for v in val:
                    if isinstance(v, dict):
                        r = self.deep_search(v, key)
                        if r:
                            return r
        return None

基于上述方法大约**71.9%的网页存在ld+json标签,大约63.7%**的网页成功获取了时间。可能还有一定的优化空间,例如可能有的字段名不叫“datePublished”或者还有其他类型的@type值。

进一步优化策略:ld+json无效json字符串处理;完善抽取规则。

下一步考虑将这部分抽取逻辑融合到gne组件中。

3.3.基于文本内容正则匹配的时间抽取

从统计结果可以看出,如果仅使用元数据,召回率为**76.3%**左右,因此可以看出仍然有大量网页需要考虑其他方法抽取时间。最直观的一个位置就是网页的文本内容,大部分网页在标题下面或正文结束后会有专门的块展示文章发布时间。

gne组件通过一组正则表达式对网页文本内容(正则表达式:.//text())进行依次匹配,一旦匹配即返回结果。规则如下:

DATETIME_PATTERN = [
    "(\d{4}[-|/|.]\d{1,2}[-|/|.]\d{1,2}\s*?[0-1]?[0-9]:[0-5]?[0-9]:[0-5]?[0-9])",
    "(\d{4}[-|/|.]\d{1,2}[-|/|.]\d{1,2}\s*?[2][0-3]:[0-5]?[0-9]:[0-5]?[0-9])",
    "(\d{4}[-|/|.]\d{1,2}[-|/|.]\d{1,2}\s*?[0-1]?[0-9]:[0-5]?[0-9])",
    "(\d{4}[-|/|.]\d{1,2}[-|/|.]\d{1,2}\s*?[2][0-3]:[0-5]?[0-9])",
    "(\d{4}[-|/|.]\d{1,2}[-|/|.]\d{1,2}\s*?[1-24]\d时[0-60]\d分)([1-24]\d时)",
    "(\d{2}[-|/|.]\d{1,2}[-|/|.]\d{1,2}\s*?[0-1]?[0-9]:[0-5]?[0-9]:[0-5]?[0-9])",
    "(\d{2}[-|/|.]\d{1,2}[-|/|.]\d{1,2}\s*?[2][0-3]:[0-5]?[0-9]:[0-5]?[0-9])",
    "(\d{2}[-|/|.]\d{1,2}[-|/|.]\d{1,2}\s*?[0-1]?[0-9]:[0-5]?[0-9])",
    "(\d{2}[-|/|.]\d{1,2}[-|/|.]\d{1,2}\s*?[2][0-3]:[0-5]?[0-9])",
    "(\d{2}[-|/|.]\d{1,2}[-|/|.]\d{1,2}\s*?[1-24]\d时[0-60]\d分)([1-24]\d时)",
    "(\d{4}年\d{1,2}月\d{1,2}日\s*?[0-1]?[0-9]:[0-5]?[0-9]:[0-5]?[0-9])",
    "(\d{4}年\d{1,2}月\d{1,2}日\s*?[2][0-3]:[0-5]?[0-9]:[0-5]?[0-9])",
    "(\d{4}年\d{1,2}月\d{1,2}日\s*?[0-1]?[0-9]:[0-5]?[0-9])",
    "(\d{4}年\d{1,2}月\d{1,2}日\s*?[2][0-3]:[0-5]?[0-9])",
    "(\d{4}年\d{1,2}月\d{1,2}日\s*?[1-24]\d时[0-60]\d分)([1-24]\d时)",
    "(\d{2}年\d{1,2}月\d{1,2}日\s*?[0-1]?[0-9]:[0-5]?[0-9]:[0-5]?[0-9])",
    "(\d{2}年\d{1,2}月\d{1,2}日\s*?[2][0-3]:[0-5]?[0-9]:[0-5]?[0-9])",
    "(\d{2}年\d{1,2}月\d{1,2}日\s*?[0-1]?[0-9]:[0-5]?[0-9])",
    "(\d{2}年\d{1,2}月\d{1,2}日\s*?[2][0-3]:[0-5]?[0-9])",
    "(\d{2}年\d{1,2}月\d{1,2}日\s*?[1-24]\d时[0-60]\d分)([1-24]\d时)",
    "(\d{1,2}月\d{1,2}日\s*?[0-1]?[0-9]:[0-5]?[0-9]:[0-5]?[0-9])",
    "(\d{1,2}月\d{1,2}日\s*?[2][0-3]:[0-5]?[0-9]:[0-5]?[0-9])",
    "(\d{1,2}月\d{1,2}日\s*?[0-1]?[0-9]:[0-5]?[0-9])",
    "(\d{1,2}月\d{1,2}日\s*?[2][0-3]:[0-5]?[0-9])",
    "(\d{1,2}月\d{1,2}日\s*?[1-24]\d时[0-60]\d分)([1-24]\d时)",
    "(\d{4}[-|/|.]\d{1,2}[-|/|.]\d{1,2})",
    "(\d{2}[-|/|.]\d{1,2}[-|/|.]\d{1,2})",
    "(\d{4}年\d{1,2}月\d{1,2}日)",
    "(\d{2}年\d{1,2}月\d{1,2}日)",
    "(\d{1,2}月\d{1,2}日)"
]

可以看出,上述规则覆盖了中文和纯数字的日期+时间与日期。对于英文网页中常见的格式如March 14, 202514 Mar 2025则不支持。本次测试选择的1000篇新闻大多数是英文,根据统计结果,基于上述规则的召回率为86.1%。通过增加常见的英文日期格式,召回率提高到95%。增加规则如下所示:

DATETIME_PATTERN = [
r"((?:January|February|March|April|May|June|July|August|September|October|November|December|Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{1,2},?\s+\d{4})", 
r"(\d{1,2}\s+(?:January|February|March|April|May|June|July|August|September|October|November|December|Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{4})",
]

上述规则同时处理了英文月份的全称和缩写以及月份与日的两种前后顺序。

存在问题:(1)基于.//text() 方法,传入整个html可能导致匹配到stylescript等非正文节点中的字符串;(2)规则不完整或过于宽泛。

3.4.三种方法总结

通过组合三种抽取方法,时间抽取召回率达到95.3%,而单独使用三种方法的召回率分别为57.9%63.7%95%。虽然基于文本内容规则匹配的方法的召回率非常接近最优,但是由于新闻文本为非结构化数据,且可能存在日期,其相比网页元数据有更大概率为文章发布时间。例如,文本中抽取的日期可能是事件日期或其他故事的日期。考虑到抽取结果的准确率,推荐方案是按照meta标签、ld+json、文本内容正则匹配的顺序进行抽取。

4.结论

本文通过对gne组件源码进行分析,了解其时间抽取的规则,通过分析总结网页,对其基于meta元数据的时间抽取和基于文本内容正则匹配的时间抽取计算所及和配置规则进行完善和补充,提高了时间抽取召回率。


http://www.kler.cn/a/590339.html

相关文章:

  • LinkedList和链表
  • 【MySQL】从零开始:掌握MySQL数据库的核心概念
  • containerd 拉取镜像的工具以及优劣
  • 系统架构设计师—案例分析—架构评估
  • LLM论文笔记 24: A Theory for Length Generalization in Learning to Reason
  • QT非UI设计器生成界面的国际化
  • Java 买百鸡问题
  • Google内购 Java服务端(Springboot)校验订单详细流程
  • 日志存储与分析
  • [贪心算法] 摆动序列
  • Matlab 汽车ABS实现模糊pid和pid控制
  • JVM垃圾回收器全面解析:从核心概念到选型指南
  • Matlab 经验模态分解和时频图绘制
  • 结构型模式之外观模式:让复杂系统变简单的利器
  • golang中的结构体
  • FlowGram 简介:开源前端流程搭建引擎
  • iPaaS集成平台轻量化架构的重要性
  • 国际数字影像产业园,超甲级地标招商进行中​
  • 重生之我在学Vue--第17天 Vue 3 项目优化与部署
  • 本地部署github上资源可能出现问题总结