网页词频统计工具

阅读英文文章时有时会出现不少这篇文章专有的一些单词,这些单词在其他地方不太可能会使用到,但是在阅读这篇文章时使用的频率可能会比较大,于是想能不能做一个工具,当你给定文章的url时,它将这篇文章中出现次数较多的那些单词统计出来。这样当你把这些单词的意义搞明白,再读这篇文章会不会压力小很多?

那么做这个工具的思路如下:

  1. 首先必须能够根据给定的url获取网页的正文信息或者网页的html文件;
  2. 如果第一步获取的是网页的html文件,那么需要根据这个html文件获取网页的正文信息;
  3. 网页的正文信息应该是包含字母,数字,各种标点符号,括号,空格,其他语言中的字符(比如说中文)等信息,但是在这里我要统计的是英文单词的频率,所以在这一步应该从正文信息中提取出单词(单词由26个大写字母和26个小写字母构成);
  4. 提取出英文单词以后就需要对这些单词进行统计,找出出现次数最多的前N(比如说10)个单词;
  5. 在第4步寻找过程中,某一些单词应该不参与统计,比如说a ,an ,the ,of ,and ,or ,are这类单词,这些不参加统计的单词可以由一个不参加统计的单词表表示。

根据上面的思路,每一步实现的解决方案为:

根据url获取网页的html文件,可以使用libcurl,这个库可以很方便的根据url下载html文件,然后根据html文件提取正文可以信息可以使用htmlcxx这个库,但是这个库05年就停止更新了,哪个时候的html语法和现在的都有很多区别,所以用htmlcxx提取html中的正文时有些标签识别不了,在提取正文这一步就无法处理,继续查阅了其它一些库,发现都好久没更新了,提取正文时也都会有一些小的问题。可能C++的确不太适合做这方面的工作,于是决定用java编写。

使用java编写每一步的解决方法如下:

  1. java就犀利多了,根据url通过htmlparse这个库解析就可以获取正文,但是有些url可以解析,有些又无法解析,查了下这个库2006年开始也没更新了,可能是这个原因吧。
  2. 接着继续查,发现了一个神器jsoup,这个库网上文档也比较丰富,最重要的是它现在还在更新中,并且支持html5标准,所以用这个库提取网页的正文时就没有碰到上面那些库时遇到的问题了。
  3. 提取正文后需要从正文字符串中获取单词,这里就可以使用java强大的正则表示了,java的正则表达式可以直接从给定的字符串中匹配到符合要求的子字符串。
  4. 获取单词后就要进行排序,我使用C++ reference中的页面发现一个页面大约的单词数量是一千多个,当然,这也是由于这个页面比较简单,我们需要的是统计出现次数最多的前N个单词。这里面的工作需要分成两步:1.统计每个单词出现的次数;2.找到了出现次数最多的前N个单词,更加人性化的设计是找到这N个单词后再对这N个单词进行降序排列。

这里的第4步是最关键的一步,为了统计每个单词出现的次数,可以使用HashMap和Trie树(Trie树的讲解可以参考这篇博客),使用Trie树能够在O(L)的时间内执行插入操作(L代表字符串长度),HashMap因为要计算字符串的hashcode,所以应该也需要O(L)的操作。但是Trie没有HashMap需要处理碰撞的问题。所以这里使用Trie树来实现单词的统计。统计完后需要找到出现次数最多的N个单词,这又是一个经典的问题,就是使用一个大小为N的最小堆,遍历完Trie树后的最小堆中的元素就是出现次数最多的N个元素。最后对这N个元素执行排序操作。

在使用深度优先遍历Trie树的过程中,需要判断单词是否在不参加统计的单词表中,如果在那么不做任何处理,否则将它与最小堆(Java中堆中通过PriorityQueue这个类实现的)中的堆顶元素比较,如果它比最小堆的堆顶元素小,不做处理,否则将堆顶元素出队,并将该元素放入堆中。

下面是一些基本的数据结构和算法:

1.Trie树节点类型

//Trie树节点数据结构
public class TrieNode {
	public int words;//以该节点结尾的单词数量
	public int prefixs;//以该节点作为前缀的单词数量
	public String str;//以该节点结尾的单词
	public TrieNode[] edges;//该节点的子节点

