fivefilters/readability.php

Readability.js 的 PHP 版本

v3.2.0 2024-04-21 13:31 UTC

README

Latest Stable Version Tests

PHP 版本的 Mozilla 的 Readability.js。解析 HTML 文本(通常是新闻和其他文章),并返回 标题作者主图像文本内容,不包含导航栏、广告、页脚或任何不是文本主体内容的东西。分析每个节点,给它们打分,并确定哪些是相关的,哪些可以被丢弃。

Screenshot

项目的目标是成为 Mozilla 版本的 1:1 端口,并紧密跟踪那里引入的所有变化,但在结构上存在一些主要差异。大部分代码是 1:1 复制——甚至注释都被导入——但一些函数和结构被调整以更好地适应 PHP 语言。

原始开发者: Andres Rey

开发者/维护者: FiveFilters.org

代码移植

主分支 - 截至 2021 年 8 月 26 日更新,除了一个 代码片段,它在 PHP 中的结果与 JS 版本不同。可能存在错误,或者底层代码中的一些差异影响了这一点。如果您知道问题所在,请随时给我们留言或提交拉取请求。 :)

版本 2.1.0 - 截至 2018 年 11 月 19 日更新至 Readability.js 的 最新版本

要求

PHP 7.4+、ext-dom、ext-xml 和 ext-mbstring。在 *nix 类环境中安装这些依赖项(在您的系统已经安装的情况下很少需要),您可以尝试以下操作

$ sudo apt-get install php7.4-xml php7.4-mbstring

如何使用

首先,您必须使用 composer 引入库

composer require fivefilters/readability.php

然后,创建一个 Readability 类并传递一个 Configuration 类,将 HTML 传递给 parse() 函数,并输出变量

<?php
require __DIR__ . '/vendor/autoload.php';
use fivefilters\Readability\Readability;
use fivefilters\Readability\Configuration;
use fivefilters\Readability\ParseException;

$readability = new Readability(new Configuration());

$html = file_get_contents('http://your.favorite.newspaper/article.html');

try {
    $readability->parse($html);
    echo $readability;
} catch (ParseException $e) {
    echo sprintf('Error processing text: %s', $e->getMessage());
}

您的脚本将输出解析后的文本或报告任何错误。您应该始终在 try/catch 块中包装 ->parse 调用,因为如果 HTML 不能正确解析,将抛出 ParseException

如果您想对输出有更精细的控制,只需逐个调用属性,并用您自己的 HTML 包装它们。

<h1><?= $readability->getTitle(); ?></h1>
<h2>By <?= $readability->getAuthor(); ?></h2>
<div class="content"><?= $readability->getContent(); ?></div>

以下是可以用的属性列表

  • 文章标题:->getTitle();
  • 文章内容:->getContent();
  • 摘要:->getExcerpt();
  • 主图像:->getImage();
  • 所有图像:->getImages();
  • 作者:->getAuthor();
  • 文本方向(ltr 或 rtl):->getDirection();

如果您需要调整最终的 HTML,可以通过调用 ->getDOMDocument() 获取结果的 DOMDocument。

选项

您可以通过 Configuration 对象更改 Readability 的行为。例如,如果您想修复相对 URL 并声明原始 URL,可以设置配置如下

$configuration = new Configuration();
$configuration
    ->setFixRelativeURLs(true)
    ->setOriginalURL('http://my.newspaper.url/article/something-interesting-to-read.html');

您还可以将配置参数数组传递给构造函数

$configuration = new Configuration([
    'fixRelativeURLs' => true,
    'originalURL'     => 'http://my.newspaper.url/article/something-interesting-to-read.html',
    // other parameters ... listing below
]);

然后,将此 Configuration 对象传递给 Readability。以下选项可用。记住,在调用时使用原生设置器时,要使用 set 预先。

  • MaxTopCandidates:默认值 5,顶级候选人的最大数量。
  • CharThreshold:默认值 500,表示文章解析成功的最小字符数。
  • ArticleByLine:默认值 false,搜索文章的作者签名并从文本中移除。它将被移动到文章元数据中。
  • StripUnlikelyCandidates:默认值 true,移除不太可能包含相关信息的节点。对于调试或解析复杂或非标准文章很有用。
  • CleanConditionally:默认值 true,在解析后移除某些节点以返回更干净的结果。
  • WeightClasses:默认值 true,在评级阶段加权类别。
  • FixRelativeURLs:默认值 false,将相对URL转换为绝对URL。例如 /test 转换为 http://host/test
  • SubstituteEntities:默认值 false,禁用libxml的substituteEntities标志。将避免替换HTML实体。例如 &aacute; 转换为 á。
  • NormalizeEntities:默认值 false,将UTF-8字符转换为相应的HTML实体。用于解析具有混合编码的HTML。
  • OriginalURL:默认值 http://fakehost,用于修复相对URL的文章原始URL。
  • KeepClasses:默认值 false,从HTML元素中移除所有 class="..." 属性值。
  • Parser:默认值 html5,使用HTML5-PHP进行解析。将解析器设置为libxml以使用它(不建议用于现代HTML文档)。
  • SummonCthulhu:默认值 false,通过正则表达式删除所有 <script> 节点。这不是理想的方法,因为它可能会破坏某些内容,但如果将解析器设置为libxml(如上所述),这可能是唯一解决方案,以解决 libxml处理未转义javascript的问题

调试日志

记录是可选的,您需要注入自己的记录器以保存所有调试消息。为此,请使用实现PSR-3记录接口的记录器,并将其传递给配置对象。例如

