牛娇客(一四二)zz

发信人: newjoker (牛娇娇), 信区: Joke
标 题: 牛娇客(一四二)zz
发信站: 水木社区 (Fri Sep 23 23:40:43 2016), 站内

1.亚洲有五大巨头,韩国棒子,日本鬼子,越南猴子,印度阿三和中国喷子。
其中中国喷子最屌,他们成群结队,按键伤人,攻击距离最远,速度最快,数秒内即可完成惊人内伤,并且常常莫名其妙发动攻击,令人感叹防不胜防!

2.大家不用太过于焦虑,现在不是只有你觉得日子难过,是几乎所有人都觉得日子难过,要想解决这个问题,最简单的方法就是:习惯了就好了。

3.同学聚会喝了点酒……
到家后装醉,就躺沙发上。二货老公看见了走了过来,关心地问道:喝了多少?我装着醉呼呼的回答好多,心想着二货老公肯定心疼我。二货接着小声地问:银行卡密码多少?……

4.最近工作太忙总是加班到深夜,三天没回家了。这是我和女友相处两年以来第一次连续三天没见面。能看出来她十分想念我,期间给我发了60来条短信,全是银行卡扣款信息。

5.办公室一男对一女同事一直色心不死,今天他对女同事说:“那天我看你老公带个女孩去宾馆了。”
女同事:“你认识那女的吗?”
他:“不认识。”
女同事:“你不认识的,就不用和我说了。如果我老公带的女的是你妹妹或者是你老婆,你告诉我,我给你主持公道。”

6.回到家一见面,老婆阴沉着脸质问我:“我听别人说,你上午跟一个女同事手挽手去买葡萄,是真的么?”
我赶紧解释:“亲爱的你肯定是误会了,我们买的是提子。”

7.今天和老婆大人出去逛庙会,看到有个套圈的不错,老板是个中年大叔,蹲在那摆摊。十块钱弄了三个圈让她玩。老婆很开心,说要套个大娃娃回来,然后直接一个圈丢出去,直接套中了店主的秃头……店主大叔很幽怨地看了我一眼,然后对我老婆说,妹纸,有老公了就不要调戏大叔了好么……

8.有个萌萌哒的老婆是什么感觉?
一天跟老婆吵架,老婆急了,拿起一瓶敌敌畏说:“你这么狠心,好!就让你知道失去我的滋味。”
过了一分钟……“老公、拧不开。”
我:……于是我帮她拧开了。
“老公,你尝尝过期没有…”

9.老妈在抱怨家里那套旧房子不好卖,高了没人要,低价又不想卖。
突然眼前一亮:“闺女,你说如果我打个广告就说买一赠一咋样?”
我纳闷:“怎么个赠法?”
老妈高兴地说:“来买房子赠送你啊!”

牛娇客(一三九)zz

发信人: newjoker (牛娇娇), 信区: Joke
标 题: 牛娇客(一三九)zz
发信站: 水木社区 (Sun Sep 18 23:18:49 2016), 站内

1.我喜欢看柯南,一集死一个日本人。 我喜欢看死亡笔记,一集死一打日本人。 我喜欢看海贼王,一集死一船日本人。 我喜欢看火影,一集死一村日本人。 我喜欢看奥特曼,一集死一城日本人。 我喜欢看“世界末日”,30分钟没到,日本岛就没了。 九一八勿忘国耻!

2.人们常说,“十五的月亮十六圆”。但今年,月亮迟到的更多,要到农历十七才圆。是因为物价上涨了吗?十五的月亮都要17元了……

3.本人要买车了,现在工作稳定,为了提高生活质量欲求购二手车一辆,要求不高,车况良好,提速一定要快,质量要过关,本人不差钱,不差钱,就是不差钱,只有四点要求如下:
一.脚蹬子必须得好使!
二.链盒子别TM哗啦哗啦老响!
三.刹车必须给力,别老让我拿脚秃噜地!
四.车座子必须要完整,不能刮裤裆!

4.伪化学解释:
香蕉含大量的钾,而雪碧的碳酸含量较高,众所周知碳酸钾是草木灰的主要成分,一起吃香蕉喝雪碧就是吃灰……
伪物理解释:
香蕉软糯,雪碧含气,通过胃酸的搅拌将产生大量香蕉泡沫,引发涨肚,严重时可导致急性肠胃炎……
伪中医解释:
香蕉味甘性平,五行属土,祛燥热、清肾毒;雪碧味酸性寒,五行属木。肝木克脾土,同食要受苦。又雪碧色清而实滞,香蕉状凝而质松,混而抑清扬浊,乃是君臣失和,配伍不当之像……

5.突然觉得李世民好傻,如果当时他不让唐僧去取经而是直接把他吃了,那我们现在就一直是大唐盛世了,那我也不用减肥了,而且可以三妻四妾啦,我简直太机智了……

6.昨晚想跟老婆亲热。老婆:先别,儿子还没睡着呢。我:老早都睡了,肯定睡着了。老婆不信,于是我拿一个一块硬币在儿子眼前晃了晃。儿子一个闪身起来:“一块钱就想办这么大的事?!”我…………

7.一哥们烟瘾特大,他媳妇儿想尽办法要他戒烟都没有一点效果。一次聚会另一哥们媳妇儿支招,回家后要求哥们抽烟可以,抽一支烟晚上啪啪一次,每天老实交待, 如果不记得按三支算。就这样三个月过去了,再一次见到哥们已经是枯瘦的身板,发烟给他都连忙摆手说戒了……就这样,又一名烟民成功戒烟!!!

8.老王是出了名的妻管严,某夜烟瘾犯了,遂找老婆要钱,老婆挥了挥手指,一分钟一块钱,老王脸当场就黑了,怒气冲冲的说道,两块钱你让我上哪里去买烟。

Elasticsearch在Centos 7上的安装与配置

安装JDK

Elasticsearch官方建议使用 Oracle的JDK8,在暗转跟之前首先要确定下机器有没有安装JDK.

rpm -qa | grep -E '^open[jre|jdk]|j[re|dk]'

如果有,有可能是系统自带的openjdk,而非oracle的jdk。可以使用

rpm -qa | grep Java | xargs rpm -e --nodeps

 批量卸载所有带有Java的文件,然后进行重新安装。

命令行下载 JDK 有个麻烦的地方,必须先要接受 Oracle 的许可协议,不过可以通过设置 cookie 来解决。

wget --no-check-certificate --no-cookies \
     --header "Cookie: oraclelicense=accept-securebackup-cookie" \
     http://download.oracle.com/otn-pub/java/jdk/8u101-b13/jdk-8u101-linux-x64.tar.gz

直接解压

tar -zxvf jdk-8u101-linux-x64.tar.gz

 后。配置环境变量:

vi /etc/profile