	public TrieNode() {
		this.words=0;
		this.prefixs=0;
		this.str=null;
		edges=new TrieNode[26];
		for(int i=0;i<edges.length;i++)
		{
			edges[i]=null;
		}
	}
}

2.Trie树类型

包括下面这些内容:

  1. 一个Trie树根节点的TrieNode类型成员变量,为root,用来表示Trie树;
  2. 一个最小堆(PriorityQueue类型的成员变量),为priorityQueue,用来遍历Trie树时记录出现次数最多的N个节点;
  3. 一个不需要统计的单词表(Set<String>类型的成员变量),为tables,用来记录那些不需要统计的单词;
  4. 往Trie树种插入字符串的方法, void insert(String word);
  5. 对Trie树进行深度遍历的方法,void traversal(),遍历的过程中会将字符串和不要统计的单词做比较,将符合要求的字符串再和priorityQueue中的元素做比较,遍历完后priorityQueue中保存的元素是出现次数最多的N个单词;
  6. 获取出现次数最多的N个单词的方法,List<TrieNode> getTrieNodes(),这个方法会将最小堆中的元素放入集合中,并执行排序操作。
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Trie {

	private TrieNode root=new TrieNode();

	private int count=10;//要显示的前count个单词
	private int init;//这个初始值和count关联,用来向最小堆中插入初始的一些元素

	//定义一个最小堆
	private Queue<TrieNode> priorityQueue;

	//定义不需要统计的单词表
	private Set<String>  tables;

	//因为是要求出现次数最多的那些元素,所以使用最小堆
	private Comparator<TrieNode> wordOrder=new Comparator<TrieNode>() {

		@Override
		public int compare(TrieNode o1, TrieNode o2) {
			// TODO Auto-generated method stub
			int word1=o1.words;
			int word2=o2.words;
			if (word1<word2) {
				return -1;
			}
			else if (word1>word2) {
				return 1;
			}
			else
				return 0;
		}
	};

	public Trie() {
		// TODO Auto-generated constructor stub
		init=count;
		priorityQueue=new PriorityQueue<TrieNode>(count,wordOrder);

		//构建不需要统计的单词表
		tables=new HashSet<String>();
        String s = "[A-Za-z]+";
        //下面列出不需要统计的单词
        String words="a is an the h type c to of and or in for at";
        Pattern  pattern=Pattern.compile(s);
        Matcher  ma=pattern.matcher(words);
        while(ma.find()){
            tables.add(ma.group());
        }
	}

	public void setCount(int c) {
		count=c;
		init=c;
	}

	//往Trie树种插入单词
	public void insert(String word) {
			insertHelper(root, word);
	}

	//真正执行插入操作的函数
	private void insertHelper(TrieNode node,String word) {
		if (word.length()==0) {
			node.words++;
			node.prefixs++;
			return;
		}

		node.prefixs++;
		char c=word.charAt(0);
		c=Character.toLowerCase(c);
		int index=c-'a';
		if (node.edges[index]==null) {
			node.edges[index]=new TrieNode();
		}
		insertHelper(node.edges[index], word.substring(1));
	}

	public void traversal() {
		TrieNode[] edges=root.edges;

		for(int i=0;i<edges.length;i++)
		{
			if (edges[i]!=null) {
				String word=""+(char)('a'+i);
				depthTraversal(edges[i], word);
			}

		}
	}

	//对某一个节点执行深度优先的遍历
	private void depthTraversal(TrieNode node,String wordPrefix) {
		if (node.words!=0) {
			node.str=wordPrefix;
			if (tables.contains(node.str)) {
				//如果该词在不需要统计的单词表中,不需要做任何处理
			}
			else if(init>0)
			{
				init--;
				priorityQueue.add(node);
			}
			else {
				//如果大于最小堆的堆顶元素,那么将堆顶元素出队
				if (node.words>priorityQueue.peek().words) {
					priorityQueue.poll();
					priorityQueue.add(node);
				}
			}
		}
		TrieNode[] edges=node.edges;
		for(int i=0;i<edges.length;i++)
		{
			if (edges[i]!=null) {
				String newWord=wordPrefix+(char)('a'+i);
				depthTraversal(edges[i], newWord);
			}
		}
	}

	public List<TrieNode> getTrieNodes() {

		List<TrieNode> list=new ArrayList<TrieNode>();

		//先将优先队列中的元素取出,然后进行排序
		while (!priorityQueue.isEmpty()) {
			list.add(priorityQueue.poll());
		}

		//执行降序排列
		Collections.sort(list,new Comparator<TrieNode>() {

			@Override
			public int compare(TrieNode o1, TrieNode o2) {
				// TODO Auto-generated method stub
				if (o1.words<o2.words) {
					return 1;
				}
				else if(o1.words>o2.words){
					return -1;
				}
				else {
					return 0;
				}
			}
		});

		return list;
	}
}

