天天看點

爬取百度學術文章及文本挖掘分析

  學了一段時間的爬蟲,給自己找一個小項目來練練手,爬取百度百科文章,之後結合自然語言處理分析文本之間的相似度和提取所有文章的重要資訊。

目标總覽

1. 爬取資料(selenium + BeautifulSoup)

2. 清洗資料(pandas + re)

3. 可視化展示(matplotlab + seaborn + plotly)

4. 詞雲展示(juba + wordart)

5. 文章相似度分析(juba + graphlab)

1. 爬取百度學術文章

首先,我們打開百度學術首頁:http://xueshu.baidu.com/

爬取百度學術文章及文本挖掘分析

可以看到我們需要填入關鍵詞,才能進行搜尋我們需要的類型文章,在此我以“牛肉品質”為例,進行搜尋。我們在搜尋欄中單擊滑鼠右鍵,在單擊檢查,檢視源碼。

爬取百度學術文章及文本挖掘分析

用相同的方法檢視“百度一下”。

爬取百度學術文章及文本挖掘分析

這樣做的目的是為了使用selenium進行自動輸入,并搜尋。

這裡寫一個方法,傳入一個參數——要輸入的關鍵詞。我是使用的谷歌浏覽器的driver,也可以使用PhantomJS無界面的driver。

from bs4 import BeautifulSoup
from selenium import webdriver
import time
import pandas as pd
import requests
import re
from collections import defaultdict

def driver_open(key_word):
    url = "http://xueshu.baidu.com/"
#     driver = webdriver.PhantomJS("D:/phantomjs-2.1.1-windows/bin/phantomjs.exe")    
    driver = webdriver.Chrome("D:\\Program Files\\selenium_driver\\chromedriver.exe")
    driver.get(url)
    time.sleep(10)
    driver.find_element_by_class_name('s_ipt').send_keys(key_word)
    time.sleep(2)
    driver.find_element_by_class_name('s_btn_wr').click()
    time.sleep(2)
    content = driver.page_source.encode('utf-8')
    driver.close()
    soup = BeautifulSoup(content, 'lxml')
    return soup
           

然後,進入搜尋界面,我們接着分析。我們需要抓取文章的題目,同時要進行翻頁爬取多頁。

爬取百度學術文章及文本挖掘分析

怎麼樣實作發現呢?我們點開多個頁面觀察網頁URL:

第一頁:

http://xueshu.baidu.com/s?wd=%E7%89%9B%E8%82%89%E5%93%81%E8%B4%A8&pn=0&tn=SE_baiduxueshu_c1gjeupa&ie=utf-8&f=3&sc_f_para=sc_tasktype%3D%7BfirstSimpleSearch%7D&sc_hit=1

第二頁:

http://xueshu.baidu.com/s?wd=%E7%89%9B%E8%82%89%E5%93%81%E8%B4%A8&pn=10&tn=SE_baiduxueshu_c1gjeupa&ie=utf-8&f=3&sc_f_para=sc_tasktype%3D%7BfirstSimpleSearch%7D&sc_hit=1

第三頁:

http://xueshu.baidu.com/s?wd=%E7%89%9B%E8%82%89%E5%93%81%E8%B4%A8&pn=20&tn=SE_baiduxueshu_c1gjeupa&ie=utf-8&f=3&sc_f_para=sc_tasktype%3D%7BfirstSimpleSearch%7D&sc_hit=1

可以發現這三頁URL中隻有一個地方發生了改變,就是“pn”的值,從0開始,然後每次遞增10,是以,我們通過這個就可以很好的實作翻頁了。

def page_url_list(soup, page=0):
    fir_page = "http://xueshu.baidu.com" + soup.find_all("a", class_="n")[0]["href"]
    urls_list = []
    for i in range(page):
        next_page = fir_page.replace("pn=10", "pn={:d}".format(i * 10))
        response = requests.get(next_page)
        soup_new = BeautifulSoup(response.text, "lxml")
        c_fonts = soup_new.find_all("h3", class_="t c_font")
        for c_font in c_fonts:
            url = "http://xueshu.baidu.com" + c_font.find("a").attrs["href"]
            urls_list.append(url)
    return urls_list
           

接下來就是對感興趣的地方實施抓取了。我們進入詳情頁,我們需要抓取的東西有:題目、摘要、出版源、被引用量,有關鍵詞。

