WorkShop#4 - 綜合案例講解 示範案例:鉅亨網新聞爬蟲與資料分析 今日流程 爬蟲:鉅亨網新聞 複習從網頁上尋找 API 複習 API 串接 文章清洗與儲存 複習 Python Regex 語法 文章資料分析 結合 Python Pandas 操作 結合 基礎 NLP 入門學習 (CKIP, Word2Vec, TFIDF, Sentiment Analysis) 結合 Plotly Express 操作 (1) 爬蟲:鉅亨網新聞 我們首先要爬取鉅亨網上的新聞。 複習第一堂工作坊:先找資料載入來源來確定要用哪種爬蟲方式 Step1. 複習從網頁上尋找 API 一開始搜尋文章標題,發現只有靜態頁面;仔細查看靜態頁面中也塞有文章資料的 dictionary 再進一步用文章 id 去搜尋,就發現了 API 所以我們找到了鉅亨網 API:https://api.cnyes.com/media/api/v1/newslist/category/tw_stock 以及其參數 startAt endAt limit Step2. 複習 API 串接 使用 requests 庫串接鉅亨網台股新聞 API import datetime as dt import numpy as np import requests from time import sleep from tqdm import tqdm 觀察最多一次會輸出幾篇文章、下一頁要怎麼取得?int(dt.datetime.today().timestamp()) # datetime object -> unixtimestamp (1655827200) cnyes_api = "https://api.cnyes.com/media/api/v1/newslist/category/tw_stock" params = { "startAt": int((dt.datetime.today()-dt.timedelta(days=30)).timestamp()), # 取最近一個月的新聞 "endAt": int(dt.datetime.today().timestamp()), "limit": 30 } data = requests.get(url=cnyes_api, params=params).json() data["items"]["data"] 觀察獲取了哪些欄位、有哪些可能用得到?# len(data["items"]["data"]) # page_num = data["items"]["last_page"] params["page"] = 2 data = requests.get(url=cnyes_api, params=params).json() data["items"]["data"][0] 最後就來取得所有文章article_list = [] data_cols = ["newsId","title","content","publishAt","market","categoryId","categoryName"] page_num = requests.get(url=cnyes_api, params=params).json()["items"]["last_page"] for i in tqdm(range(1, page_num + 1)): params["page"] = i data = requests.get(url=cnyes_api, params=params).json()["items"]["data"] article_list += [{k: v for k, v in article.items() if k in data_cols} for article in data] sleep(np.random.randint(3, 5)) ```python len(article_list) (2) 文章清洗與儲存 順利抓下文章後,將換行符、與內容無關的段落清洗 再以 .csv 和 .json 儲存import html import json import pandas as pd import re Step3. 複習 Python Regex 語法 觀察數篇文章,他們共同有哪些與內容無關的字符?article_list[0]["content"], article_list[1]["content"] # < p> & nbsp; article_list[7]["content"] _text _text = html.unescape(article_list[2]["content"]) pattern = "<.*?>|\n|&[a-z0-9]+;|http\S+" re.sub(pattern, "", _text) 像是 < > & 其實是 html 標籤的 < > & \xa0 使用 Regex 語法篩選這些字符並排除 排除 html 標籤_html_text = html.unescape(article_list[2]["content"]) # pattern = "<.*?>|&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-f]{1,6});|\n" pattern = "<.*?>|\n|&[a-z0-9]+;|http\S+" re.sub(pattern, "", _html_text).strip() df = pd.DataFrame(article_list) # 套用 html 標籤清除 regex 語法 df["content"] = df["content"].apply(lambda x: re.sub(pattern, "", html.unescape(x)).strip()) df["content"][0] 儲存檔案df.to_csv("articles_cnyes.csv", index=False) with open("articles_cnyes.json", "w", encoding="utf-8") as f: json.dump(article_list, f, ensure_ascii=False, indent=4) 比較不同檔案格式的大小!ls -l (3) 文章資料分析 結合 Python Pandas 操作 結合 基礎 NLP (CKIP / Word2Vec, TFIDF, Sentiment Analysis) 結合 Plotly Express 操作 資料分析入門題 在這過去一個月中,統整所有文章來看: 熱詞排名情形? 哪些公司最常一起出現?最可能產生連帶影響? 文章情緒分數隨時間變化 公司情緒分數隨時間變化基本分析 import datetime as dt from dateutil import tz # 調整時區 import pandas as pd df = pd.read_csv("articles_cnyes.csv") df.info() # 載入爬蟲預存好的資料 df = pd.read_csv("articles_cnyes.csv") for col in ["content","title","market","categoryName"]: if col == "market": df[col] = df[col].fillna("[]") df[col] = df[col].apply(lambda x: eval(x)) else: df[col] = df[col].astype(str) df.info() tz.gettz("Asia/Taipei") dt.datetime.fromtimestamp(1656657673, tz=tz.gettz("Asia/Taipei")) # 為 dataframe 添加可用的時間篩選欄位 df["date"] = df["publishAt"].apply(lambda x: int(dt.datetime.fromtimestamp(x, tz=tz.gettz("Asia/Taipei")).strftime("%Y%m%d"))) df["hour"] = df["publishAt"].apply(lambda x: int(dt.datetime.fromtimestamp(x, tz=tz.gettz("Asia/Taipei")).strftime("%H"))) df["weekday"] = df["publishAt"].apply(lambda x: int(dt.datetime.fromtimestamp(x, tz=tz.gettz("Asia/Taipei")).weekday() + 1)) df["stock_num"] = df["market"].apply(lambda x: len(x)) df.head(1) 時區名稱查表 工作日和週末的發文頻率差異df_agg = df.groupby(["weekday"]).agg({"newsId": "count"}) df_agg 每小時發文的頻率差異!pip install plotly pyyaml==5.4.1 df_agg = df.groupby(["hour"]).agg({"newsId": "count"}).reset_index() df_agg import plotly.express as px fig = px.bar(df_agg, x="hour", y="newsId", width=800, height=400, title="每小時發文的頻率差異") fig.show() 週末和工作日的每小時圖比較df_agg_weekday = df[df["weekday"].isin([1,2,3,4,5])].groupby(["hour"]).agg({"newsId": "count"}) df_agg_weekday["type"] = "weekday" df_agg_weekend = df[df["weekday"].isin([6,7])].groupby(["hour"]).agg({"newsId": "count"}) df_agg_weekend["type"] = "weekend" df_agg_weekend df_agg = pd.concat([df_agg_weekday, df_agg_weekend]) df_agg # 分組長條圖 fig = px.bar(df_agg.reset_index(), x="hour", y="newsId", color="type", text_auto=True, barmode="group") fig.show() 每篇文提及公司數量分佈fig = px.histogram(df, x="stock_num") fig.show() 📎articles_cnyes.csv📎articles_cnyes.json