// Using monolog

$log = new Logger('Readability');
$log->pushHandler(new StreamHandler('path/to/my/log.txt'));

$configuration->setLogger($log);

在日志中,您可以找到有关解析节点、为什么它们被移除以及为什么它们被认为对最终文章相关的重要信息。

局限性

当然,主要限制是PHP。通过延迟加载、AJAX或任何类型的javascript驱动的调用加载内容网站将被忽略(实际上,未运行),与readability.js的结果相比,生成的文本将是不正确的。您想要使用readability.php解析的所有文章都需要完整,并且所有内容都应该已经包含在HTML中。

已知的libxml解析问题

Readability.php自3.0.0版本起使用HTML5解析器。早期版本使用libxml。以下问题适用于libxml解析,因此如果您使用的是Readability.php的早期版本(3.0.0之前),或者您已将解析器设置为libxml(在配置中),请继续阅读...

JavaScript溢出到文本主体

DOMDocument在解析包含未转义HTML的字符串中的javascript时存在一些问题。考虑以下代码

<div> <!-- Offending div without closing tag -->
<script type="text/javascript">
       var test = '</div>';
       // I should not appear on the result
</script>

如果您想从HTML中删除脚本(如readability所做的那样),您会期望最终只得到一个div和一个注释。问题是libxml将关闭div标签视为HTML标签,从而有效地关闭未关闭的标签,并将剩余的javascript作为字符串放在P标签中。如果保存该节点,最终的HTML将变成这样

<div> <!-- Offending div without closing tag -->
<p>';
       // I should not appear on the result
</p></div>

这是一个libxml问题,而不是Readability.php的错误。

这个问题有一个解决方案:使用summonCthulhu选项。这将通过正则表达式删除所有脚本标签via regex,这并不理想,因为你可能最终会召唤出黑暗的统治者

&nbsp实体消失

&nbsp实体会被libxml自动转换成空格,无法禁用此功能。

自闭合标签渲染为完整标签

自闭合标签如<br />会自动扩展为<br></br。在libxml中无法禁用此功能。

依赖关系

Readability.php使用

  • HTML5-PHP来解析和序列化HTML。
  • PSR Log接口来定义允许的日志记录器类型。
  • Monolog仅在开发安装中需要。(在composer install时使用--dev选项)。

待办事项

  • 跟进Readability.js的变化
  • 为__toString()方法添加一个小型模板引擎,而不是使用硬编码的。
  • 将所有的iterator_to_array调用替换为自定义的PHP生成器,该生成器会跟踪已删除或更改的节点。

工作原理

Readability使用DOMDocument解析所有文本,扫描文本节点并给出基于单词数量、链接和元素类型的分数。然后它选择得分最高的元素,并使用所有兄弟元素创建一个新的DOMDocument。每个兄弟元素都会被评分以丢弃无用的元素,如导航栏、空节点等。

安全性

如果您打算使用未经验证的输入(无论是HTML还是DOM形式)与Readability一起使用,我们强烈建议您使用像HTML Purifier这样的清洗库,以避免在使用Readability输出时发生脚本注入。我们还建议使用CSP来为允许的结果内容添加额外的深度防御限制。Firefox的阅读模式集成本身也使用这两种技术。明确地说,从输入中清洗不安全内容不是Readability本身的目标 - 有其他很好的清洗库,请使用它们!

测试

本地安装的任何7.4及以上的PHP版本都足以开发新功能和添加新测试用例。如果您想确保您的更改不会与其他版本的PHP版本产生问题,您可以使用提供的Docker容器来测试目前在7.4、8.0、8.1版本上的代码。

如果您使用composer下载此包,请确保传递--prefer-source标志,否则test/文件夹将不会被下载。

您需要Docker和Docker Compose才能进行此操作。要运行上述三个PHP版本的测试,只需键入以下命令

make test-all

这将启动所有容器并在每个支持的PHP版本上运行所有测试。如果您想针对特定版本进行测试,可以使用make test-7.4make test-8.0make test-8.1

不同版本的libxml

如果您想针对支持的PHP版本多个版本的libxml进行测试,请运行test-all-versions。这将针对7.4到8.1版本的PHP和2.9.10、2.9.13和2.9.14版本的libxml进行测试。通常您不需要这样做,除非您认为您在特定版本的libxml上发现了错误。

更新预期测试

如果您对代码进行了改进,您可能想检查这里测试用例的Readability.php输出。首先,从项目文件夹的根目录运行以下命令

docker-compose up -d php-7.4-libxml-2.9.10

现在您应该已经在Docker实例上运行了一个Docker镜像,项目根目录已映射到Docker实例上的/app/(参见docker-compose.yml)。从现在起,对这些文件的任何更改都可以从Docker实例访问。

接下来,在tests/目录下创建一个名为/changed的文件夹,然后运行以下命令来运行测试套件

docker-compose exec -e output-changes=1 -e output-diff=1 php-7.4-libxml-2.9.10 php /app/vendor/phpunit/phpunit/phpunit --configuration /app/phpunit.xml

两个环境变量(output-changes=1output-diff=1)将导致任何失败的测试的新输出(包括更改的diff)被写入changed/文件夹。

如果您对更改感到满意,设置output-diff=0,则不再写入diff文件,这使得将新的预期输出文件复制到test-pages的相应位置更加容易。

许可证

基于Arc90的readability.js(1.7.1)脚本,可在以下网址找到:http://code.google.com/p/arc90labs-readability

Copyright (c) 2010 Arc90 Inc

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   https://apache.ac.cn/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.