前情提要:飛物作者屢次四級考試未能透過,進而惱羞成怒,製作了基於Hadoop實現的對歷年四級單詞的詞頻分析專案,希望督促自己儘快透過四級(然而並沒有什麼卵用)
專案需求:Pycharm、IDEA、Linux、Hadoop執行環境、Hive、beeline、八爪魚採集器
資料來源:https://zhenti.burningvocabulary.cn/cet4
“如果你想要資料,就得自己來拿,這規矩你早就懂得” ——某V姓男子
一、 資料採集
1.從目標網站上獲取所需要的網址
用來獲取資料的網站是一個由主介面指向各個題目頁面的分支結構,所以需要使用Python爬蟲從主介面獲取每一個題目頁面的網址
# 從該四級真題主網站上獲取各個具體題目頁面的連結網址
import re
import requests
# 防止爬蟲被攔截
header = {
'User-Agent': 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.193 Safari/537.36'
}
httpsGet = requests.get("https://zhenti.burningvocabulary.cn/cet4", headers=header) # 爬取整個頁面
httpsTxt = open(r"E:\ShuJu\SiJiDanCi\AllWWW.txt", 'w', encoding='utf-8') # 建立儲存網站資訊的txt檔案(真正的英語大佬都在使用拼音來命名資料夾)
httpsTxt.write(httpsGet.text) # 將爬取資訊存入txt
httpsTxt.close() # 關閉並儲存檔案
a = open(r"E:\ShuJu\SiJiDanCi\AllWWW.txt", encoding='utf-8').read() # a為主網頁的資訊
httpsAns = re.findall(
r'<a class="link-primary" href="(.*?)"><div class="card">', a, # 正規表示式查詢各頁面網址
re.S)
httpsTxt.close() # 關閉存有主網站資訊的txt
print(httpsAns) # 輸出正規表示式的查詢結果
with open(r"E:\ShuJu\SiJiDanCi\cet4WWW.txt", 'w', encoding="UTF8") as f: # 將結果存入檔案
for item in httpsAns:
f.write(str(item) + "\t")
f.close()
獲得了每一期題目的頁面地址
2.使用八爪魚採集器獲取題目資料
(飛物作者用自己的爬蟲爬了一天都沒能拿到資料,只好藉助外力)
點選新建,自定義任務,把需要爬取的網址全部複製貼上過去,儲存設定,然後在頁面中點選你需要的文字,然後點選採集按鈕就可以啟動採集了
採集完成後獲得一個存有歷年所有英語題目的csv格式檔案shuju.csv
之後對該csv檔案進行資料處理
二、資料處理
使用Python將存有所有英語題目中的無用資料剔除,獲得僅存有所有單詞的文字資料
# 將csv格式的檔案轉換為txt格式
import pandas as pd
df = pd.read_csv(r"E:\ShuJu\SiJiDanCi\shuju.csv", index_col=0)
print(df)
df.to_csv(r"E:\ShuJu\SiJiDanCi\shuju.txt", sep='\t', index=False)
#清洗無用資料
import re
with open(r"E:\ShuJu\SiJiDanCi\shuju.txt", "r", encoding="UTF8") as f: # 讀入原始資料
data = f.read()
f.close()
str = re.sub(r"[^a-zA-Z]+", " ", data) # 將非字母型的字元全部替換為空格
str = str.lower() # 將大寫字母替換為小寫字母
print(str) # 將資料處理情況顯示在控制檯,檢視效果
with open(r"E:\ShuJu\SiJiDanCi\sijiShaitext.txt", 'w', encoding="UTF8") as f: # 將資料寫出
f.write(str)
f.close()
最終效果長這個模樣
三、Hadoop計算
作者在這裡使用三臺Linux虛擬機器搭建的完全分散式Hadoop叢集來進行計算
1.詞頻統計
首先是大家耳熟能詳的wordcount計數 : map和reduce操作
開啟IDEA,複製貼上程式碼然後導包
Driver
public class WordCountDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
// 1 獲取配置資訊以及獲取job物件
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
// 2 關聯本Driver程式的jar
job.setJarByClass(WordCountDriver.class);
// 3 關聯Mapper和Reducer的jar
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
// 4 設定Mapper輸出的kv型別
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
// 5 設定最終輸出kv型別
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 6 設定輸入和輸出路徑
FileInputFormat.setInputPaths(job, new Path("E:\\ShuJu\\SiJiDanCi\\sijiShaitext.txt"));
FileOutputFormat.setOutputPath(job, new Path("E:\\ShuJu\\SiJiDanCi\\sijiOUT.txt"));
// 7 提交job
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
}
}
Mapper
public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
Text outK = new Text();
IntWritable outV = new IntWritable(1);
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//map被迴圈遍歷,讀取檔案中的每一行字串,重寫map方法
//1、獲取一行存入字串line
String line = value.toString();
//2、切割存入名為words的字串陣列
String[] words = line.split(" ");//以空格為分界線拆分單詞
//3、迴圈寫出,讀取words中的各個字串
for (String word : words) {//從words陣列中讀出的字串word
//封裝outK
outK.set(word);
//寫出
context.write(outK, outV);
}
}
}
Reducer
public class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
int sum;
IntWritable value = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
// 1 累加求和
sum = 0;
for (IntWritable count : values) {
sum += count.get();//資料型別切換為int
}
// 2 輸出
value.set(sum);
context.write(key, value);
}
}
這樣就得到了初步的詞頻統計結果,預設是按照字母表順序來排列的
接下來進行一個排序,可以更直觀的看出單詞的出現情況
(作為一個懶人肯定要挑出現次數高的單詞來背)
2.按照單詞出現次數遞減排序
FlowBean
public class FlowBean implements WritableComparable<FlowBean> {
private long num; //每個單詞的個數
//提供無參構造
public FlowBean() {
}
public long getnum() {
return num;
}
public void setnum(long num) {
this.num = num;
}
@Override
public void write(DataOutput out) throws IOException {
out.writeLong(this.num);
}
@Override
public void readFields(DataInput in) throws IOException {
this.num = in.readLong();
}
@Override
public String toString() {
return String.valueOf(num);
}
@Override
public int compareTo(FlowBean o) { //按照單詞個數進行遞減排序
if (this.num > o.num) {
return -1;
} else if (this.num < o.num) {
return 1;
} else {
return 0;
}
}
}
Driver
public class FlowDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//1 獲取job物件
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
//2 關聯本Driver類
job.setJarByClass(FlowDriver.class);
//3 關聯Mapper和Reducer
job.setMapperClass(FlowMapper.class);
job.setReducerClass(FlowReducer.class);
//4 設定Map端輸出資料的KV型別
job.setMapOutputKeyClass(FlowBean.class);
job.setMapOutputValueClass(Text.class);
//5 設定程式最終輸出的KV型別
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);
//6 設定輸入輸出路徑
FileInputFormat.setInputPaths(job, new Path("E:\\ShuJu\\SiJiDanCi\\sijiOUT.txt\\part-r-00000"));
FileOutputFormat.setOutputPath(job, new Path("E:\\ShuJu\\SiJiDanCi\\sijiOUTCompare"));
//7 提交Job
boolean b = job.waitForCompletion(true);
System.exit(b ? 0 : 1);
}
}
Mapper
public class FlowMapper extends Mapper<LongWritable, Text, FlowBean, Text> {
private FlowBean outK = new FlowBean();
private Text outV = new Text();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
String[] split = line.split("\t");
try {
outK.setnum(Long.parseLong(split[1].trim()));
} catch (ArrayIndexOutOfBoundsException e) {
outK.setnum(Long.parseLong(split[0].trim()));
}
outV.set(split[0]);
//4 寫出outK outV
context.write(outK, outV);
}
}
Reducer
public class FlowReducer extends Reducer<FlowBean, Text, Text, FlowBean> {
@Override
protected void reduce(FlowBean key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
for (Text value : values) {
context.write(value, key);
}
}
}
這樣就得到了我們想要的資料
四、Hive儲存
接下來將資料存入Hive表格便於查詢
首先把上面處理好的單詞檔案上傳至HDFS中的/test資料夾中
然後在HDFS上建立一個表來存入資料,位置在/test/WordsData
使用Linux上的beeline客戶端連線Hive,輸入建表語句,word列存單詞,num列存對應出現次數
CREATE EXTERNAL TABLE Words
(
word STRING,
num INT
)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
LINES TERMINATED BY '\n'
STORED AS TEXTFILE
LOCATION '/test/WordsData';
將文字檔案中的單詞資料寫入Hive表格
LOAD DATA INPATH '/test/part-r-00000' OVERWRITE INTO TABLE Words;
接下來就可以使用SQL語句愉快的查詢各種單詞的資料了
select * from Words where num>100;
想必這些操作對大家來說簡直有手就行
點我跳轉 Ciallo~(∠・ω< )⌒★