基於Hadoop框架實現的對歷年四級單詞的詞頻分析(入門級Hadoop專案)

你这过氧化氢掺水了發表於2024-05-01

前情提要:飛物作者屢次四級考試未能透過,進而惱羞成怒,製作了基於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~(∠・ω< )⌒★

相關文章