爬取百度學術文章及文本挖掘分析

還是按照老方法,将這些需要爬取的東西一個一個檢查源碼,用CSS select 方法處理。

def get_item_info(url):
    print(url)
    # brower = webdriver.PhantomJS(executable_path= r"C:\\phantomjs-2.1.1-windows\\bin\\phantomjs.exe")
    # brower.get(url)
    # time.sleep(2)
    # more_text = brower.find_element_by_css_selector('p.abstract_more.OP_LOG_BTN')
    # try:
    #     more_text.click()
    # except:
    #     print("Stopping load more")
    # content_details = brower.page_source.encode('utf-8')
    # brower.close()
    # time.sleep(3)
    content_details = requests.get(url)
    soup = BeautifulSoup(content_details.text, "lxml")
    # 提取文章題目
    title = ''.join(list(soup.select('#dtl_l > div > h3 > a')[0].stripped_strings))
    # 提取文章作者
    authors = ''.join(str(author_) for author_ in list(soup.select('div.author_wr')[0].stripped_strings)[1:])
    # 提取摘要
    abstract = list(soup.select('div.abstract_wr p.abstract')[0].stripped_strings)[0].replace("\u3000", ' ')
    # 提取出版社和時間
    fir_publish_text = list(soup.select('p.publish_text'))
    if len(fir_publish_text) == 0:
        publish_text = "NA"
        publish = "NA"
        year = "NA"
    else:
        publish_text = list(soup.select('p.publish_text')[0].stripped_strings)
        publish = publish_text[0]
        publish = re.sub("[\r\n ]+", "", publish)
        publish_text = ''.join(publish_text)
        publish_text = re.sub("[\r\n ]+", "", publish_text)
        # 提取時間
        match_re = re.match(".*?(\d{4}).*", publish_text)
        if match_re:
            year = int(match_re.group(1))
        else:
            year = 0
    # 提取引用量
    ref_wr = list(soup.select('a.sc_cite_cont'))
    if len(ref_wr) == 0:
        ref_wr = 0
    else:
        ref_wr = list(soup.select('a.sc_cite_cont')[0].stripped_strings)[0]
    # 提取關鍵詞
    key_words = ','.join(key_word for key_word in list(soup.select('div.dtl_search_word > div')[0].stripped_strings)[1:-1:2])
#     data = {
#         "title":title,
#         "authors":authors,
#         "abstract":abstract,
#         "year":int(year),
#         "publish":publish,
#         "publish_text":publish_text,
#         "ref_wr":int(ref_wr),
#         "key_words":key_words
#     }
    return title, authors, abstract, publish_text, year, publish, ref_wr, key_words
           

這裡有特别說明一下:在爬取摘要的時候,有一個JS動态加載,“更多”樣式加載按鈕。是以,我想要将摘要全部爬下來,可能就要使用selenium模仿點選操作(我在代碼中加了注釋的地方)。但是,我沒有用這種方式因為多次通路網頁,可能會有很多問題,一個是速度的問題,一個是很容易被伺服器拒絕通路,是以在這裡我隻爬取了一部分摘要。

接着儲存爬取的資料,這裡我為了後面直接用pandas讀取處理,且資料量不大,是以直接儲存為csv格式。

def get_all_data(urls_list):
    dit = defaultdict(list)
    for url in urls_list:
        title, authors, abstract, publish_text, year, publish, ref_wr, key_words = get_item_info(url)
        dit["title"].append(title)
        dit["authors"].append(authors)
        dit["abstract"].append(abstract)
        dit["publish_text"].append(publish_text)
        dit["year"].append(year)
        dit["publish"].append(publish)
        dit["ref_wr"].append(ref_wr)
        dit["key_words"].append(key_words)
    return dit
           
def save_csv(dit):
    data = pd.DataFrame(dit)
    columns = ["title", "authors", "abstract", "publish_text", "year", "publish", "ref_wr", "key_words"]
    data.to_csv("abstract_data.csv", index=False, columns=columns)
    print("That's OK!")
           

到此,程式完成,然後開始爬取前20頁的資料:

if __name__ == "__main__":
    key_word = "牛肉品質"
    soup = driver_open(key_word)
    urls_list = page_url_list(soup, page=20)
    dit = get_all_data(urls_list)
    save_csv(dit)
           