添加如下内容,并保存:

# set java environment
export JAVA_HOME=/usr/local/jdk1.8.0_101
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:${PATH}

保存后运行

source /etc/profile

  使环境变量生效

输入

java -version

 确认是否安装成功。

[root@localhost local]# java -version
java version "1.8.0_101"
Java(TM) SE Runtime Environment (build 1.8.0_101-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.101-b13, mixed mode)

安装Elasticsearch

最简单的方式是通过rpm方式进行安装,这里介绍的是手动安装的方法:

1、进入官网查看最新版本的下载链接

2、使用命令行进行下载:

wget https://download.elastic.co/elasticsearch/release/org/elasticsearch/distribution/tar/elasticsearch/2.4.0/elasticsearch-2.4.0.tar.gz

3、解压文件

tar -zxvf elasticsearch-2.4.0.tar.gz

4、运行elasticsearch

执行

sh /usr/local/elasticsearch-2.4.0/bin/elasticsearch -d

  其中-d表示后台启动

不出意外,可以看到如下报错信息:

[root@localhost bin]# Exception in thread "main" java.lang.RuntimeException: don't run elasticsearch as root.
       at org.elasticsearch.bootstrap.Bootstrap.initializeNatives(Bootstrap.java:94)
       at org.elasticsearch.bootstrap.Bootstrap.setup(Bootstrap.java:160)
       at org.elasticsearch.bootstrap.Bootstrap.init(Bootstrap.java:286)
       at org.elasticsearch.bootstrap.Elasticsearch.main(Elasticsearch.java:35)
Refer to the log for complete error details.

原因是elasticsearch默认是不支持用root用户来启动的。

解决方案一:Des.insecure.allow.root=true

修改/usr/local/elasticsearch-2.4.0/bin/elasticsearch,添加

ES_JAVA_OPTS="-Des.insecure.allow.root=true"

或执行时添加:

sh /usr/local/elasticsearch-2.4.0/bin/elasticsearch -d -Des.insecure.allow.root=true

注意:正式环境用root运行可能会有安全风险,不建议用root来跑。

解决方案二:添加专门的用户

useradd elastic
chown -R elastic:elastic  elasticsearch-2.4.0
su elastic
sh /usr/local/elasticsearch-2.4.0/bin/elasticsearch -d

使用 curl http://localhost:9200/ 查看是否运行,如果返回如下信息则标示运行正常:

[elastic@localhost local]$ curl http://localhost:9200/
{
  "name" : "Astrid Bloom",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "2.4.0",
    "build_hash" : "ce9f0c7394dee074091dd1bc4e9469251181fc55",
    "build_timestamp" : "2016-08-29T09:14:17Z",
    "build_snapshot" : false,
    "lucene_version" : "5.5.2"
  },
  "tagline" : "You Know, for Search"
}

elasticsearch默认restful-api的端口是9200 不支持Ip地址,只能在本机用http://localhost:9200来访问。如果需要改变,需要修改配置文件。

默认情况下 Elasticsearch 的 RESTful 服务只有本机才能访问,也就是说无法从主机访问虚拟机中的服务。为了方便调试,可以修改 /etc/elasticsearch/config/elasticsearch.yml 文件,加入以下两行:

network.bind_host: “0.0.0.0"
network.publish_host: _nonloopback:ipv4

 

