首页 > 代码库 > PageRank网页排名算法

PageRank网页排名算法

互联网上各个网页之间的链接关系我们都可以看成是一个有向图,一个网页的重要性由链接到该网页的其他网页来投票,一个较多链入的页面会有比较高等级,反之如果一个页面没有链入或链入较少等级则低,网页的PR值越高,代表网页越重要

假设一个有A、B、C、D四个网页组成的集合,B、C、D三个页面都链入到A,则A的PR值将是B、C、D三个页面PR值的总和:

PR(A)=PR(B)+PR(C)+PR(D)

继续上面的假设,B除了链接到A,还链接到C和D,C除了链接到A,还链接到B,而D只链接到A,所以在计算A的PR值时,B的PR值只能投出1/3的票,C的PR值只能投出1/2的票,而D只链接到A,所以能投出全票,所以A的PR值总和应为:

PR(A)=PR(B)/3+PR(C)/2+PR(D)

所以可以得出一个网页的PR值计算公式应为:

技术分享

其中,Bu是所有链接到网页u的网页集合,网页v是属于集合Bu的一个网页,L(v)则是网页v的对外链接数(即出度)

 

技术分享

图1-1

 

表1-2 根据图1-1计算的PR值

 

PA(A)

P(B)

PR(C) PR(D)
初始值 0.25  0.25 0.25

 0.25

一次迭代  0.125  0.333  0.083

0.458

二次迭代  0.1665  0.4997 0.0417

 0.2912

……  …… …… ……

……

n次迭代  0.1999 0.3999 0.0666

0.3333

 

 表1-2,经过几次迭代后,PR值逐渐收敛稳定

然而在实际的网络环境中,超链接并没有那么理想化,比如PageRank模型会遇到如下两个问题:

  1.排名泄露 

  如图1-3所示,如果存在网页没有出度链接,如A节点所示,则会产生排名泄露问题,经过多次迭代后,所有网页的PR只都趋向于0

技术分享

图1-3

 

 

PR(A)

PR(B) PR(C) PR(D)
初始值

0.25

0.25 0.25 0.25
一次迭代

0.125

0.125 0.25 0.25
二次迭代

0.125

0.125 0.125 0.25
三次迭代

0.125

0.125 0.125 0.125
……

……

…… …… ……
n次迭代

0

0 0 0

 

表1-4为图1-3多次迭代后结果 

   2.排名下沉

   如图1-5所示,若网页没有入度链接,如节点A所示,经过多次迭代后,A的PR值会趋向于0

  技术分享

图1-5

 

 

PR(A)

PR(B) PR(C) PR(D)
初始值

0.25

0.25 0.25 0.25
一次迭代

0

0.375 0.25 0.375
二次迭代

0

0.375 0.375 0.25
三次迭代

0

0.25 0.375 0.375
……

……

…… …… ……
n次迭代

0

…… …… ……

表1-5为图1-4多次迭代后结果 

 

 

我们假定上王者随机从一个网页开始浏览,并不断点击当前页面的链接进行浏览,直到链接到一个没有任何出度链接的网页,或者上王者感到厌倦,随机转到另外的网页开始新一轮的浏览,这种模型显然更贴近用户的习惯。为了处理那些没有任何出度链接的页面,引入阻尼系数d来表示用户到达缪戈页面后继续向后浏览的概率,一般将其设为0.85,而1-d就是用户停止点击,随机转到另外的网页开始新一轮浏览的概率,所以引入随机浏览模型的PageRank公式如下:

技术分享

 技术分享

图1-6

代码1-7为根据图1-6建立数据集

root@lejian:/data# cat links 
A       B,C,D
B       A,D
C       D
D       B

 

GraphBuilder步骤的主要目的是建立网页之间的链接关系图,以及为每一个网页分配初始的PR值,由于我们的数据集已经建立好网页之间的链接关系图,所以在代码1-8中只要为每个网页分配相等的PR值即可

代码1-8

package com.hadoop.mapreduce;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class GraphBuilder {

	public static class GraphBuilderMapper extends Mapper<LongWritable, Text, Text, Text> {

		private Text url = new Text();
		private Text linkUrl = new Text();

		protected void map(LongWritable key, Text value, Context context) throws java.io.IOException, InterruptedException {
			String[] tuple = value.toString().split("\t");
			url.set(tuple[0]);
			if (tuple.length == 2) {
				// 网页有出度
				linkUrl.set(tuple[1] + "\t1.0");
			} else {
				// 网页无出度
				linkUrl.set("\t1.0");
			}
			context.write(url, linkUrl);
		};
	}

	protected static void main(String[] args) throws Exception {
		Configuration conf = new Configuration();
		Job job = Job.getInstance(conf);
		job.setJobName("Graph Builder");
		job.setJarByClass(GraphBuilder.class);
		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(Text.class);
		job.setMapperClass(GraphBuilderMapper.class);
		FileInputFormat.addInputPath(job, new Path(args[0]));
		FileOutputFormat.setOutputPath(job, new Path(args[1]));
		job.waitForCompletion(true);
	}

}

 

PageRankIter步骤的主要目的是迭代计算PageRank数值,直到满足运算结束条件,比如收敛或达到预定的迭代次数,这里采用预设迭代次数的方式,多次运行该步骤

代码1-9