爬取完之後,我們用pandas進行讀取。

data = pd.read_csv("abstract_data.csv")
data.head()
           
爬取百度學術文章及文本挖掘分析

2. 資料清洗及分析

在publish這一列中,還有小問題需要處理。如下,有些行中出現了逗号。

爬取百度學術文章及文本挖掘分析

我們将它處理掉。

data["publish"] = data["publish"].map(lambda x: str(x).replace(',', ""))
           

同時,發現在出版社這一欄南京農業大學有兩種表示(《南京農業大學》,南京農業大學),其實它們都是一個意思,需要統一下。

data.publish = data.publish.map(lambda x: re.sub("(.+大學$)", r"《\1》", x))
           

這樣就将所有以“大學”結尾的出版社加上了“《》”進行統一。

data.nunique()
           
爬取百度學術文章及文本挖掘分析

可以看出現在200篇論文中隻在91個出版社發表過,我們來統計前10個發表最多的出版社的發表情況。

data.publish.value_counts()[:10]
           
爬取百度學術文章及文本挖掘分析

可視化結果:

首先使用seaborn畫圖

爬取百度學術文章及文本挖掘分析

其次使用Web可視化工具plotly展示

爬取百度學術文章及文本挖掘分析
爬取百度學術文章及文本挖掘分析

對于“牛肉品質”相關的文章,大家都傾向于投《食品科學》、《肉類研究》、《延邊大學》等期刊。

下面,我們接着看這幾年來文章發表的情況。

首先,我們先檢視資料,有沒有缺失值。

data.info()
           
爬取百度學術文章及文本挖掘分析

這裡紅框的地方,時間這一列隻有197個資料,說明有三個缺失值。因為,缺失值很少,是以,我們直接删除他們。

df = data.dropna(axis=0, how="any")
df.info()
           
爬取百度學術文章及文本挖掘分析

這裡,因為“year”列是浮點型的類型,需要轉化一下類型。

df["year"] = df["year"].map(lambda x: str(int(x)))
df["year"].value_counts()
           
爬取百度學術文章及文本挖掘分析

進行可視化展示:

plt.figure(figsize=(12, 5))
# sns.set_style("darkgrid",{"font.sans-serif":['simhei','Droid Sans Fallback']})
temp = df["year"].value_counts()
sns.countplot(
    x = df.year,
    palette = "Set3",
    order = temp.index
)
           
爬取百度學術文章及文本挖掘分析

通過這張圖雖然可以看出哪些年發表文章最多,但是卻不能展示随時間走勢,看到發表趨勢。下面就通過時間序列分析的方式展現一下。

df["year"] = pd.to_datetime(df["year"])
df["year"].value_counts().resample("Y").sum().plot.line()
           
爬取百度學術文章及文本挖掘分析

這樣就展示了随時間變化,發表牛肉品質的文章的趨勢。但是,還是不夠美觀。下面使用Web可視化工具plotly再次展示。

爬取百度學術文章及文本挖掘分析

這張圖就更能展現1997到2018年期間肉牛品質文章的發表情況了,圖下方還有一個時間bar,它可以前後拖動,進行放大。這就是使用Web可視化工具的最大好處,可以更加形象具體的可視化展示。

爬取百度學術文章及文本挖掘分析

接下來,我們再看哪些作者在1997到2018年期間發表文章最多。

data.authors.value_counts()[:10]
           
爬取百度學術文章及文本挖掘分析

考慮到發表文章的作者數量不統一,是以,我們隻提取第一作者進行分析。

data["authors_fir"] = data.authors.map(lambda x: x.split(",")[0])
len(data["authors_fir"].unique())
           

得出一共有171位不同的作者以第一作者的身份發表過關于“牛肉品質”的文章。

data.authors_fir.value_counts()[:10]
           
爬取百度學術文章及文本挖掘分析

我們再來看發表最多5篇的萬發春老師具體是哪五篇文章。

wfc = data[data["authors_fir"] == "萬發春"]["title"]
wfc = pd.DataFrame(np.array(wfc), columns=["Title"], index=[1,2,3,4,5])
wfc
           
爬取百度學術文章及文本挖掘分析

3. 詞雲展示