或去除network.host 和http.port之前的注释,并将network.host的IP地址修改为本机外网IP。然后重启,Elasticsearch 关闭方法(输入命令:

ps -ef | grep elasticsearch

 ,找到进程,然后kill掉就行了。

如果外网还是不能访问,则有可能是防火墙设置导致的。

防火墙设置

1、关闭selinux

sed -i "s/SELINUX=enforcing/SELINUX=disabled/" /etc/selinux/config
setenforce 0

2、安装firewall

yum install firewalld firewall-config

systemctl start firewalld.service
systemctl enable firewalld.service
systemctl status firewalld.service

3、开放端口

firewall-cmd --permanent --add-port={9200/tcp,9300/tcp}
firewall-cmd --reload
firewall-cmd --state
firewall-cmd --list-all

至此,elasticsearch就顺利安装完成了,但是为了更好的使用,还需要安装中文分析工具等插件,下篇文章再做介绍。

参考链接:

Elasticsearch学习笔记:简介

Elasticsearch是一个基于Apache Lucene(TM)的开源搜索引擎。无论在开源还是专有领域,Lucene可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库。但是,Lucene只是一个库。想要使用它,你必须使用Java来作为开发语言并将其直接集成到你的应用中,更糟糕的是,Lucene非常复杂,你需要深入了解检索的相关知识来理解它是如何工作的。Elasticsearch也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。它提供了近实时的索引、搜索、分析功能。Elasticsearch不仅仅是Lucene和全文搜索,我们还能这样去描述它:

  • 分布式的实时文件存储,每个字段都被索引并可被搜索
  • 分布式的实时分析搜索引擎
  • 可以扩展到上百台服务器,处理PB级结构化或非结构化数据

所有的这些功能被集成到一个服务里面,你的应用可以通过简单的RESTful API、各种语言的客户端甚至命令行与之交互。它可以用于全文搜索、结构化搜索、分析以及将这三者混合使用,使用案例如下:

  • 维基百科使用Elasticsearch提供全文搜索并高亮关键字,以及输入实时搜索(search-as-you-type)和搜索纠错(did-you-mean)等搜索建议功能。
  • 英国卫报使用Elasticsearch结合用户日志和社交网络数据提供给他们的编辑以实时的反馈,以便及时了解公众对新发表的文章的回应。
  • StackOverflow结合全文搜索与地理位置查询,以及more-like-this功能来找到相关的问题和答案。
  • Github使用Elasticsearch检索1300亿行的代码。

上手Elasticsearch非常容易。它提供了许多合理的缺省值,并对初学者隐藏了复杂的搜索引擎理论。它开箱即用(安装即可使用),只需很少的学习既可在生产环境中使用。

  • 它提供了强大的搜索功能,可以实现类似百度、谷歌等搜索。
  • 可以搜索日志或者交易数据,用来分析商业趋势、搜集日志、分析系统瓶颈或者运行发展等等
  • 可以提供预警功能(持续的查询分析某个数据,如果超过一定的值,就进行警告)
  • 分析商业信息,在百万级的大数据中轻松的定位关键信息

在ElasticSearch中,我们常常会听到Index、Type以及Document等概念,将Elasticsearch和传统关系型数据库Mysql做一下类比:

Elasticsearch-mysql

注意:MySQL的Index和Elasticsearch的Index含义并不一致。

Elasticsearch集群可以包含多个索引(indices)(数据库),每一个索引可以包含多个类型(types)(表), 每一个类型包含多个文档(documents)(行),然后每个文档包含多个字段(Fields)(列)。数据被存储和索引在分片(shards)中,索引只是把一个或多个分片分组在一起的逻辑空间。我们只需要知道文档存储在索引(index)中。其他细节都可以有Elasticsearch搞定。

Elasticsearch

一些基本概念:

  • Cluster/集群:Cluster是一组服务器构成的集成,用于协同存储数据、索引数据、检索数据和解析数据。Cluster具有唯一标识名,你只需要指定集群标识名(默认是elasticsearch),启动的时候,凡是集群是这个名字的,都会默认加入到一个集群中,选举master节点和节点管理都是自动完成的。当然一个节点也可以组成一个集群。
  • Node/节点:Node是参与到Cluster的单个服务器节点,具有唯一标识名,可加入到指定的Cluster中。是一个运行着的Elasticsearch实例。
  • Index/索引:Index是一类文档的集合,是具有相同业务特征的数据文档集合(不是相同数据结构),相当于传统数据库中的数据库。ES数据的索引、搜索和分析都是基于索引完成的。Cluster中可以创建任意个Index。每个Index(对应Database)包含多个Shard,默认是5个,分散在不同的Node上,但不会存在两个相同的Shard(相同的主和复制)存在一个Node上。当索引创建完成的时候,主分片的数量就固定了,但是复制分片的数量可以随时调整。
  • Type/类型:Type是Index中数据的 ,用于标识不同的文档字段信息的集合,相当于传统数据库的表。在0之后的版本直接做了插入检查,禁止一个索引下不同Type的字段类型冲突。举例来说,在一个博客系统中,你可以定义一个 user type,可以定义一个 blog type,还可以定义一个 comment type。
  • Document/文档:Document是ES数据可被索引化的基本的存储单元,需要存储在Type中,相当于传统数据库的行记录,使用json来表示。
  • Shard/分片:一个索引可能会存储大量的数据,进而会让单个节点超出硬件能承受范围。举例来说,存储了10亿文档的单个节点,会占用1TB磁盘空间,并且会导致查询的时候速度很慢。为了解决这个问题,Elasticsearch 提供了分片也就是shards对index进行划分成更小的部分。当你创建index的时候,你可以简单地指定你想要的碎片数量。每一个碎片具有和 index 完全相同的功能。碎片最主要的两个作用是:它允许你水平地切割你的容量体积;它允许你并行地分发作业,提高系统的性能。默认在创建索引时会创建5个分片,这个数量可以修改。分片的数量只能在创建索引的时候指定,不能在后期修改。
  • Replicas/副本:因为各种原因,所以数据丢失等问题会时有发生,碎片也可能会丢失,为了防止这个问题,所以你可以将一个或多个索引碎片复制到所谓的复制碎片,简称为副本。副本最主要的两个作用是:它提供了高可用性,以防碎片/节点失败。之于这点,所以副本的永远不要和原始碎片分布在同一个节点上;它可以扩展系统的吞吐量,因为搜索可以在所有副本执行。默认情况下,Elasticsearch为每个索引分配了5个主碎片和1个副本,这意味着在你的集群中,如果至少有两个节点,那么每个索引将有5个主碎片和5个复制碎片,总共10碎片/索引。

注意:ES并不是一个标准的数据库,它不像MongoDB,它侧重于对存储的数据进行搜索。因此要注意到它不是实时读写的,这也就意味着,刚刚存储的数据,并不能马上查询到。缺省配置下,shard每秒自动更新,所以会有1S的延时。

ElasticSearch生态圈你所应该了解的内容:(后续的文章会一一介绍,尽请期待~)

ElasticSearch-learn

参考链接:

使用Python自动提取内容摘要

利用计算机将大量的文本进行处理,产生简洁、精炼内容的过程就是文本摘要,人们可通过阅读摘要来把握文本主要内容,这不仅大大节省时间,更提高阅读效率。但人工摘要耗时又耗力,已不能满足日益增长的信息需求,因此借助计算机进行文本处理的自动文摘应运而生。近年来,自动摘要、信息检索、信息过滤、机器识别、等研究已成为了人们关注的热点。

自动摘要(Automatic Summarization)的方法主要有两种:Extraction和Abstraction。其中Extraction是抽取式自动文摘方法,通过提取文档中已存在的关键词,句子形成摘要;Abstraction是生成式自动文摘方法,通过建立抽象的语意表示,使用自然语言生成技术,形成摘要。由于自动摘要方法需要复杂的自然语言理解和生成技术支持,应用领域受限。,抽取式摘要成为现阶段主流,它也能在很大程度上满足人们对摘要的需求。

目前抽取式的主要方法:

  • 基于统计:统计词频,位置等信息,计算句子权值,再简选取权值高的句子作为文摘,特点:简单易用,但对词句的使用大多仅停留在表面信息。
  • 基于图模型:构建拓扑结构图,对词句进行排序。例如,TextRank/LexRank
  • 基于潜在语义:使用主题模型,挖掘词句隐藏信息。例如,采用LDA,HMM
  • 基于线路规划:将摘要问题转为线路规划,求全局最优解。

2007年,美国学者的论文《A Survey on Automatic Text Summarization》(Dipanjan Das, Andre F.T. Martins, 2007)总结了目前的自动摘要算法。其中,很重要的一种就是词频统计。这种方法最早出自1958年的IBM公司科学家 H.P. Luhn的论文《The Automatic Creation of Literature Abstracts》

Luhn博士认为,文章的信息都包含在句子中,有些句子包含的信息多,有些句子包含的信息少。”自动摘要”就是要找出那些包含信息最多的句子。句子的信息量用”关键词”来衡量。如果包含的关键词越多,就说明这个句子越重要。Luhn提出用”簇”(cluster)表示关键词的聚集。所谓”簇”就是包含多个关键词的句子片段。

cluster

上图就是Luhn原始论文的插图,被框起来的部分就是一个”簇”。只要关键词之间的距离小于”门槛值”,它们就被认为处于同一个簇之中。Luhn建议的门槛值是4或5。也就是说,如果两个关键词之间有5个以上的其他词,就可以把这两个关键词分在两个簇。下一步,对于每个簇,都计算它的重要性分值。

cluster-2

以上图为例,其中的簇一共有7个词,其中4个是关键词。因此,它的重要性分值等于 ( 4 x 4 ) / 7 = 2.3。

然后,找出包含分值最高的簇的句子(比如5句),把它们合在一起,就构成了这篇文章的自动摘要。具体实现可以参见 《Mining the Social Web: Analyzing Data from Facebook, Twitter, LinkedIn, and Other Social Media Sites》(O’Reilly, 2011)一书的第8章,python代码见github

Luhn的这种算法后来被简化,不再区分”簇”,只考虑句子包含的关键词。下面就是一个例子(采用伪码表示),只考虑关键词首先出现的句子。

类似的算法已经被写成了工具,比如基于Java的Classifier4J库的SimpleSummariser模块、基于C语言的OTS库、以及基于classifier4J的C#实现python实现

参考文章:

TextTeaser

TextTeaser 原本是为在线长文章(所谓 tl;dr:too long; didn’t read)自动生成摘要的服务,其原本的收费标准是每摘要 1000 篇文章付费 12 美元或每月 250 美元。巴尔宾称 TextTeaser 可以为任何使用罗马字母的文本进行摘要,而且比同类工具如 Cruxbot 和 Summly(在 2013 年 3 月被 雅虎斥资 3000 万美元收购)更准确。其创造者霍洛•巴尔宾(Jolo Balbin)表示,在“发现一些扩展问题,特别是 API 中的问题后”,他决定将 TextTeaser 代码开源。

TextTeaser开源的代码一共有三个class,TextTeaser,Parser,Summarizer。

  • TextTeaser,程序入口类。给定待摘要的文本和文本题目,输出文本摘要,默认是原文中最重要的5句话。
  • Summarizer,生成摘要类。计算出每句话的分数,并按照得分做排序,然后按照原文中句子的顺序依次输出得分最高的5句话作为摘要。
  • Parser,文本解析类。对文本进行去除停用词、去除标点符号、分词、统计词频等一些预处理操作。

其中打分模型分为四部分:

  • 句子长度,长度为20的句子为最理想的长度,依照距离这个长度来打分。
  • 句子位置,根据句子在全文中的位置,给出分数。(巴尔宾认为一篇文章的第二句比第一句更重要,因为很多作家都习惯到第二句话引入关键点)备注:用段落效果会怎样?
  • 文章标题与文章内容的关系,句子是否包含标题词,根据句子中包含标题词的多少来打分。
  • 句子关键词打分,文本进行预处理之后,按照词频统计出排名前10的关键词,通过比较句子中包含关键词的情况,以及关键词分布的情况来打分(sbs,dbs两个函数)。

开源版本:

自己尝试这个调用Python版本。主要:不要使用pip install textteaser进行安装,该安装方式安装的是这个项目:

https://github.com/jgoettsch/py-textteaser,该项目并非算法实现,而是API实现。直接下载代码即可:https://github.com/DataTeaser/textteaser

下载完成后在Windows下运营test.py会报错,报错信息如下:

Traceback (most recent call last):
  File "D:/textteaser/test.py", line 12, in <module>
    sentences = tt.summarize(title, text)
  File "D:\textteaser\textteaser\__init__.py", line 13, in summarize
    result = self.summarizer.summarize(text, title, source, category)
  File "D:\textteaser\textteaser\summarizer.py", line 11, in summarize
    sentences = self.parser.splitSentences(text)
  File "D:\textteaser\textteaser\parser.py", line 62, in splitSentences
    tokenizer = nltk.data.load('file:' + os.path.dirname(os.path.abspath(__file__)) + '/trainer/english.pickle')
  File "C:\Python27\lib\site-packages\nltk\data.py", line 806, in load
    resource_val = pickle.load(opened_resource)
ImportError: No module named copy_reg

针对报错信息,做如下修改:

1、将”D:\textteaser\textteaser\parser.py”第62行进行修改:

#tokenizer = nltk.data.load('file:' + os.path.dirname(os.path.abspath(__file__)) + '/trainer/english.pickle')
tokenizer = nltk.data.load('file:' + os.path.dirname(os.path.abspath(__file__)) + os.sep + 'trainer' + os.sep + 'english.pickle')

2、找到”\Lib\site-packages\nltk\data.py”第924行,将

return find(path_, ['']).open()

修改为:

file_path = find(path_, [''])
        data = open(file_path, 'rb').read()
        newdata = data.replace("\r\n", "\n")
        if newdata != data:
            f = open(file_path, "wb")
            f.write(newdata)
            f.close()
        f = open(file_path, "rb")
        return f

注意:TextTeaser目前只支持英文摘要。

TextRank

TextRank算法是一种用于文本的基于图的排序算法。其基本思想来源于谷歌的PageRank算法, 通过把文本分割成若干组成单元(单词、句子)并建立图模型, 利用投票机制对文本中的重要成分进行排序, 仅利用单篇文档本身的信息即可实现关键词提取、文摘。和 LDA、HMM 等模型不同, TextRank不需要事先对多篇文档进行学习训练, 因其简洁有效而得到广泛应用。

TextRank 一般模型可以表示为一个有向有权图 G =(V, E), 由点集合 V和边集合 E 组成, E 是V ×V的子集。图中任两点 Vi , Vj 之间边的权重为 wji , 对于一个给定的点 Vi, In(Vi) 为 指 向 该 点 的 点 集 合 , Out(Vi) 为点 Vi 指向的点集合。点 Vi 的得分定义如下:

TextRank

其中, d 为阻尼系数, 取值范围为 0 到 1, 代表从图中某一特定点指向其他任意点的概率, 一般取值为 0.85。使用TextRank 算法计算图中各点的得分时, 需要给图中的点指定任意的初值, 并递归计算直到收敛, 即图中任意一点的误差率小于给定的极限值时就可以达到收敛, 一般该极限值取 0.0001。

基于TextRank的关键词提取

关键词抽取的任务就是从一段给定的文本中自动抽取出若干有意义的词语或词组。TextRank算法是利用局部词汇之间关系(共现窗口)对后续关键词进行排序,直接从文本本身抽取。其主要步骤如下:

  1. 把给定的文本T按照完整句子进行分割,
  2. 对于每个句子,进行分词和词性标注处理,并过滤掉停用词,只保留指定词性的单词,如名词、动词、形容词,其中是保留后的候选关键词。
  3. 构建候选关键词图G = (V,E),其中V为节点集,由2生成的候选关键词组成,然后采用共现关系(co-occurrence)构造任两点之间的边,两个节点之间存在边仅当它们对应的词汇在长度为K的窗口中共现,K表示窗口大小,即最多共现K个单词。
  4. 根据上面公式,迭代传播各节点的权重,直至收敛。
  5. 对节点权重进行倒序排序,从而得到最重要的T个单词,作为候选关键词。
  6. 由5得到最重要的T个单词,在原始文本中进行标记,若形成相邻词组,则组合成多词关键词。例如,文本中有句子“Matlab code for plotting ambiguity function”,如果“Matlab”和“code”均属于候选关键词,则组合成“Matlab code”加入关键词序列。

基于TextRank的自动文摘

基于TextRank的自动文摘属于自动摘录,通过选取文本中重要度较高的句子形成文摘,其主要步骤如下:

  1. 预处理:将输入的文本或文本集的内容分割成句子得,构建图G =(V,E),其中V为句子集,对句子进行分词、去除停止词,得,其中是保留后的候选关键词。
  2. 句子相似度计算:构建图G中的边集E,基于句子间的内容覆盖率,给定两个句子,采用如下公式进行计算:simikarity-1若两个句子之间的相似度大于给定的阈值,就认为这两个句子语义相关并将它们连接起来,即边的权值:similarity-2
  3. 句子权重计算:根据公式,迭代传播权重计算各句子的得分;
  4. 抽取文摘句:将3得到的句子得分进行倒序排序,抽取重要度最高的T个句子作为候选文摘句。
  5. 形成文摘:根据字数或句子数要求,从候选文摘句中抽取句子组成文摘。

参考资料:

玻森自动摘要

玻森采用的是最大边缘相关模型(Maximal Marginal Relevance)的一个变种。MMR是无监督学习模型,它的提出是为了提高信息检索(Information Retrieval)系统的表现。例如搜索引擎就是目前大家最常用的信息检索系统。大家可能经常会碰到,对于我们输入的一个关键词,搜索引擎通常会给出重复的或者内容太接近的检索的情况。为了避免这个现象,搜索引擎可以通过MMR来增加内容的多样性,给出多方面考虑的检索结果,以此来提高表现。这样的思想是可以被借鉴用来做摘要的,因为它是符合摘要的基本要求的,即权衡相关性和多样性。不难理解,摘要结果与原文的相关性越高,它就接近全文中心意思。而考虑多样性则使得摘要内容更加的全面。非常的直观和简单是该模型的一个优点。

相比于其他无监督学习方法,如TextRank(TR), PageRank(PR)等,MMR是考虑了信息的多样性来避免重复结果。TR,PR是基于图(Graph)的学习方法,每个句子看成点,每两个点之间都有一条带权重(Weighted)的无向边。边的权重隐式定义了不同句子间的游走概率。这些方法把做摘要的问题看成随机游走来找出稳态分布(Stable Distribution)下的高概率(重要)的句子集,但缺点之一便是无法避免选出来的句子相互之间的相似度极高的现象。而MMR方法可以较好地解决句子选择多样性的问题。具体地说,在MMR模型中,同时将相关性和多样性进行衡量。因此,可以方便的调节相关性和多样性的权重来满足偏向“需要相似的内容”或者偏向“需要不同方面的内容”的要求。对于相关性和多样性的具体评估,玻森是通过定义句子之间的语义相似度实现。句子相似度越高,则相关性越高而多样性越低。

自动摘要的核心便是要从原文句子中选一个句子集合,使得该集合在相关性与多样性的评测标准下,得分最高。数学表达式如下:

mmr

需要注意的是,D,Q,R,S都为句子集,其中,D表示当前文章,Q表示当前中心意思,R表示当前非摘要,S表示当前摘要。可以看出,在给定句子相似度的情况下,上述MMR的求解为一个标准的最优化问题。但是,上述无监督学习的MMR所得摘要准确性较低,因为全文的结构信息难以被建模,如段落首句应当有更高的权重等。为了提高新闻自动摘要的表现,玻森在模型中加入了全文结构特征,将MMR改为有监督学习方法。从而模型便可以通过训练从“标准摘要”中学习特征以提高准确性。

玻森采用摘要公认的Bi-gram ROUGE F1方法来判断自动生成的摘要和“标准摘要”的接近程度。经过训练,玻森在训练数集上的表现相对于未学习的摘要结果有了明显的提升——训练后的摘要系统F1提高了30%。值得一提的是,在特征训练中,为了改善摘要结果的可读性,玻森加指代关系特征,使得模型表现提高了8%。

相关链接:

其他相关开源项目

[转]反对修订《美国联邦刑事诉讼规则和证据规则》第 41 条

收到一位国外朋友的邮件,建议转发对美国拟修订第41条规则的抵制。大概研究了一下, 《美国联邦刑事诉讼规则和证据规则》第 41 条为「搜查和扣押」 。美国政府准备修订相关条款,而该修订行为会对个人信息安全造成损害。美国很多科技媒体、社群、个人,都在抵制这一修订。转发一下,也欢迎大家发表意见。毕竟,这个世界是紧密联系在一起的!

不要让美国政府侵入个人的计算机 – 停止修订第41条规则

Ariel Hochstadt (前谷歌雇员,国际科技讲师)

美国政府希望采用一种模糊不清的程序——修订一条称为第41条规则的联邦规定——从而充分扩大他们侵入我们计算机的权限。第41条规则的修订将会使其更容易闯入我们的计算机、收集数据、进行远程监控。这些修订可能会影响在世界任何地方使用联网计算机的任何人。然而,他们对采用隐私保护技术如Tor和 VPN的人员的影响则不尽相同。美国国会直到12月1日才可阻止修订的生效。我们需要大声疾呼。与你的朋友以及在你的博客上分享这个帖子。提高人们对修订第41条规则的意识!

什么是第41条规则?它如何对生活在美国之外的你产生影响?

第41条规则授权联邦治安法官签发执法搜索和查封的许可。但它含有一条重要限制:它要求政府必须获得其所欲执行搜索的管辖区域法官的许可,某些有限的情况除外。第41条规则的修订将会削弱这条限制,允许政府在某一管辖区域申请许可,执行对位于另一管辖区域的计算机的远程搜索。修订将会适用于下列情况:
* 有人采用“技术手段”隐藏其计算机的位置时;或
* 调查僵尸网络时,此时受损的计算机位于5个或更多行政区。

50家组织 – 包括公共利益集团、隐私工具提供商以及因特网公司 – 团结起来,大声疾呼,反对修订第41条规则。vpnMentor与noglobalwarrants.org协调一致,领导要求撤销所提议的第41条规则修订的全球性努力。虽然noglobalwarrants.org专注于美国公民,鼓励他们联系其国会代表,但对这条规则的全球性意识依然极为重要,因为美国政府也将能够侵入采用 VPN 或 Tor 浏览器的全球用户。这就是我们将“行动呼吁”翻译为26种语言、尽最大努力在全球分享的原因。

reject rule 41 changes

修订第41条规则错在哪里?

规则修订可能会极大增加执法人员侵入我们计算机的频率。这是因为修订条款将会授权这个国家几乎任何联邦治安法官签发这类许可。执法人员可择地诉讼,找到美国最利于起诉或最不懂技术的地方法官,签发这些危险的许可。根据这些规则的修订条款,如果人们的隐私保护技术使其计算机的位置模糊不清,美国几乎任何行政区的法官都可能授权执法人员远程搜索或侵入他们的计算机。这就意味着,极其在意隐私的人有可能受到这一规则修订大小不一的影响。

在很多情况下,地方法官可能会不顾其他国家的法律保护,在不知情的情况下签发侵入位于世界各地,而非只是美国的计算机的许可。

规则修订还会致使执法人员寻求远程搜索成千上万计算机的单一许可 – 违反美国宪法第四条修正案和国际人权法的保护条款。

非法侵入 – 秘密闯入计算机、复制数据、删除数据或执行代码 – 可能会对用户及其设备产生严重后果。比之僵尸网络本身,政府代表实际上可能会在僵尸网络调查期间对无辜用户的计算机造成更大的损害。如果国会采取极端措施,授权政府非法侵入,则必须对何时允许此类行动进行严格限制,对遵守美国宪法和国际法的用户给予强有力的保护。

如果美国国会不采取行动,这条新规则的更新则将于2016年12月1日生效。这就是我们必须大声疾呼,要求否决这一规则修订的原因。在社交网络和你的博客上分享这条帖子吧。

http://xbeta.info  ( 6793ee6ca67cfacc9d4333d9ae43a11b)

使用Python计算文本相似性之编辑距离

在做爬虫的时候,很容易保持一些相似的数据,这些相似的数据由于不完全一致,如果要通过人工一一的审核,将耗费大量的时间,在上一遍介绍simhash的文章中,提到了编辑距离,我们先来了解下什么是编辑距离。

编辑距离(Edit Distance),又称Levenshtein距离,是指两个字串之间,由一个转成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。一般来说,编辑距离越小,两个串的相似度越大。

例如将kitten一字转成sitting:(’kitten’ 和 ‘sitting’ 的编辑距离为3)

  • sitten (k→s)
  • sittin (e→i)
  • sitting (→g)

Python中的Levenshtein包可以方便的计算编辑距离,包的安装:

pip install python-Levenshtein

我们来使用下:

# -*- coding:utf-8 -*-
import Levenshtein
texta = '艾伦 图灵传'
textb = '艾伦•图灵传'
print Levenshtein.distance(texta,textb)

上面的程序执行结果为3,但是只改了一个字符,为什么会发生这样的情况?原因是Python将这两个字符串看成string类型,而在 string 类型中,默认的 utf-8 编码下,一个中文字符是用三个字节来表示的。解决办法是将字符串转换成unicode格式,即可返回正确的结果1。

# -*- coding:utf-8 -*-
import Levenshtein
texta = u'艾伦 图灵传'
textb = u'艾伦•图灵传'
print Levenshtein.distance(texta,textb)

接下来重点介绍下保重几个方法的作用:

Levenshtein.distance(str1, str2)

计算编辑距离(也成Levenshtein距离)。是描述由一个字串转化成另一个字串最少的操作次数,在其中的操作包括插入、删除、替换。算法实现:动态规划。

Levenshtein.hamming(str1, str2)

计算汉明距离。要求str1和str2必须长度一致。是描述两个等长字串之间对应位置上不同字符的个数。

Levenshtein.ratio(str1, str2)

计算莱文斯坦比。计算公式  r = (sum – ldist) / sum, 其中sum是指str1 和 str2 字串的长度总和,ldist是类编辑距离。注意这里是类编辑距离,在类编辑距离中删除、插入依然+1,但是替换+2。

Levenshtein.jaro(s1, s2)

计算jaro距离,Jaro Distance据说是用来判定健康记录上两个名字是否相同,也有说是是用于人口普查,我们先来看一下Jaro Distance的定义。

两个给定字符串S1和S2的Jaro Distance为:

Jaro-Distance

其中的m为s1, s2匹配的字符数,t是换位的数目。

两个分别来自S1和S2的字符如果相距不超过max

时,我们就认为这两个字符串是匹配的;而这些相互匹配的字符则决定了换位的数目t,简单来说就是不同顺序的匹配字符的数目的一半即为换位的数目t。举例来说,MARTHA与MARHTA的字符都是匹配的,但是这些匹配的字符中,T和H要换位才能把MARTHA变为MARHTA,那么T和H就是不同的顺序的匹配字符,t=2/2=1。

两个字符串的Jaro Distance即为:

jaro

Levenshtein.jaro_winkler(s1, s2)

计算Jaro–Winkler距离,而Jaro-Winkler则给予了起始部分就相同的字符串更高的分数,他定义了一个前缀p,给予两个字符串,如果前缀部分有长度为ι的部分相同,则Jaro-Winkler Distance为:

jaro_winkler

  • dj是两个字符串的Jaro Distance
  • ι是前缀的相同的长度,但是规定最大为4
  • p则是调整分数的常数,规定不能超过25,不然可能出现dw大于1的情况,Winkler将这个常数定义为0.1

这样,上面提及的MARTHA和MARHTA的Jaro-Winkler Distance为:

dw = 0.944 + (3 * 0.1(1 − 0.944)) = 0.961

个人觉得算法可以完善的点:

  • 去除停用词(主要是标点符号的影响)
  • 针对中文进行分析,按照词比较是不是要比按照字比较效果更好?

其他参考资料:

使用 ChatterBot构建聊天机器人

ChatterBot是一个基于机器学习的聊天机器人引擎,构建在python上,主要特点是可以自可以从已有的对话中进行学习。

安装调试最简单的聊天机器人

安装

pip install chatterbot

基本使用

#!/usr/bin/python
# -*- coding: utf-8 -*-
from chatterbot import ChatBot
from chatterbot.trainers import ChatterBotCorpusTrainer

chatbot = ChatBot("myBot")
chatbot.set_trainer(ChatterBotCorpusTrainer)

# 使用英文语料库训练它
chatbot.train("chatterbot.corpus.english")

# 开始对话
while True:
    print(chatbot.get_response(input(">")))

默认情况下, ChatterBot 使用JsonDatabaseAdapter作为storage adapter,使用ClosestMatchAdapter作为logic adapter, 使用VariableInputTypeAdapter作为input adapter。

storage adapter

ChatterBot创建之后,会建立一个类族适配器(adapter classes),在该适配器下该ChatterBot可以连接到不同类型的数据集。本文所采用的是JsonDatabaseAdapter,该Storage adapter是以json格式存储数据的。注意: JsonDatabaseAdapter 并不适用于海量数据,如果数据集过大该方法的性能将受到很大局限。

创建ChatterBot的时候可以在入参中指定JsonDatabaseAdapter,如下面的参数所示:

bot = ChatBot(
    "Norman"
    storage_adapter="chatterbot.adapters.storage.JsonDatabaseAdapter",
    database="./database.json"
)

其中的database参数是用以指定所创建的chat bot所使用数据集的位置。上述的定义吗,我们可以看出该chat bot所使用的数据集是database.json,如果该database.json不存在的话,则会自动创建。注意:JsonDatabaseAdapter是ChatterBot的默认adapter,可以缺省。

输入和输出adapters

在创建ChatBot的时候可以指定输入和输出终端adapter。输入终端adapter用以读取终端的输入,输入终端adapter则是打印出chat bot的应答信息。

使用如下:

bot = ChatBot(
    "Norman"
    storage_adapter="chatterbot.adapters.storage.JsonDatabaseAdapter",
    input_adapter="chatterbot.adapters.input.TerminalAdapter",
    output_adapter="chatterbot.adapters.output.TerminalAdapter",
    database="./database.json"
)

Logic adapters

在新建ChatBot的时候可以指定logic_adapters的值,该参数是一序列的logic adapter。在ChatBot中一个logic adapter就是一个类,这个类是用于接收输入的语句和反馈该输入的语句。

在logic adapter的使用数量上并不受限。下面的例子中可以看出,使用的是两个logic adapter。其中TimeLogicAdapter是返回当前时间,MathematicalEvaluation adapter则是用以计算问题的。

bot = ChatBot(
    "Norman"
    storage_adapter="chatterbot.adapters.storage.JsonDatabaseAdapter",
    input_adapter="chatterbot.adapters.input.TerminalAdapter",
    output_adapter="chatterbot.adapters.output.TerminalAdapter",
    logic_adapters=[
        "chatterbot.adapters.logic.MathematicalEvaluation",
        "chatterbot.adapters.logic.TimeLogicAdapter"
    ],
    database="./database.json"
)

创建自己的adapters参考默认使用的ClosestMatchAdapterVariableInputTypeAdapter。如果需要语音输入,则可以调用百度语音接口。

让机器人支持中文

Chatterbot提供一个公用模块进行数据集的训练,目前该模块集成7种语种的训练,包括英语,葡萄牙语、西班牙语、法语、印尼语、意大利语和中文。训练集存放在\Lib\site-packages\chatterbot\corpus\data目录下:

corpus

使用中文语料库进行训练(注意,这里只支持Python 3,否则会报编码错误),报错内容为:UnicodeDecodeError: ‘ascii’ codec can’t decode byte 0xe4 in position 0: ordinal not in range(128)

#!/usr/bin/python
# -*- coding: utf-8 -*-
from chatterbot import ChatBot
from chatterbot.trainers import ChatterBotCorpusTrainer

chatbot = ChatBot("myBot")
chatbot.set_trainer(ChatterBotCorpusTrainer)

# 使用英文语料库训练它
chatbot.train("chatterbot.corpus.chinese")

# 开始对话
while True:
    print(chatbot.get_response(input(">")))

ChatterBot也支持训练数据集的子集,如只想要训练英文问候语和对话,则只要将该两个子集导入进行训练即可:

chatterbot.train(
    "chatterbot.corpus.english.greetings",
    "chatterbot.corpus.english.conversations"
)

里面虽然包含各种语言,但是训练集的数据非常的小,如需更好的表现,需要自己添加更多的数据。

手动训练机器人

ChatterBot 内置training class,也可以根据自己的需要自行创建,通过调用train()函数之前先调用set_trainer() 来进行设置。使用方法如下:

#!/usr/bin/python
# -*- coding: utf-8 -*-

from chatterbot import ChatBot
from chatterbot.trainers import ListTrainer


my_bot = ChatBot("Training demo")
my_bot.set_trainer(ListTrainer)
my_bot.train([
    "嗳,渡边君,真喜欢我?",
    "那还用说?",
    "那么,可依得我两件事?",
    "三件也依得",
])

# test
print(my_bot.get_response("真喜欢我?"))
print(my_bot.get_response("可依得我两件事?"))

训练好的数据,默认存在./database.db(参考jsondatabase.py),不是sqlite数据库,实际是jsondb,对json做了封装(参考jsondb/db.py

只读模式

ChatterBot是会对每个输入的语句进行学习的。如果想要使得你已经训练过的bot不再继续学习输入的语句,可以通过以下方式进行设置,在初始化的时候将read_only设置为true。

chatbot = ChatBot("wwjtest", read_only=True) //否则bot会学习每个输入

最后一个问题,语料库可以从哪里来?

  • 电视电影字幕
  • 知识问答库

参考链接:

网页正文提取工具:Readability

从网页中提取出主要内容,一直是一个比较有挑战的算法。Readability是其中一个很不错的实现。Readability之前是开源的,后来不再公开了,这里还有当初的开源版本可以参考:arc90labs-readability – Readability cleans up hard-to-read articles on the Web

Readability通过遍历Dom对象,通过标签和常用文字的加减权,来重新整合出页面的内容。接下来我们就简单看看这个算法是如何实现的。首先,它定义了一系列正则:

regexps: {
        unlikelyCandidates:    /combx|comment|community|disqus|extra|foot|header|menu|remark|rss|shoutbox|sidebar|sponsor|ad-break|agegate|pagination|pager|popup|tweet|twitter/i,
        okMaybeItsACandidate:  /and|article|body|column|main|shadow/i,
        positive:              /article|body|content|entry|hentry|main|page|pagination|post|text|blog|story/i,
        negative:              /combx|comment|com-|contact|foot|footer|footnote|masthead|media|meta|outbrain|promo|related|scroll|shoutbox|sidebar|sponsor|shopping|tags|tool|widget/i,
        extraneous:            /print|archive|comment|discuss|e[\-]?mail|share|reply|all|login|sign|single/i,
        divToPElements:        /<(a|blockquote|dl|div|img|ol|p|pre|table|ul)/i,
        replaceBrs:            /(<br[^>]*>[ \n\r\t]*){2,}/gi,
        replaceFonts:          /<(\/?)font[^>]*>/gi,
        trim:                  /^\s+|\s+$/g,
        normalize:             /\s{2,}/g,
        killBreaks:            /(<br\s*\/?>(\s|&nbsp;?)*){1,}/g,
        videos:                /http:\/\/(www\.)?(youtube|vimeo)\.com/i,
        skipFootnoteLink:      /^\s*(\[?[a-z0-9]{1,2}\]?|^|edit|citation needed)\s*$/i,
        nextLink:              /(next|weiter|continue|>([^\|]|$)|»([^\|]|$))/i, // Match: next, continue, >, >>, » but not >|, »| as those usually mean last.
        prevLink:              /(prev|earl|old|new|<|«)/i
},

可以看到,标签和文字都有加权或降权分组。整个内容分析是通过grabArticle函数来实现的。首先开始遍历节点:

for(var nodeIndex = 0; (node = allElements[nodeIndex]); nodeIndex+=1)

然后将不像内容的元素去掉:

if (stripUnlikelyCandidates) 
{
    var unlikelyMatchString = node.className + node.id;
    if (
        (
            unlikelyMatchString.search(readability.regexps.unlikelyCandidates) !== -1 &&
            unlikelyMatchString.search(readability.regexps.okMaybeItsACandidate) === -1 &&
            node.tagName !== "BODY"
        )
    )
    {
        dbg("Removing unlikely candidate - " + unlikelyMatchString);
        node.parentNode.removeChild(node);
        nodeIndex-=1;
        continue;
    }               
}

将DIV替换为P标签后,再对目标节点进行遍历,进行计分:

var candidates = [];
for (var pt=0; pt < nodesToScore.length; pt+=1) {
    var parentNode      = nodesToScore[pt].parentNode;
    var grandParentNode = parentNode ? parentNode.parentNode : null;
    var innerText       = readability.getInnerText(nodesToScore[pt]);

    if(!parentNode || typeof(parentNode.tagName) === 'undefined') {
        continue;
    }

    /* If this paragraph is less than 25 characters, don't even count it. */
    if(innerText.length < 25) {
        continue; }

    /* Initialize readability data for the parent. */
    if(typeof parentNode.readability === 'undefined') {
        readability.initializeNode(parentNode);
        candidates.push(parentNode);
    }

    /* Initialize readability data for the grandparent. */
    if(grandParentNode && typeof(grandParentNode.readability) === 'undefined' && typeof(grandParentNode.tagName) !== 'undefined') {
        readability.initializeNode(grandParentNode);
        candidates.push(grandParentNode);
    }

    var contentScore = 0;

    /* Add a point for the paragraph itself as a base. */
    contentScore+=1;

    /* Add points for any commas within this paragraph */
    contentScore += innerText.split(',').length;
    
    /* For every 100 characters in this paragraph, add another point. Up to 3 points. */
    contentScore += Math.min(Math.floor(innerText.length / 100), 3);
    
    /* Add the score to the parent. The grandparent gets half. */
    parentNode.readability.contentScore += contentScore;

    if(grandParentNode) {
        grandParentNode.readability.contentScore += contentScore/2;             
    }
}

最后根据分值,重新拼接内容

var articleContent        = document.createElement("DIV");
if (isPaging) {
    articleContent.id     = "readability-content";
}
var siblingScoreThreshold = Math.max(10, topCandidate.readability.contentScore * 0.2);
var siblingNodes          = topCandidate.parentNode.childNodes;


for(var s=0, sl=siblingNodes.length; s < sl; s+=1) {
    var siblingNode = siblingNodes[s];
    var append      = false;

    /**
     * Fix for odd IE7 Crash where siblingNode does not exist even though this should be a live nodeList.
     * Example of error visible here: http://www.esquire.com/features/honesty0707
    **/
    if(!siblingNode) {
        continue;
    }

    dbg("Looking at sibling node: " + siblingNode + " (" + siblingNode.className + ":" + siblingNode.id + ")" + ((typeof siblingNode.readability !== 'undefined') ? (" with score " + siblingNode.readability.contentScore) : ''));
    dbg("Sibling has score " + (siblingNode.readability ? siblingNode.readability.contentScore : 'Unknown'));

    if(siblingNode === topCandidate)
    {
        append = true;
    }

    var contentBonus = 0;
    /* Give a bonus if sibling nodes and top candidates have the example same classname */
    if(siblingNode.className === topCandidate.className && topCandidate.className !== "") {
        contentBonus += topCandidate.readability.contentScore * 0.2;
    }

    if(typeof siblingNode.readability !== 'undefined' && (siblingNode.readability.contentScore+contentBonus) >= siblingScoreThreshold)
    {
        append = true;
    }
    
    if(siblingNode.nodeName === "P") {
        var linkDensity = readability.getLinkDensity(siblingNode);
        var nodeContent = readability.getInnerText(siblingNode);
        var nodeLength  = nodeContent.length;
        
        if(nodeLength > 80 && linkDensity < 0.25)
        {
            append = true;
        }
        else if(nodeLength < 80 && linkDensity === 0 && nodeContent.search(/\.( |$)/) !== -1)
        {
            append = true;
        }
    }

    if(append) {
        dbg("Appending node: " + siblingNode);

        var nodeToAppend = null;
        if(siblingNode.nodeName !== "DIV" && siblingNode.nodeName !== "P") {
            /* We have a node that isn't a common block level element, like a form or td tag. Turn it into a div so it doesn't get filtered out later by accident. */
            
            dbg("Altering siblingNode of " + siblingNode.nodeName + ' to div.');
            nodeToAppend = document.createElement("DIV");
            try {
                nodeToAppend.id = siblingNode.id;
                nodeToAppend.innerHTML = siblingNode.innerHTML;
            }
            catch(er) {
                dbg("Could not alter siblingNode to div, probably an IE restriction, reverting back to original.");
                nodeToAppend = siblingNode;
                s-=1;
                sl-=1;
            }
        } else {
            nodeToAppend = siblingNode;
            s-=1;
            sl-=1;
        }
        
        /* To ensure a node does not interfere with readability styles, remove its classnames */
        nodeToAppend.className = "";

        /* Append sibling and subtract from our list because it removes the node when you append to another node */
        articleContent.appendChild(nodeToAppend);
    }
}

可以看到,里边用到了很多很trick的技巧,比如25字以下的段落不计分。目前已经有很多其他语言的版本,但是考虑到原始功能是针对的英文页面进行设计的。在移植过程中需要考虑:

  • 支持GBK, GB2312等编码
  • 支持更多的视频网站,比如优酷、土豆等
  • 支持提取文章标题
  • 支持图片相对路径转成绝对路径

目前收集到的比较好的移植版本为:

漂亮的网页大纲视图显示工具:jQuery.fracs

jQuery.fracs是一个jquery插件,可用于网站大纲视图的显示,用来替代滚动条。

如何使用:

1、加载js

<script src="//cdn.bootcss.com/jquery-fracs/0.15.1/jquery.fracs.min.js"></script>
<script src="//cdn.bootcss.com/jquery-fracs/0.15.1/jquery.outline.min.js"></script>

2、添加节点

注意需要将元素固定下来不随滚动条滚动

<canvas id='outline' width='200' height='400'></canvas>

3、添加样式

(function($){
	$(document).ready(function(){
	$("#outline").fracs("outline", {
		crop: true,
		styles: [{
			selector: 'p',
			fillStyle: '#fff'
		},{
			selector: '.mybtn',
			fillStyle: 'rgb(104,169,255)'
		},{
			selector: 'h1',
			fillStyle: '#888',
		},{
			selector: 'h2,h3',
			fillStyle: '#aaa',
		},{
			selector:".next-page",
			fillStyle:"#eee"
		},{
			selector:"pre,svg,img",
			fillStyle:"#eee"
		}],
		viewportStyle: {
			fillStyle: 'rgba(104,169,255,0.1)',
		},
		viewportDragStyle: {
			fillStyle: 'rgba(104,169,255,0.3)'
		}
	});
});
})(jQuery);

项目地址:https://github.com/lrsjng/jquery-fracs

Category

Archives