Newspaper3k源码解析:架构设计与核心算法
Newspaper3k是一个专业的新闻文章提取库,采用清晰的分层架构设计,将复杂的网页内容提取任务分解为多个职责明确的模块。整个项目架构体现了高内聚、低耦合的软件工程原则,主要分为API接口层、核心业务层、数据处理层和基础工具层四个主要层次。核心模块包括API接口模块、Article类、Source类、提取器模块、清理器模块、NLP模块、解析器模块、网络模块、URL处理模块、多线程处理模块和配置管理模块。项目广泛应用了工厂模式、策略模式、观察者模式和模板方法等多种设计模式,并通过模块化的语言支持设计实现了多语言处理能力。
项目架构与模块划分分析
Newspaper3k作为一个专业的新闻文章提取库,采用了清晰的分层架构设计,将复杂的网页内容提取任务分解为多个职责明确的模块。整个项目的架构设计体现了高内聚、低耦合的软件工程原则,使得各个模块可以独立开发和测试。
核心架构层次
Newspaper3k的架构可以分为四个主要层次:
模块详细划分
1. API接口模块 (api.py)
作为整个库的入口点,提供简洁的面向用户接口:
# 主要API函数
def build(url='', dry=False, config=None, **kwargs) -> Source
def build_article(url='', config=None, **kwargs) -> Article
def fulltext(html, language='en') -> str
def languages() -> list
2. 核心业务模块
Article类 (article.py)
负责单篇文章的完整生命周期管理:
Source类 (source.py)
处理整个新闻源的多篇文章批量操作:
class Source:
def __init__(self, url, config=None):
self.url = url
self.config = config
self.articles = [] # 文章列表
self.categories = [] # 分类URL
self.feeds = [] # Feed URL
def build(self):
"""构建新闻源"""
def download_articles(self, threads=1):
"""多线程下载文章"""
def parse_articles(self):
"""批量解析文章"""
3. 数据处理模块
提取器模块 (extractors.py)
包含各种内容提取算法:
| 提取功能 | 方法名 | 描述 |
|---|---|---|
| 作者提取 | get_authors() | 从byline和meta标签提取作者 |
| 发布日期 | get_publishing_date() | 解析文章发布日期 |
| 标题提取 | get_title() | 提取和清理文章标题 |
| 元数据 | get_meta_*() | 获取各种meta信息 |
| 内容评分 | calculate_best_node() | 计算最佳内容节点 |
清理器模块 (cleaners.py)
负责HTML内容的清理和标准化:
class Cleaners:
def clean(self, doc_to_clean):
"""主清理方法"""
def clean_body_classes(self, doc):
"""清理body类名"""
def remove_scripts_styles(self, doc):
"""移除脚本和样式"""
def clean_bad_tags(self, doc):
"""清理不良标签"""
NLP模块 (nlp.py)
提供自然语言处理功能:
4. 基础工具模块
解析器模块 (parsers.py)
基于lxml的HTML解析工具集,提供XPath和CSS选择器支持:
class Parser:
@classmethod
def css_select(cls, node, selector):
"""CSS选择器查询"""
@classmethod
def get_elements_by_tag(cls, node, tag=None, attr=None, value=None):
"""按标签名和属性查询"""
@classmethod
def clean_article_html(cls, node):
"""清理文章HTML"""
网络模块 (network.py)
处理HTTP请求和响应:
def get_html(url, config=None, response=None):
"""获取HTML内容"""
def get_request_kwargs(timeout, useragent, proxies, headers):
"""构建请求参数"""
def multithread_request(urls, config=None):
"""多线程请求处理"""
URL处理模块 (urls.py)
提供URL标准化和验证功能:
| 功能 | 方法 | 描述 |
|---|---|---|
| URL清理 | remove_args() | 移除URL参数 |
| 域名提取 | get_domain() | 提取主域名 |
| URL验证 | valid_url() | 验证URL有效性 |
| 重定向处理 | redirect_back() | 处理重定向URL |
5. 多线程处理模块 (mthreading.py)
实现高效的多线程下载框架:
class NewsPool:
def __init__(self, num_threads=10, timeout_seconds=1):
self.num_threads = num_threads
self.timeout = timeout_seconds
def add_task(self, func, *args, **kwargs):
"""添加下载任务"""
def wait_completion(self):
"""等待所有任务完成"""
6. 配置管理模块 (configuration.py)
集中管理所有配置参数:
class Configuration:
def __init__(self):
self.MIN_WORD_COUNT = 300 # 最小词数
self.MIN_SENT_COUNT = 7 # 最小句子数
self.MAX_TITLE = 200 # 标题最大长度
self.fetch_images = True # 是否获取图片
self.language = 'en' # 默认语言
self.number_threads = 10 # 线程数
模块间协作关系
Newspaper3k的模块间采用清晰的依赖关系:
设计模式应用
Newspaper3k中广泛应用了多种设计模式:
- 工厂模式:
api.build()和api.build_article()作为工厂方法 - 策略模式: 不同语言的停用词处理采用策略模式
- 观察者模式: 多线程下载中的任务管理
- 模板方法: 文章处理流程的标准化
国际化支持架构
项目通过模块化的语言支持设计,实现了多语言处理能力:
这种架构设计使得Newspaper3k能够高效、准确地处理各种语言的新闻内容,同时保持了代码的可维护性和扩展性。每个模块都有明确的职责边界,便于单独测试和优化,体现了优秀的软件架构设计思想。
HTML解析器与内容提取算法
Newspaper3k的HTML解析与内容提取是其核心功能之一,它通过精心设计的算法从复杂的网页结构中准确提取文章正文内容。该系统的设计借鉴了python-goose项目的优秀思想,并结合现代网页结构特点进行了优化。
解析器架构设计
Newspaper3k采用分层架构设计,将HTML解析与内容提取逻辑分离:
Parser解析层
Parser类基于lxml库构建,提供统一的DOM操作接口:
class Parser(object):
@classmethod
def fromstring(cls, html):
# 处理编码问题并转换为lxml文档对象
html = cls.get_unicode_html(html)
return lxml.html.fromstring(html)
@classmethod
def getElementsByTag(cls, node, tag=None, attr=None, value=None):
# 支持属性筛选的XPath查询
selector = 'descendant-or-self::%s' % (tag or '*')
if attr and value:
selector = '%s[contains(@%s, "%s")]' % (selector, attr, value)
return node.xpath(selector)
内容清理策略
DocumentCleaner负责移除无关的HTML元素,采用多重清理策略:
| 清理类型 | 目标元素 | 清理方法 |
|---|---|---|
| 脚本样式 | script, style | 直接移除 |
| 广告相关 | 特定class/id | 正则匹配移除 |
| 社交媒体 | facebook, twitter | 特征匹配移除 |
| 空标签 | 无内容元素 | 深度遍历移除 |
def clean(self, doc_to_clean):
"""多层清理流水线"""
doc_to_clean = self.clean_body_classes(doc_to_clean)
doc_to_clean = self.remove_scripts_styles(doc_to_clean)
doc_to_clean = self.clean_bad_tags(doc_to_clean)
doc_to_clean = self.remove_nodes_regex(doc_to_clean, self.caption_re)
return doc_to_clean
核心提取算法
ContentExtractor采用基于权重的重力评分算法(Gravity Score Algorithm)来识别文章主体内容。
算法流程
1. 候选节点筛选
算法首先筛选潜在的文本容器节点:
def nodes_to_check(self, doc):
"""返回需要检查的节点列表"""
nodes_to_check = []
for tag in ['p', 'pre', 'td']: # 段落、预格式文本、表格单元格
items = self.parser.getElementsByTag(doc, tag=tag)
nodes_to_check += items
return nodes_to_check
2. 文本质量评估
每个节点通过停用词密度和链接密度进行评估:
def is_highlink_density(self, e):
"""检查链接密度,高链接密度通常表示导航或广告内容"""
links = self.parser.getElementsByTag(e, tag='a')
if not links:
return False
text = self.parser.getText(e)
words = [word for word in text.split() if word.isalnum()]
if not words:
return True
words_number = float(len(words))
link_text = ''.join(self.parser.getText(link) for link in links)
num_link_words = float(len(link_text.split()))
num_links = float(len(links))
link_divisor = num_link_words / words_number
score = link_divisor * num_links
return score >= 1.0
3. 重力评分计算
核心算法通过多维度计算每个节点的权重:
def calculate_best_node(self, doc):
top_node = None
nodes_to_check = self.nodes_to_check(doc)
starting_boost = 1.0
parent_nodes = []
nodes_with_text = []
# 第一阶段:初步筛选
for node in nodes_to_check:
text_node = self.parser.getText(node)
word_stats = self.stopwords_class.get_stopword_count(text_node)
high_link_density = self.is_highlink_density(node)
if word_stats.get_stopword_count() > 2 and not high_link_density:
nodes_with_text.append(node)
# 第二阶段:评分计算
for i, node in enumerate(nodes_with_text):
boost_score = 0.0
# 可提升节点加分
if self.is_boostable(node):
boost_score = (1.0 / starting_boost) * 50
starting_boost += 1
# 末尾节点减分(通常是非内容区域)
if len(nodes_with_text) > 15:
if (len(nodes_with_text) - i) <= len(nodes_with_text) * 0.25:
booster = float(len(nodes_with_text) * 0.25 - (len(nodes_with_text) - i))
boost_score = -pow(booster, 2.0)
# 综合评分
text_node = self.parser.getText(node)
word_stats = self.stopwords_class.get_stopword_count(text_node)
upscore = int(word_stats.get_stopword_count() + boost_score)
# 向父节点传播分数
parent_node = self.parser.getParent(node)
self.update_score(parent_node, upscore)
self.update_node_count(parent_node, 1)
if parent_node not in parent_nodes:
parent_nodes.append(parent_node)
# 第三阶段:选择最高分节点
top_node_score = 0
for node in parent_nodes:
score = self.get_score(node)
if score > top_node_score:
top_node = node
top_node_score = score
return top_node
智能提升机制
算法包含智能提升逻辑,识别真正的内容段落:
def is_boostable(self, node):
"""判断节点是否应该被提升评分"""
para = "p"
steps_away = 0
minimum_stopword_count = 5
max_stepsaway_from_node = 3
# 检查相邻段落
nodes = self.walk_siblings(node)
for current_node in nodes:
if self.parser.getTag(current_node) == para:
if steps_away >= max_stepsaway_from_node:
return False
paragraph_text = self.parser.getText(current_node)
word_stats = self.stopwords_class.get_stopword_count(paragraph_text)
if word_stats.get_stopword_count() > minimum_stopword_count:
return True
steps_away += 1
return False
输出格式化
OutputFormatter负责将选定的DOM节点转换为纯净文本:
def get_formatted(self, top_node):
"""返回格式化后的文本和HTML"""
self.top_node = top_node
# 多重清理和转换
self.remove_negativescores_nodes()
self.links_to_text()
self.add_newline_to_br()
self.add_newline_to_li()
self.replace_with_text()
self.remove_empty_tags()
text = self.convert_to_text()
html = self.convert_to_html() if self.config.keep_article_html else ''
return (text, html)
算法优势与特点
- 多语言支持:通过停用词统计适配不同语言的内容特征
- 抗噪能力强:有效处理网页中的广告、导航、侧边栏等噪声内容
- 结构感知:理解HTML文档结构,通过父节点聚合提高准确性
- 可配置性:提供丰富的配置选项适应不同网站结构
性能优化策略
- 懒加载设计:仅在需要时进行完整的解析和提取
- 内存管理:及时释放DOM对象避免内存泄漏
- 缓存机制:对重复操作进行缓存优化性能
该内容提取算法经过大量实际网页测试,在准确性和效率之间取得了良好平衡,能够处理大多数新闻网站的文章提取需求。
图像识别与评分机制实现
Newspaper3k的图像识别与评分系统是其内容提取架构中的关键组成部分,专门用于从网页中智能识别和选择最具代表性的主图。该系统基于多维度评分算法,综合考虑图像尺寸、宽高比、内容熵值等多个因素,确保选择出最符合文章主题的高质量图片。
图像提取架构设计
Newspaper3k的图像处理模块采用分层架构设计,主要包含以下几个核心组件:
核心评分算法实现
图像评分机制的核心在于calculate_area方法,该方法对每个候选图像进行多维度评估:
def calculate_area(self, img_url, dimension):
if not dimension:
return 0
area = dimension[0] * dimension[1]
# 忽略过小的图像
if area < minimal_area:
log.debug('ignore little %s' % img_url)
return 0
# PIL不会放大图像,设置最小宽度并保持宽高比
if dimension[0] < thumbnail_size[0]:
return 0
# 忽略过长/过宽的图像
current_ratio = max(dimension) / min(dimension)
if current_ratio > self.config.image_dimension_ration:
log.debug('ignore dims %s' % img_url)
return 0
# 对包含"sprite"或"logo"的文件名进行惩罚
lower_case_url = img_url.lower()
if 'sprite' in lower_case_url or 'logo' in lower_case_url:
log.debug('penalizing sprite %s' % img_url)
area /= 10
return area
图像评分维度分析
Newspaper3k的图像评分系统基于以下关键维度进行综合评估:
| 评分维度 | 权重 | 说明 | 处理逻辑 |
|---|---|---|---|
| 图像面积 | 高 | 像素总面积 | 必须大于最小面积阈值(5000像素) |
| 宽度限制 | 高 | 最小宽度要求 | 必须大于缩略图宽度(90像素) |
| 宽高比 | 中 | 图像比例合理性 | 不能超过配置的最大宽高比(16:9) |
| 文件名 | 低 | 图像内容提示 | 包含"sprite"或"logo"的文件名会被惩罚 |
图像熵值计算与裁剪优化
系统采用信息熵算法来优化图像裁剪过程,确保保留最重要的视觉内容:
def image_entropy(img):
"""计算图像的熵值"""
hist = img.histogram()
hist_size = sum(hist)
hist = [float(h) / hist_size for h in hist]
return -sum([p * math.log(p, 2) for p in hist if p != 0])
def square_image(img):
"""将图像裁剪为正方形,基于熵值选择保留区域"""
x, y = img.size
while y > x:
# 每次切片10像素直到变为正方形
slice_height = min(y - x, 10)
bottom = img.crop((0, y - slice_height, x, y))
top = img.crop((0, 0, x, slice_height))
# 移除熵值较小的切片
if image_entropy(bottom) < image_entropy(top):
img = img.crop((0, 0, x, y - slice_height))
else:
img = img.crop((0, slice_height, x, y))
x, y = img.size
return img
图像获取与预处理流程
图像获取过程采用智能重试机制和内容类型验证:
def fetch_url(url, useragent, referer=None, retries=1, dimension=False):
cur_try = 0
nothing = None if dimension else (None, None)
url = clean_url(url)
while True:
try:
response = requests.get(url, stream=True, timeout=5, headers={
'User-Agent': useragent,
'Referer': referer,
})
# 智能内容类型验证
content_type = response.headers.get('Content-Type')
if not content_type or 'image' not in content_type:
return nothing
# 使用PIL解析器逐步读取图像数据
p = ImageFile.Parser()
content = response.raw.read(chunk_size)
while not p.image and content:
p.feed(content)
content = response.raw.read(chunk_size)
return p.image.size if dimension else (content_type, content)
except requests.exceptions.RequestException:
cur_try += 1
if cur_try >= retries:
return nothing
性能优化策略
Newspaper3k在图像处理方面采用了多项性能优化措施:
- 渐进式加载:使用流式读取,避免一次性加载大图像
- 维度优先获取:首先只获取图像尺寸信息,减少网络传输
- 智能重试机制:对网络请求失败进行自动重试
- 内存优化:及时关闭网络连接,避免资源泄漏
配置参数详解
系统提供了灵活的配置选项来控制图像处理行为:
# 最小图像面积阈值
minimal_area = 5000
# 缩略图目标尺寸
thumbnail_size = 90, 90
# 默认宽高比限制
image_dimension_ration = 16 / 9.0
# 是否获取图像配置
fetch_images = True
这种基于多维度评分的图像选择机制,确保了Newspaper3k能够从网页中智能识别出最具代表性和高质量的主图像,为内容提取提供了重要的视觉支持。
多语言处理与分词技术
Newspaper3k作为一个强大的新闻文章提取库,其多语言处理能力是其核心优势之一。该库支持超过40种语言的文章提取和处理,从英语、中文到阿拉伯语、日语等,每种语言都有专门的分词和停用词处理机制。这种多语言支持使得Newspaper3k能够处理全球范围内的新闻内容,为国际化应用提供了强有力的技术基础。
语言检测与配置机制
Newspaper3k采用智能的语言检测机制,当用户未明确指定语言时,系统会自动检测文章的语言类型。语言配置通过configuration.py模块进行管理:
class Configuration(object):
def __init__(self):
self.language = 'en' # 默认英语
def get_language(self):
return self.language
def set_language(self, language):
self.language = language
语言检测流程如下:
停用词管理系统
Newspaper3k为每种支持的语言都提供了专门的停用词列表,这些停用词文件存储在text/stopwords-{language}.txt中。停用词管理通过StopWords基类和语言特定的子类实现:
class StopWords(object):
_cached_stop_words = {}
def __init__(self, language='en'):
if language not in self._cached_stop_words:
path = os.path.join('text', 'stopwords-%s.txt' % language)
self._cached_stop_words[language] = \
set(FileHelper.loadResourceFile(path).splitlines())
self.STOP_WORDS = self._cached_stop_words[language]
多语言分词技术实现
不同语言的分词技术存在显著差异,Newspaper3k为每种语言实现了专门的分词策略:
中文分词(使用jieba)
中文分词采用jieba库,支持全模式和精确模式:
class StopWordsChinese(StopWords):
def candidate_words(self, stripped_input):
import jieba
return jieba.cut(stripped_input, cut_all=True)
阿拉伯语分词(使用NLTK)
阿拉伯语处理涉及特殊的词干提取和分词:
class StopWordsArabic(StopWords):
def remove_punctuation(self, content):
return content # 阿拉伯语保留标点
def candidate_words(self, stripped_input):
import nltk
s = nltk.stem.isri.ISRIStemmer()
words = []
for word in nltk.tokenize.wordpunct_tokenize(stripped_input):
words.append(s.stem(word)) # 应用ISRI词干提取
return words
日语分词(使用TinySegmenter)
日语分词采用轻量级的TinySegmenter:
class StopWordsJapanese(StopWords):
def candidate_words(self, stripped_input):
import tinysegmenter
segmenter = tinysegmenter.TinySegmenter()
tokens = segmenter.tokenize(stripped_input)
return tokens
泰语分词(使用PyThaiNLP)
泰语分词使用专门的PyThaiNLP库:
class StopWordsThai(StopWords):
def candidate_words(self, stripped_input):
import pythainlp
tokens = pythainlp.word_tokenize(stripped_input)
return tokens
语言特定处理策略对比
下表展示了不同语言的处理策略差异:
| 语言 | 分词库 | 标点处理 | 词干提取 | 特殊处理 |
|---|---|---|---|---|
| 英语 | 内置 | 移除标点 | 无 | 标准空格分词 |
| 中文 | jieba | 保留中文标点 | 无 | 基于词典的分词 |
| 阿拉伯语 | NLTK | 保留标点 | ISRI词干提取 | 从右到左处理 |
| 日语 | TinySegmenter | 保留标点 | 无 | 基于规则的分词 |
| 泰语 | PyThaiNLP | 保留标点 | 无 | 基于机器学习的分词 |
自然语言处理管道
多语言NLP处理遵循统一的管道架构:
性能优化策略
Newspaper3k在多语言处理中采用了多种性能优化策略:
- 缓存机制:停用词列表在内存中缓存,避免重复文件读取
- 延迟加载:分词库只在需要时导入,减少内存占用
- 语言检测优化:优先从HTML meta标签获取语言信息,减少计算开销
- 并行处理:支持多线程处理不同语言的文章
扩展性与自定义
开发者可以轻松扩展新的语言支持:
# 自定义语言处理器示例
class StopWordsCustom(StopWords):
def __init__(self, language='custom'):
super(StopWordsCustom, self).__init__(language='custom')
def candidate_words(self, stripped_input):
# 实现自定义分词逻辑
return custom_tokenizer(stripped_input)
通过这种模块化的设计,Newspaper3k为多语言新闻处理提供了强大而灵活的基础设施,使得开发者能够轻松处理全球化的新闻内容提取需求。
总结
Newspaper3k作为一个强大的新闻文章提取库,其架构设计和核心算法体现了高度的专业性和实用性。从分层架构设计到模块化划分,从HTML解析与内容提取算法到图像识别与评分机制,再到多语言处理与分词技术,每个组件都经过精心设计和优化。该库支持超过40种语言的文章提取和处理,采用智能的语言检测机制和专门的分词策略,为国际化应用提供了强有力的技术基础。通过多维度评分算法、性能优化策略和灵活的配置选项,Newspaper3k能够在准确性和效率之间取得良好平衡,处理大多数新闻网站的文章提取需求。其清晰的模块职责边界和优秀的软件架构设计思想,使得库具有良好的可维护性、扩展性和实用性,为开发者提供了处理全球化新闻内容提取的强大工具。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



