okturan/news-extractor
GitHub: okturan/news-extractor
一个零依赖的 Python 库和 CLI,用于从土耳其语新闻页面提取结构化文章数据,优先 JSON-LD 解析并内置 SSRF 防护机制。
Stars: 0 | Forks: 0
# News Extractor
[](https://github.com/okturan/news-extractor/actions/workflows/test.yml)
[](./pyproject.toml)
[](./pyproject.toml)
[](./LICENSE)
一个零依赖的 Python 库和 CLI,用于提取结构化的土耳其语新闻文章。它优先使用 schema.org `NewsArticle`/`Article` JSON-LD,退回到语义化 HTML,并将出站 URL 获取视为安全边界。
这是一个库和研究项目,而不是托管的抓取服务。其有效证明是可安装的包、确定性的测试夹具(fixtures)、专注于安全性的传输测试以及可选的实时验证——而不是截图或演示页面。
## 它所展示的内容
| 方面 | 当前证据 |
|---|---|
| 提取 | 以 JSON-LD 优先,并带有语义化 ``/`` 回退路径 |
| 安全性 | 版本稳定的公共地址策略、递归转换地址检查、地址绑定的 sockets、TLS 主机名验证,以及单一的绝对获取截止时间 |
| 依赖健康 | 没有运行时或测试依赖;已检查构建的 wheel 是否存在意外的 `Requires-Dist` 元数据 |
| 兼容性 | `ArticleExtractor`、`extract_article`、批量/统计、CLI 和积压(backlog)辅助工具仍然可用 |
| Unicode | 确定性的土耳其语测试夹具涵盖了端到端的 `İ`、`ı`、ş、`ğ`、ü、ö 和 ç |
| 自动化 | 固定的、最小权限的 CI 涵盖了 Python 3.10、3.12 和 3.14,以及包安装冒烟测试 |
## 安装
该项目需要 Python 3.10 或更高版本,且没有运行时依赖。
```
python -m venv .venv
source .venv/bin/activate
python -m pip install --editable .
```
## 快速开始
```
from news_extractor import ArticleExtractor
extractor = ArticleExtractor()
article = extractor.extract("https://example.com/news/article")
if article:
print(article["title"])
print(article["method"]) # "json-ld" or "semantic-html"
print(article["text"][:300])
```
便捷函数仍然可用:
```
from news_extractor import extract_article
article = extract_article("https://example.com/news/article")
```
对于你已经拥有的 HTML 的确定性处理,可以完全绕过网络:
```
from news_extractor import ArticleExtractor
html = """
"""
article = ArticleExtractor(min_text_length=30).extract_html(
html,
url="https://publisher.example/article",
)
```
## CLI
```
news-extractor 'https://example.com/news/article'
news-extractor --format json --timeout 8 --max-bytes 1000000 'https://example.com/news/article'
python -m news_extractor.cli --help
```
只有在每个请求的 URL 都成功时,CLI 才会退出并返回代码 `0`;如果任何提取失败,则返回 `1`;如果配置无效,则返回 `2`。
`--timeout` 是用于完成获取的单一绝对物理时间预算。DNS 解析、每一次重定向和地址尝试、TCP/TLS 连接、请求传输、响应头和响应体都消耗相同的预算;缓慢的字节传输并不会重置它。
## 提取流水线
1. 验证 `http`/`https` URL,并拒绝嵌入的凭证、端口零以及无效端口。
2. 在获取截止时间和进程范围的八线程工作容量限制内解析主机名;对每个回答应用明确的公共地址策略,并递归验证嵌入在转换格式中的 IPv4 目标。
3. 将 socket 连接到已审查的地址,同时保留原始主机名以用于 TLS 验证和 `Host` 头。
4. 对每次重定向重复验证,在整个链条中共享原始截止时间,并拒绝 HTTPS 到 HTTP 的降级。
5. 要求 HTML/XHTML、身份编码、有限的响应体、有限的重定向次数,以及一个不能因缓慢的头部或正文字节而延长的绝对截止时间。
6. 解析 schema.org JSON-LD 并选择最强匹配的 `NewsArticle` 或 `Article` 候选者。
7. 如果结构化文章文本缺失或太短,则从语义化 ``/`` 区域收集段落和块引用内容,同时排除导航、表单、脚本、样式、侧边栏和页脚。
当获取或提取失败时,解析器返回 `None`,这与最初的公共 API 相匹配。操作诊断通过 Python logging 发出。
## 响应结构
```
{
"url": "https://publisher.example/start",
"final_url": "https://publisher.example/article",
"title": "Article title",
"text": "Article body...",
"authors": ["Author Name"],
"date": "2026-07-16T09:30:00+03:00",
"keywords": ["keyword"],
"description": "Article summary",
"image": "https://publisher.example/image.jpg",
"categories": ["Technology"],
"language": "tr",
"method": "json-ld", # or "semantic-html"
"text_length": 1234,
"extracted_at": "2026-07-16T07:00:00+00:00",
}
```
各个网站暴露的元数据并不一致,因此 `title`、`date`、`description`、`image` 和 `categories` 可能为 `None`;作者和关键词为列表。文本和元数据属于不受信任的发布者内容,在输出到目标位置时必须进行转义。
## 网络安全默认设置
默认客户端使用明确的 IPv4 和 IPv6 网络表,而不是 Python 那个依赖于版本的 `ipaddress.is_global` 分类。它阻止私有、环回、链路本地、共享、文档、基准测试、保留、站点本地、多播、未指定和已弃用的转换范围——包括只有一个答案被拒绝的混合 DNS 回答。IPv4 映射、6to4、Teredo、已知的 NAT64 以及两种 ISATAP 接口 ID 形式都会被递归检查,因此它们无法隐藏被拒绝的 IPv4 目标。全球可路由的 `192.0.0.9` 和 `192.0.0.10` 任播异常保持允许状态。
同步解析器调用在所有客户端共享的一个进程范围容量限制器后台运行。最多可以有八个 DNS 工作线程处于未决状态;等待容量会消耗相同的绝对获取截止时间,并且在饱和时会直接失败,而不会创建另一个工作线程。
仅在受信任的内部部署中,请显式开启:
```
extractor = ArticleExtractor(allow_private_networks=True)
```
```
news-extractor --allow-private-network 'http://intranet.example/article'
```
对于用户提供的 URL,请勿启用该选项。有关威胁模型和报告途径,请参阅 [SECURITY.md](./SECURITY.md)。
人类可读的诊断信息会隐去 URL 的凭证、查询和片段,并从发布者文本中剥离终端控制序列。本地积压(backlog)查看器通过 DOM 文本节点呈现不受信任的 JSONL 标量,并且仅链接经过验证的 HTTP(S) URL。
## 批量和统计 API
```
extractor = ArticleExtractor()
results = extractor.extract_batch(
[
"https://publisher.example/article-1",
"https://publisher.example/article-2",
]
)
stats = extractor.get_stats(results)
print(stats)
# {
# "total": 2,
# "successful": 1,
# "failed": 1,
# "success_rate": 50.0,
# "methods": {"json-ld": 1},
# }
```
## SQLite 积压工作流
`news_extractor.backlog` 模块保留了原始的收集器桥接:
- `load_records()` 从 News Gatherer SQLite 数据库读取分页行。
- `reextract()` 应用 `ArticleExtractor`,同时隔离行级别的失败。
- `summarize()` 计算成功总数。
- `write_jsonl()` 输出 UTF-8 JSONL。
```
python examples/scrape_news_gatherer_backlog.py \
--db ../news-gatherer/output/news-gatherer.db \
--limit 50 \
--format json > /tmp/news-extractor-backlog.jsonl
python examples/view_jsonl.py --file /tmp/news-extractor-backlog.jsonl --limit 10
```
在本地打开 `examples/backlog_viewer.html`,对所跟踪的根目录 `backlog.jsonl` 进行可搜索的视觉检查,该页面会直接加载此文件。该文件是 2025 年 11 月的遗留输出样本;其旧的 `newspaper4k`/`trafilatura` 方法标签是历史数据,而不是当前的运行时值。
## 验证
所需的测试套件是确定性的,并且不需要网络:
```
python -m pip install --no-deps --editable .
python -m pip check
python -m unittest discover -s tests/unit -v
python -m compileall -q src tests/unit tests/validation examples
```
构建并检查分发包:
```
python -m pip install build==1.3.0
python -m build
python scripts/verify_distribution.py dist
```
单独刷新实时发布者证据:
```
python tests/validation/validate_live.py
python tests/validation/validate_live.py --json
python tests/validation/validate_live.py --skip-legacy --url 'https://publisher.example/article'
```
实时脚本特意不作为 CI 门控。发布者内容的移除、机器人控制、付费墙、重新设计以及超出范围的图库均属于外部漂移,需要人工进行分类。
## 范围和限制
- 启发式回退特意比完整的浏览器/可读性引擎更小。
- 不执行仅由 JavaScript 渲染的正文。
- 付费墙仅能提供下载的 HTML 中存在的内容。
- 图库、列表页、实时博客和多文章订阅源不在核心文章契约范围内。
- 元数据质量取决于发布者的标记。
- 地址验证降低了 SSRF 风险,但并不能使任意抓取在法律或操作上变得合适;请尊重发布者的条款、robots 指导、版权、速率限制和隐私义务。
## 历史研究
2025 年 11 月的实验在固定的语料库中测量了 Newspaper4k → Trafilatura 的组合,覆盖了 5/6 的 URL。该实现于 2026 年 7 月被废弃,因为其传递依赖图包含多个严重/高危险级别的公告,且没有可用的兼容补丁。该数字作为过时的研究证据仍然有用,而不是对当前标准库实现的声明。
有关决策记录,请参阅 [RESEARCH.md](./RESEARCH.md);有关冻结的报告和原型,请参阅 `archive/legacy_research/`。归档的 Python 文件仅为源代码工件;它们不是包、测试发现或受支持的执行表面的一部分。
## 许可证
原始项目源代码和文档在 [MIT](./LICENSE) 许可下授权。发布者 URL、捕获的内容和遗留研究样本仍受其原始所有者权利的约束,本存储库不会对其重新授权。
Türkçe başlık
Yeterince uzun haber metni burada yer alır...
标签:JSON-LD, Python, 文档结构分析, 新闻提取, 无后门, 逆向工具