在這裡,我們直接使用關鍵詞進行雲詞展示,因為,摘要不夠完整,且這樣也避免了分詞處理。

docs = list(data["key_words"].map(lambda x: x.split(",")))
from juba import Similar
S = Similar(docs)
# 詞彙表
S.vocabularyList
# 前100個詞彙量
tags = S.vocabulary
sort_tage = sorted(tags.items(), key=lambda x: x[1], reverse=True)
sort_tage[:100]
# 列印出詞彙和該詞彙的出現次數
for v, n in sort_tage[:100]:
        print (v + '\t' + str(int(n)))
           

然後,将結果導入https://wordart.com/edit/gvh0mvzkyzem中,如下圖:

爬取百度學術文章及文本挖掘分析

然後,設定字型和背景圖檔,注意一點是:中文需要自己加載字型,我使用的微軟雅黑字型(網上可以下載下傳)。

爬取百度學術文章及文本挖掘分析

最後形成的詞雲:

爬取百度學術文章及文本挖掘分析

到此,第三部分完成,下面我們進行文章相似度分析。

4. 文章相似度分析

考慮到本次爬取的并沒有完整的文章且摘要不全的情況,是以隻是采用關鍵詞進行分析,是以可能不準,主要介紹方法。但是,後面我将選擇一個文本資料集再進行完整的文本相似度分析。

(1)使用juba進行分析。

juba最長使用餘弦相似度cosine_sim(self, dtm=none)函數計算文檔相似度,都是用于計算第一個文檔與其他的文檔之間的相似度,其中有dtm有三種參數選擇,分别為:“tfidf_dtm”(詞頻逆文檔頻率模式)、“prob_dtm”(機率模式)、“tf_dtm”(詞頻模式)。

sim = S.cosine_sim(dtm="prob_dtm")
sim.insert(0, 1)
data["similar"] = sim
data
           
爬取百度學術文章及文本挖掘分析

然後,我從高到低排列

data.sort_values(by="similar", ascending=False)
           
爬取百度學術文章及文本挖掘分析

可以看出文章相似度都很低,這也符合文章發表的規律。

(2)使用graphlab計算相似度

這裡,我使用另外一個資料集,它是爬取維基百科上很多名人的介紹的一個文本資料集。

import graphlab
people = graphlab.SFrame.read_csv("people_wiki.csv")
# 去掉索引列
del people["X1"]
people.head()
           
爬取百度學術文章及文本挖掘分析

我們來看一共有多少位名人

len(people.unique())
           

59071位

我們從中挑選一位名人——奧巴馬來看看。

obama = people[people["name"] == "Barack Obama"]
obama
           
爬取百度學術文章及文本挖掘分析
# 檢視奧巴馬的具體介紹内容
obama["text"]
           

接下來進行詞頻統計。

obama["word_count"] = graphlab.text_analytics.count_words(obama["text"])
obama_word_count_table = obama[["word_count"]].stack("word_count", new_column_name=["word", "count"])
obama_word_count_table.sort("count", ascending=False)
           
爬取百度學術文章及文本挖掘分析

很顯然,“the”、“in”、“and”等停用詞的頻率最大,但是,這并不是我們想要關注的單詞或者說并不是整篇文章的主旨。是以,要使用tfidf進行統計詞頻。

people["word_count"] = graphlab.text_analytics.count_words(people["text"])
tfidf = graphlab.text_analytics.tf_idf(people["word_count"])
people["tfidf"] = tfidf
people.head()
           
爬取百度學術文章及文本挖掘分析

然後,我們再來看奧巴馬的介紹詞頻。

obama[["tfidf"]].stack("tfidf", new_column_name = ["word", "tfidf"]).sort("tfidf", ascending=False)
           
爬取百度學術文章及文本挖掘分析

這樣就正常了,直接通過詞頻就可以看出介紹誰的。

建構knn模型,計算相似度距離。

knn_model = graphlab.nearest_neighbors.create(people, features=["tfidf"], label= 'name')
           

然後檢視與奧巴馬相近的名人。

knn_model.query(obama)
           
爬取百度學術文章及文本挖掘分析

這些人大多都是美國的總統或相近的人正是與奧巴馬相近,是以,也證明了模型的準确性。至此,整個分析結束,但是也還會存在不少問題,再接再厲吧!

繼續閱讀