106.7.6 古佳怡 網路爬蟲、分析與視覺化
先聊聊之前做的奇怪東西 資料來源: 動機與目的: 最後成果: 聯合報過去十年的 焦點 / 要聞 / 民意論壇 資料 首先假設: 2 資料來源: 聯合報過去十年的 焦點 / 要聞 / 民意論壇 資料 動機與目的: 首先假設: 「民意論壇」- 偏向民眾所關注的議題 「焦點」與「要聞」 - 偏向報社本身所關注的議題 想要呈現: 1. 版面之間,關注哪些議題、關注程度比較 2. 議題之間,不同時間的關注程度變化 最後成果:
背後實作流程 Python JavaScript crawler 中研院CKIP Topic Model LDA 工人智慧 D3.js JSON bag of words
今天來做點類似的事情 4 抓取新聞 文本分析 視覺化呈現
首先 5 抓取新聞 文本分析 視覺化呈現
資料來源 6 http://www.appledaily.com.tw/realtimenews/section/new/
首先,需要把資料抓下來 還記得網頁其實是在網路上的一個檔案? 到底我們抓到什麼? 瀏覽器右上角 > 更多工具 > 開發人員工具 7 還記得網頁其實是在網路上的一個檔案? import requests # 抓網路檔案 url = "http://www.appledaily.com.tw/realtimenews/section/new" res = requests.get(url) 到底我們抓到什麼? 瀏覽器右上角 > 更多工具 > 開發人員工具 符合HTML格式的檔案內容 密密麻麻,一大堆tag,非常噁心
分析結構 新聞在哪裡? 瀏覽器右上角 > 更多工具 > 開發人員工具 > ctrl+F 搜尋關鍵字 8 新聞在哪裡? 瀏覽器右上角 > 更多工具 > 開發人員工具 > ctrl+F 搜尋關鍵字 from bs4 import BeautifulSoup # 爬梳檔案 soup = BeautifulSoup(res.text,"html.parser") for item in soup.select(".rtddt"): # 取得一個完整新聞
分析結構 新聞裡面有什麼? from bs4 import BeautifulSoup # 爬梳檔案 9 新聞裡面有什麼? from bs4 import BeautifulSoup # 爬梳檔案 soup = BeautifulSoup(res.text,"html.parser") for item in soup.select(".rtddt"): # 取得一個完整新聞 # 取得新聞標題、熱門度、時間、類別、網址 news_title = item.find("h1").text news_popular = int(news_title[news_title.rfind("(")+1 : -1]) news_time = item.find("time").text news_category = item.find("h2").text news_url = item.find("a")["href"] 最右邊的"("+1 不含 最後字元
進一步抓內文 重覆剛剛流程 問題來了,全文結構好像有點髒? # 再次抓網路檔案 10 重覆剛剛流程 # 再次抓網路檔案 news_url = "http://www.appledaily.com.tw/"+ news_url res2 = requests.get(news_url) # 再次爬梳檔案 soup2 = BeautifulSoup(res2.text,"html.parser") # 找到內文 問題來了,全文結構好像有點髒?
進一步抓內文 放棄全文,改擷取較乾淨的meta data # 再次抓網路檔案 11 放棄全文,改擷取較乾淨的meta data # 再次抓網路檔案 news_url = "http://www.appledaily.com.tw/"+ news_url res2 = requests.get(news_url) # 再次爬梳檔案 soup2 = BeautifulSoup(res2.text,"html.parser") # 找到內文 news_content = soup2.find("meta",{"name" : "description"}) if news_content: news_content = news_content["content"] else: news_content = ""
最後,把它們存起來 但是如果我想抓 N 頁所有的新聞? from bs4 import BeautifulSoup 12 from bs4 import BeautifulSoup news_group = [] soup = BeautifulSoup(res.text,"html.parser") for item in soup.select(".rtddt"): # 取得新聞標題、熱門度、時間、類別、網址 # 從網址進一步取得內文 news_group.append({"標題":news_title, "時間":news_time, "類別":news_category, "內文":news_content, "人氣":news_popular}) 但是如果我想抓 N 頁所有的新聞?
觀察每一頁網址 所以在最外面多包一層for 第二頁 第三頁 合理推測第一頁 news_group = [] page = 3 13 第二頁 第三頁 合理推測第一頁 所以在最外面多包一層for news_group = [] page = 3 for i in range(1,page): res = requests.get("http://www.appledaily.com.tw/realtimenews/section/new/"+str(i)) soup = BeautifulSoup(res.text,"html.parser") for item in soup.select(".rtddt"): # 取得新聞標題、熱門度、時間、類別、網址 # 從網址進一步取得內文 # 把資料存進news_group
最後不要忘記 14 import time time.sleep(0.2) 除非你想要讓你的IP被ban掉
接著對新聞內容 15 抓取新聞 文本分析 視覺化呈現
Jieba文本分析 基於簡體字開發 import jieba import jieba.analyse # 支援繁體字典 16 基於簡體字開發 import jieba import jieba.analyse # 支援繁體字典 jieba.set_dictionary("dict.txt.big.txt") # 斷詞(全採用模式、精準模式[預設]) # e.g., "我要成為神奇寶貝大師" # 全採用模式:我/ 要/ 成/ 為/ 神奇/ 神奇寶貝/ 大/ 師 # 精準模式:我要/ 成為/ 神奇寶貝/ 大師 seglist = jieba.cut(news_content, cut_all=False) print("斷詞:" + "/ ".join(seglist)) # 取關鍵字 tags = jieba.analyse.extract_tags(news_content, 3) print("三個關鍵字:" + "/ ".join(tags)) TF-IDF取關鍵字
Snownlp文本分析 基於簡體字開發 情感分析來自買賣評價 from snownlp import SnowNLP 17 基於簡體字開發 情感分析來自買賣評價 from snownlp import SnowNLP s = SnowNLP(news_content) # 斷詞 print("斷詞:" + "/ ".join(s.words)) # 關鍵字 print("三個關鍵字:" + "/ ".join(s.keywords(3))) # 摘要 print("三個摘要:" + "/ ".join(s.summary(3))) # 情感分析(代表是正面情緒的機率[0,1]) print("情感分析:" + s.sentiments) TextRank取關鍵字與摘要 http://ihong-blog.logdown.com/posts/873914
比較分析結果 18
比較分析結果 19
比較分析結果 20
最後,視覺化呈現 21 抓取新聞 文本分析 視覺化呈現
目前為止,我們抽出了 22 時間 類別 標題 人氣 內文 斷詞 TF-IDF取關鍵字 關鍵字 摘要 情感分析
可以比較哪些資訊? 時間(橫軸)、人氣(縱軸)、類別(顏色) 時間(橫軸)、情感(縱軸)、類別(顏色) 23 時間(橫軸)、人氣(縱軸)、類別(顏色) 不同類別的新聞人氣度 不同時間的新聞人氣度 不同時間會不會傾向於張貼某些類別的新聞 時間(橫軸)、情感(縱軸)、類別(顏色) 不同類別新聞的正負面情緒 不同時間的新聞情緒 不同時間會不會傾向於張貼某些情緒的新聞 關鍵字(橫軸)、頻率(縱軸)、類別(顏色) 不同字詞的出現頻率 不同類別傾向於出現哪些字詞 TF-IDF取關鍵字
可以做成哪些圖表? 24 TF-IDF取關鍵字
先決定類別顏色 # category = ["焦點","熱門","爆社","動物","副刊","3C",……,"論壇"] 25 # category = ["焦點","熱門","爆社","動物","副刊","3C",……,"論壇"] # 統計多少類別需要上色 category = [] have_label = [] for news in news_group: if news["類別"] not in category: # 給予各類別一個編號 print(len(category),news["類別"]) category.append(news["類別"]) have_label.append(0) # 平均分配顏色 import matplotlib.cm as cm import numpy as np colors = cm.rainbow(np.linspace(0, 1, len(category))) # 紀錄每個類別的顏色 colors_map = dict() for i in range(len(category)): colors_map[category[i]] = colors[i]
開始畫點 import matplotlib.pyplot as plt # 在圖表上畫點 (x,y) = (距離最新新聞的時間,人氣) 26 import matplotlib.pyplot as plt # 在圖表上畫點 (x,y) = (距離最新新聞的時間,人氣) latest_time = datetime.strptime(news_group[0]["時間"], "%Y/%m/%d %H:%M") for news in news_group: news_time = datetime.strptime(news["時間"], "%Y/%m/%d %H:%M") x = int((latest_time-news_time).seconds//60) y = int(news["人氣"]) # 已經在圖上標示該顏色的類別 if have_label[category.index(news["類別"])] == 1: plt.scatter([x], [y], color=colors_map[news["類別"]]) # 尚未在圖上標示該顏色的類別 else: plt.scatter([x], [y], color=colors_map[news["類別"]], label=str(category.index(news["類別"]))) have_label[category.index(news["類別"])] = 1
最後標上其他資訊 # 標示圖表標題、x軸、y軸 plt.title('timeline vs. popular') 27 # 標示圖表標題、x軸、y軸 plt.title('timeline vs. popular') plt.xlabel('new <-- old(minute)') plt.ylabel('popular') # 顯示圖表顏色標示 plt.legend() # 標示圖表 plt.show()