3.根据url获取网页正文的方法:

    //根据网页的url将从网页抓取的单词插入tire树中
    public static void getText3(String url,Trie trie) {
        Document doc;
        try {
        	doc=Jsoup.connect(url).userAgent("Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537.31").get();
            String html=doc.text();
      //      System.out.println(html);
            String s = "[A-Za-z]+";//匹配单词,但是由英文大小写字母组成
          //  String s="\\w+";
            Pattern  pattern=Pattern.compile(s);
            Matcher  ma=pattern.matcher(html);
            while(ma.find()){
              //  System.out.println(ma.group());
            	trie.insert(ma.group());//每次匹配一个单词,就将它插入Trie树中
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

那么只需要在main函数中这样调用就可以进行统计输出了:

	public static void main(String[] args) {
	//	GUIFrame frame=new GUIFrame();
		String url="http://www.cplusplus.com/reference/deque/deque/";
		Trie trie=new Trie();
		getText3(url, trie);
		trie.traversal();
		List<TrieNode> list=trie.getTrieNodes();
		//打印输出出现次数最多的那些字符串
		for(TrieNode t:list)
		{
			System.out.println(t.str+"  "+t.words);
		}

	}

上面会统计http://www.cplusplus.com/reference/deque/deque/这个网页上出现频率最高的10个单词:

在这里我定义的不需要参与统计的单词是:String words="a is an the h type c to of and or in for at";可以发现这些都是一些比较常用的,类似中文中“的”,“地”,“吗”,“把”,“一”之类的这种单词

输出:统计的结果应该还算比较准吧,这个页面是讲解deque的用法的,deque排第一位,出现了56次。

上面就是基本的算法了,但是这样的话就只能在Eclipse中运行,并且要改变统计的网页或者显示最多多少个单词时时还需要更改源代码,所以我给它做了一个简单的界面。

界面类的源码就不列出来了,整个工程的源码可以参考附件

在url栏中输入要统计网页的url,在出现次数最多后面的文本框中输入要显示出现次数最多的多少个单词,点击统计按钮,下面的文本框中就会显示出现次数最多的那些单词和它的出现次数,最下面显示的这次查询和统计花费了多少时间。可执行jar文件的下载可以参考附件

参考的一些文章:

jsoup开发指南

Java正则表达式

PriorityQueue说明

HashSet用法

Java中对list排序

TextArea用法

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-07-28 16:26:28

网页词频统计工具的相关文章

词频统计-功能一

一.完成一个小程序 我 拿到这个题目之后,就决定用最不熟悉的c#来实现,因为老师说不懂的去学才会有进步.布置任务后的第二天就开始去图书馆借了两本书<c#从入门到精髓>,<c#项目实战>,拿到书之后看了入门书<c#从入门到精髓>,看书的过程时痛苦的,因为发现大二选修课学的c#全交还给老师了,只能重头再学了.唯一有点印象的就是窗口应用程序,基于UI的设计. 写代码首先需要工具,由于电脑上没有visual studio的安装包,当时求助了度娘. 如果没有安装包的同学们,可以借

结对项目 - 词频统计Ⅱ

目的与要求 代码复审练习 结对练习 编写单元测试 基于上一个结对项目的结果,读取小文本文件A_Tale_of_Two_Cities.txt 或者 大文本文件Gone_with_the_wind.txt,统计某一指定单词在该文本文件中出现的频率. 命令行格式: 提示符> Myapp.exe -f filename.txt -w word (PS:C++ 程序,Java 程序输出方式类似) 解释: 选项 -f 表示打开某一文件 选项 -w 表示统计其后单词在打开的文件中的频率 详细内容 开发语言:J

个人项目——词频统计

前言: 开发工具:Visual Studio 2013 开发语言:C++ 源代码管理工具:Github Github源代码网址:https://github.com/superyy/YY1/blob/master/%E8%AF%8D%E9%A2%91%E7%BB%9F%E8%AE%A1main.cpp 预计各功能所花时间:some hours 实际各功能所花时间:some hours 性能提高所花时间:some hours 要求 :实现一个控制台程序,给定一段英文字符串,统计其中各个英文单词(4

实验二-3 Hadoop&amp;Paoding 中文词频统计

  参考教程 在Hadoop上使用庖丁解牛(较复杂,并未采用,可以之后试试) http://zhaolinjnu.blog.sohu.com/264905210.html Lucene3.3.Lucene3.4中文分词——庖丁解牛分词实例(屈:注意版本) http://www.360doc.com/content/13/0217/13/11619026_266124504.shtml 庖丁分词在hadoop上运行时的配置问题(采纳了一半,没有按照其所写配置dic属性文件) http://f.da

一站式学习Wireshark(七):Statistics统计工具功能详解与应用

Wireshark一个强大的功能在于它的统计工具.使用Wireshark的时候,我们有各种类型的工具可供选择,从简单的如显示终端节点和会话到复杂的如Flow和IO图表.本文将介绍基本网络统计工具.包括:捕捉文件摘要(Summary),捕捉包的层次结构(Protocol Hirarchy), 会话(Conversations), 终端节点(Endpoints), HTTP. 更多信息 Summary: 从statistics菜单,选择Summary: 如下图的截屏所示,你会看到: File: 捕捉

结对项目 - 词频统计

目的与要求 代码复审练习 结对练习 编写单元测试 基于作业3的结果,读取一个较小的文本文件A_Tale_of_Two_Cities.txt,统计该文件中的单词的频率,并将统计结果输出到当前目录下的 Result1.txt 文件. (第一阶段初稿完成该要求) 命令行格式: 提示符> Myapp.exe -f filename.txt > Result.txt (PS:C++ 程序,Java 程序输出方式类似) filename.txt 为前面下载的文件名. 解释: 选项 -f 表示后面跟文件名

Hadoop的改进实验(中文分词词频统计及英文词频统计)(4/4)

声明: 1)本文由我bitpeach原创撰写,转载时请注明出处,侵权必究. 2)本小实验工作环境为Windows系统下的百度云(联网),和Ubuntu系统的hadoop1-2-1(自己提前配好).如不清楚配置可看<Hadoop之词频统计小实验初步配置> 3)本文由于过长,无法一次性上传.其相邻相关的博文,可参见<Hadoop的改进实验(中文分词词频统计及英文词频统计) 博文目录结构>,以阅览其余三篇剩余内容文档. (五)单机伪分布的英文词频统计Python&Streamin

词频统计单元测试

我这次用构造单词树的形式进行词频统计,此次的任务是对已有的程序进行单元测试.选用的工具是JUnit.它是基于测试驱动开发(TDD)原理的. 此次词频统计的主体思想是,每次读入文章中的128(自己设定)个字符(目的是防止溢出),将这些字符存储到一颗树中,树中的节点有一个存储词频的变量和一个指向子节点的数组(类似于c语言中的指针).最后遍历整棵树,按照词频进行排序. 下面是我的片段代码 下面这段代码是定义的节点的结构 class CharTreeNode{ int count=0; CharTree

效能分析——词频统计的java实现方法的第一次改进

java效能分析可以使用新版本jdk自带的jvisualvm工具进行统计 由于词频统计的运行在本人使用的机器上运行很快,无法被jvisualvm捕捉到线程的运行,所以捕捉的是eclipse的运行波动间接反映词频统计的效能 捕捉到的快照如下: 词频统计处理的文件为WarAndPeace,大小3282KB约3.3MB,输出结果到文件 在程序本身内开始和结束分别加入时间戳,差值平均为480-490ms.