一、什么是PageRank
PageRank,又称网页排名、谷歌左侧排名,是一种由搜索引擎根据网页之间相互的超链接计算的技术,而作为网页排名的要素之一,以Google公司创办人拉里·佩奇(Larry Page)之姓来命名。Google用它来体现网页的相关性和重要性,在搜索引擎优化操作中是经常被用来评估网页优化的成效因素之一。
二、实现公式
对于一个页面A,那么它的PR值为:
-
R(A) 是页面A的PR值
-
PR(Ti)是页面Ti的PR值,在这里,页面Ti是指向A的所有页面中的某个页面
-
C(Ti)是页面Ti的出度,也就是Ti指向其他页面的边的个数
-
d 为阻尼系数,其意义是,在任意时刻,用户到达某页面后并继续向后浏览的概率,该数值是根据上网者使用浏览器书签的平均频率估算而得,通常d=0.85
-
N为页面总数
三、项目梳理
1.项目流程
分为两个流程:Mapper和Reducer
Mapper:将文件输出成 网页+pr值 的形式
Reducer:将Mapper端输入的文件输出成 网页+pr值+出链 的形式
通过迭代的方式使pr值收敛。
2.项目难点
Mapper端:
1、 如何将数据切分
2.、切分后如何求值
3、求值后如何输出
Reduce端:
1、如何取出数据值
2、取出数据后如何求值
3、求值后如何输出
Driver时:
通过哪种方法进行迭代,可以使数据在迭代时被正确处理
3.解决方法
Mapper端:
1、如何将数据切分
数据的形式为:
1 0.25 2 4
2 0.25 3
3 0.25 1 2
4 0.25 2 3
其中网页与pr值中是用\t分隔的pr值与出链网页中均由空格分隔
此时考虑采用split()将value分隔,用split("\t")分隔成两段,后将第二段用split(" “)将pr值和出链网页分隔开。因为迭代时key与value中间会有一个”\t",所以考虑将文件输入设置为以上形式。
2、切分后如何求值与如何输出
Mapper端目标输出文件形式:
1 0.125
4 0.125
3 0.25
…
另外的,考虑到迭代问题,一个Mapper还应输出
1 _2 4
代表网页1的出链网页有2、4
所以在切分后,先将切分出的出链网页和对应的pr值(PR(Ti)/C(Ti))作为key-value写出,再将行数据的第一部分(元网页)与所有出链网页作为key-value写出。
Reducer端:
1、如何取数据
此时Mapper端输出的数据形式为:
1 0.125
4 0.125
1 _2 4
…
此时,输入数据中含有两种数据,一种是用于下一步进行PR值计算的数据,一种是写明网页及其出链网页的数据。此时考虑用contains()方法,通过判断是否含有"_“来判断是将此行数据取出计算PR值还是将此行作为value的后半部分。
2、如何计算数据
通过迭代器取出相同网页收到的PR值,再通过(1 - d) / n + d * pr_sum 计算出每个网页的pr值。或取出出链网页,将其存入变量用于下一步输出。
3、求值后如何输出
key:从Mapper端输入的key就是求出PR值对应的网页,所以不用更改直接继续输出
value:value分为两部分(pr值+” "+出链网页)
Driver时
使用for循环进行迭代,通过path+i的方式对每次迭代的文件进行命名。设置完输入输出路径后,将输出路径赋值给出入路径,作为下次调用时的输入路径。通过此方法递归读取数据,直至数据收敛。
注意:job提交后不能使用System.exit()来查看是否执行完毕,否则MR只会运行一次
四、项目Demo
Mapper端:
package com.wuyik.pagerank;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
//将传入的文件输出为 网页+pr值 的形式
public class PageRankMapper extends Mapper<Object, Text, Text, Text> {
private Text urlKey = new Text(); //输出的key值 网页url
@Override
protected void map(Object key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
//判断文件是否为part-r-00000
if (line.substring(0, 1).matches("[0-9]{1}")) {
boolean flag = false;
//开始递归调用时(第一次文件中不含有代表url的“_”)
if (line.contains("_")){
line = line.replace("_","");
flag = true;
}
String[] values = line.split("\t");
Text text = new Text(values[0]);
String[] urlValue = values[1].split(" ");
String url = "_";
double pr = 0; //PR(Ti)
int num = 0; //C(Ti)
int i = 0; //遍历数组urlValue时使用
if (flag){ //递归时
i = 2;
pr = Double.parseDouble(urlValue[1]);
num = urlValue.length - 2;
} else { //首次进入Mapper时
i = 1;
pr = Double.parseDouble(urlValue[0]);
num = urlValue.length - 1;
}
for(; i< urlValue.length; i++){
url += urlValue[i] + " ";
urlKey.set(urlValue[i]);
Text urlResult = new Text(String.valueOf(pr / num));
context.write(urlKey,urlResult);
}
//再次将网页和对应的出链输出
context.write(text,new Text(url));
}
}
}
Reducer端:
package com.wuyik.pagerank;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
//将Map端输入的数据输出为 网页+pr值+出链网页 的形式
public class PageRankReducer extends Reducer<Text, Text, Text, Text> {
private Text result = new Text();
@Override
protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
double sum = 0;
String url = "";
for (Text value : values) {
//如果结果中含有"_"说明此行记录为 网页+外链 不参与计算
if (value.toString().contains("_")) {
url = value.toString();
} else {
sum += Double.parseDouble(value.toString());
}
}
//设置阻尼值d=0.85
double d = 0.85;
//求pr
double pr = (1 - d) / 4 + d * sum;
String str = String.format("%.6f",pr);
result.set(new Text(str + " " + url));
context.write(key,result);
}
}
Driver端:
package com.wuyik.pagerank;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
public class PageRankDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
String paths = "E:\\PageRank\\out\\00";
String path1 = paths;
String path2 = "";
for (int i = 1; i <= 20; i++) {
path2 = paths + i;
Job job = Job.getInstance(new Configuration());
job.setJarByClass(PageRankDriver.class);
job.setMapperClass(PageRankMapper.class);
job.setCombinerClass(PageRankReducer.class);
job.setReducerClass(PageRankReducer.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
FileInputFormat.setInputPaths(job, new Path(path1));
FileOutputFormat.setOutputPath(job, new Path(path2));
path1 = path2;
boolean b = job.waitForCompletion(true);
}
}
}