package com.hadoop.mapreduce;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class PageRankIter {

	private static final double DAMPING = 0.85;

	public static class PRIterMapper extends Mapper<LongWritable, Text, Text, Text> {

		private Text prKey = new Text();
		private Text prValue = http://www.mamicode.com/new Text();"\t");
			if (tuple.length <= 2) {
				return;
			}
			String[] linkPages = tuple[1].split(",");
			double pr = Double.parseDouble(tuple[2]);
			for (String page : linkPages) {
				if (page.isEmpty()) {
					continue;
				}
				prKey.set(page);
				prValue.set(tuple[0] + "\t" + pr / linkPages.length);
				context.write(prKey, prValue);
			}
			prKey.set(tuple[0]);
			prValue.set("|" + tuple[1]);
			context.write(prKey, prValue);
		};
	}

	public static class PRIterReducer extends Reducer<Text, Text, Text, Text> {

		private Text prValue = http://www.mamicode.com/new Text();"";
			double pageRank = 0;
			for (Text val : values) {
				String tmp = val.toString();
				if (tmp.startsWith("|")) {
					links = tmp.substring(tmp.indexOf("|") + 1);
					continue;
				}
				String[] tuple = tmp.split("\t");
				if (tuple.length > 1) {
					pageRank += Double.parseDouble(tuple[1]);
				}
			}
			pageRank = (1 - DAMPING) + DAMPING * pageRank;
			prValue.set(links + "\t" + pageRank);
			context.write(key, prValue);
		};
	}

	protected static void main(String[] args) throws Exception {
		Configuration conf = new Configuration();
		Job job = Job.getInstance(conf);
		job.setJobName("PageRankIter");
		job.setJarByClass(PageRankIter.class);
		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(Text.class);
		job.setMapperClass(PRIterMapper.class);
		job.setReducerClass(PRIterReducer.class);
		FileInputFormat.addInputPath(job, new Path(args[0]));
		FileOutputFormat.setOutputPath(job, new Path(args[1]));
		job.waitForCompletion(true);
	}

}

 

 

PageRankViewer步骤将迭代计算得到的最终排名结果按照PageRank值从大到小的顺序进行输出,并不需要reduce,输出的键值对为<PageRank,URL>

代码1-10

package com.hadoop.mapreduce;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.FloatWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class PageRankViewer {

	public static class PageRankViewerMapper extends Mapper<LongWritable, Text, FloatWritable, Text> {

		private Text outPage = new Text();
		private FloatWritable outPr = new FloatWritable();

		protected void map(LongWritable key, Text value, Context context) throws java.io.IOException, InterruptedException {
			String[] line = value.toString().split("\t");
			outPage.set(line[0]);
			outPr.set(Float.parseFloat(line[2]));
			context.write(outPr, outPage);
		};

	}

	public static class DescFloatComparator extends FloatWritable.Comparator {

		public float compare(WritableComparator a, WritableComparable<FloatWritable> b) {
			return -super.compare(a, b);
		}

		public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) {
			return -super.compare(b1, s1, l1, b2, s2, l2);
		}
	}

	protected static void main(String[] args) throws Exception {
		Configuration conf = new Configuration();
		Job job = Job.getInstance(conf);
		job.setJobName("PageRankViewer");
		job.setJarByClass(PageRankViewer.class);
		job.setOutputKeyClass(FloatWritable.class);
		job.setSortComparatorClass(DescFloatComparator.class);
		job.setOutputValueClass(Text.class);
		job.setMapperClass(PageRankViewerMapper.class);
		FileInputFormat.addInputPath(job, new Path(args[0]));
		FileOutputFormat.setOutputPath(job, new Path(args[1]));
		job.waitForCompletion(true);
	}

}

 

 

代码1-11

package com.hadoop.mapreduce;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;

public class PageRankDriver {

	public static void main(String[] args) throws Exception {
		if (args == null || args.length != 3) {
			throw new RuntimeException("请输入输入路径、输出路径和迭代次数");
		}
		int times = Integer.parseInt(args[2]);
		if (times <= 0) {
			throw new RuntimeException("迭代次数必须大于零");
		}
		String UUID = "/" + java.util.UUID.randomUUID().toString();
		String[] forGB = { args[0], UUID + "/Data0" };
		GraphBuilder.main(forGB);
		String[] forItr = { "", "" };
		for (int i = 0; i < times; i++) {
			forItr[0] = UUID + "/Data" + i;
			forItr[1] = UUID + "/Data" + (i + 1);
			PageRankIter.main(forItr);
		}
		String[] forRV = { UUID + "/Data" + times, args[1] };
		PageRankViewer.main(forRV);
		Configuration conf = new Configuration();
		FileSystem fs = FileSystem.get(conf);
		Path path = new Path(UUID);
		fs.deleteOnExit(path);
	}

}

 

运行代码1-11,结果如代码1-12所示

代码1-12

root@lejian:/data# hadoop jar pageRank.jar com.hadoop.mapreduce.PageRankDriver /data /output 10
…… root@lejian:/data# hadoop fs -ls -R /output
-rw-r--r--   1 root supergroup          0 2017-02-10 17:55 /output/_SUCCESS
-rw-r--r--   1 root supergroup         50 2017-02-10 17:55 /output/part-r-00000
root@lejian:/data# hadoop fs -cat /output/part-r-00000
1.5149547       B
1.3249696       D
0.78404236      A
0.37603337      C

 

PageRank网页排名算法