環境 Windows Python3.7
#これからやること
Beautiful Soup 4(以後bs4)を使ってスクレイピング。結果をJSONファイルで出力。
WEBから分析用のデータを収集する。
データ分析の勉強をしたかったが、そもそも良い感じのデータが無かったためスクレイピングで入手することに。
####スクレイピング対象
- ニコニコチャート(http://www.nicochart.jp/)
- ニコニコ動画(https://www.nicovideo.jp/)
ニコニコ動画ゲームカテゴリランキングの上位100に入った動画と投稿者の情報を取得する。
Q. なぜニコニコ動画?
A. 私も投稿者だからです(宣伝)。https://www.nicovideo.jp/user/35233530
####取得したい情報
- 動画情報
- 動画ID
- 投稿日時
- 再生時間
- 過去最高ランク
- タグ
- 投稿者情報
- 投稿者ID
- ユーザー登録時のニコニコ動画のバージョン
- フォロワー数
- 投稿動画数
#Beautiful Soupについて
###事前準備
BeautifulSoupをインストール
$ pip install beautifulsoup4
###基本的な使い方
import urllib.request
from bs4 import BeautifulSoup
url="対象のURL"
html = urllib.request.urlopen(url)
soup = BeautifulSoup(html, "html.parser")
#引数はどれか1つ以上でOK。最初に見つかったタグが返される。
hoge = soup.find("HTMLタグ名",class_="タグのクラス名",id="タグのID")
#条件に一致する全てのタグが返される。
hoge = soup.find_all("HTMLタグ名",class_="タグのクラス名",id="タグのID")
#あるタグの子要素に限定して検索することも可能。
hogehoge=hoge.find("HTMLタグ名",class_="タグのクラス名",id="タグのID")
#タグのテキストを取得
print(hoge.text)
#タグの属性値を取得
print(hoge["href"])
###エラー対策
ネットワークエラーの対策。
503エラーが発生することがあるので。
時間を空けてリトライ。それでもダメなら諦め。
try:
html = urllib.request.urlopen(_Url)
except urllib.error.HTTPError as e:
print(e.code)
sleep(60)
html = urllib.request.urlopen(_Url)
soup = BeautifulSoup(html, "html.parser")
タグが見つからないエラーの対策。
HTMLが上手く読めてない可能性があるので時間を空けてリトライ。
それでもダメなら諦め。
try:
tag_panel=soup.find(class_="クラス名1").find(class_="クラス名2")
except:
try:
print("error")
sleep(60)
#再読み込み
html = urllib.request.urlopen(_Url)
soup = BeautifulSoup(html, "html.parser")
tag_panel=soup.find(class_="クラス名1").find(class_="クラス名2")
result=tag_panel.text
except:
print("error")
result = None
###スクレイピングの注意点
- サイトの規約を読みましょう。
- 相手のサーバーに負担をかけるため、sleep等でリクエストの間隔を空けましょう。1秒以上は空けたほうが良さげ。
#JSON出力
辞書型からJSONファイルで出力。エンコードはUTF-8で出力。Shift_Jisは文字化けしたり何かと厄介。
ensure_ascii=Falseも忘れずに。
ほんとはwith句でやると安全。
fw = open("ファイル名",'w',encoding='utf-8')
json.dump(_dictionary,fw,indent=4,ensure_ascii=False)
fw.close()
#実際にやってみた
エラー対策は頻発する箇所のみ実施。
#%%
import urllib.request
import re
import datetime
import json
import sys
from bs4 import BeautifulSoup
from time import sleep
#ニコニコチャートからスクレイピング
###ランキングページを開く
def setRankPage(_dateStr):
url="http://www.nicochart.jp/ranking/game/"+_dateStr
html = urllib.request.urlopen(url)
print(url)
soup = BeautifulSoup(html, "html.parser")
return soup
##動画IDと動画URLと投稿者名、IDとURLの取得
def getRankInfo(_soup,_num):
rank=_soup.find(id="rank"+str(_num))
link=rank.find(class_="title").find("a")
infoUrl="http://www.nicochart.jp/"+str(link["href"])
videoId=re.findall('\d+',link["href"])[0]
print(infoUrl,videoId)
##投稿者名とURLの取得
nameInfo=rank.find("em",class_="user")
if not nameInfo:
return {"videoId":videoId,"videoUrl":infoUrl,"userName":None,"userUrl":None}
nameInfo=nameInfo.find("a")
nameUrl=str(nameInfo["href"])
userId=re.findall('\d+',nameUrl)[0]
nameUrl += "/video"
name=rank.find("em",class_="name").find("a").text
print(name,nameUrl)
return {"videoId":videoId,"videoUrl":infoUrl,"userName":name,"userId":userId,"userUrl":nameUrl}
###動画URLからタグ、投稿日時、最高ランク、再生時間の取得
def getVideoInfo(_infoUrl):
try:
html = urllib.request.urlopen(_infoUrl)
except urllib.error.HTTPError as e:
try:
print(e.code)
sleep(60)
html = urllib.request.urlopen(_infoUrl)
except:
return {"tags":None,"date":None,"length":None,"maxRank":None}
soup = BeautifulSoup(html, "html.parser")
##タグの取得
try:
tag_panel=soup.find(class_="tab-panel").find(class_="cloud")
tags=tag_panel.find_all("a",class_="word")
tagWords=list(map(lambda tag: tag.text, tags))
except:
try:
print("error")
sleep(60)
html = urllib.request.urlopen(_infoUrl)
soup = BeautifulSoup(html, "html.parser")
tag_panel=soup.find(class_="tab-panel").find(class_="cloud")
tags=tag_panel.find_all("a",class_="word")
tagWords=list(map(lambda tag: tag.text, tags))
except:
print("error")
tagWords=None
print(tagWords)
##投稿日時の取得
date=soup.find("dd",class_="first-retrieve").find("span").text
##再生時間の取得
length=soup.find("span",class_="length").text
##最高ランクの取得
nicoRank=soup.find(class_="point-data").find_all("td",class_="fav")
nicoRank=list(map(lambda rank:rank.find_all("a"),nicoRank))
nicoRank=list(map(lambda rank:sys.maxsize if len(rank)==0 else int(rank[len(rank)-1].text),nicoRank))
nicoRank=min(nicoRank)
print(nicoRank)
return {"tags":tagWords,"date":date,"length":length,"maxRank":nicoRank}
###投稿者情報から投稿者の基本情報を取得
#投稿者URLからフォロワー数、投稿動画数、ユーザーID、ニコ動Verを取得。
def getUserInfo(_nameUrl):
try:
html = urllib.request.urlopen(_nameUrl)
except urllib.error.HTTPError as e:
print(e.code)
return {"followerNum":None,"videoNum":None,"nicoVer":None}
soup = BeautifulSoup(html, "html.parser")
##基本情報の取得
followerNum=soup.find(class_="stats channel_open_mb8").find_all(class_="num")[1].text
followerNum=followerNum.replace(',',"")
videoNum=soup.find(class_="content",id="video").find("h3").text
videoNum=re.findall('\d+', videoNum)
if videoNum:
videoNum=videoNum[0]
else:
videoNum=None
idInfo=soup.find(class_="accountNumber").find("span").text
nicoVer=re.findall('(?<=\().*?(?=\))',idInfo)[0]
print(followerNum,videoNum,nicoVer)
return {"followerNum":followerNum,"videoNum":videoNum,"nicoVer":nicoVer}
###ランキングから動画URLと投稿者情報の取得
def getRankDataDay(_dateStr):
soup=setRankPage(_dateStr)
rankData=[]
for rankNum in range(1,100):
print("//////",rankNum,"//////")
rankData.append(getRankInfo(soup,rankNum))
print(rankData)
###動画一覧、投稿者一覧の作成
videoData={}
userData={}
user_videoData={}
num=0
for rank in rankData:
num +=1
videoId=rank["videoId"]
videoData[videoId]={"videoUrl":rank["videoUrl"]}
if not "userId" in rank:
continue
userId=rank["userId"]
user_videoData[videoId]={"userId":rank["userId"]}
if not userId in userData:
userData[userId]={"userName":rank["userName"],"userUrl":rank["userUrl"]}
###動画一覧にタグ情報を追加
for video in videoData.values():
print("/////////",video["videoUrl"])
videoInfo=getVideoInfo(video["videoUrl"])
video.update(videoInfo)
sleep(2)
###投稿者一覧に投稿者情報を追加
for user in userData.values():
userInfo=getUserInfo(user["userUrl"])
user.update(userInfo)
sleep(1)
print(videoData)
print(userData)
return {"videoData":videoData,"userData":userData,"user_videoData":user_videoData}
###Json出力
def outputJson(_dateStr,_data,_folder):
fw = open(_folder+ _dateStr +".json",'w',encoding='utf-8')
json.dump(_data,fw,indent=4,ensure_ascii=False)
fw.close()
return
###指定期間分のデータを取得。指定日の前日から取得開始。
date = datetime.date(2019,2,2)
count=365
for num in range(count):
date -=datetime.timedelta(days=1)
dateStr=date.strftime("%Y%m%d")
rankDataDay=getRankDataDay(dateStr)
outputJson(dateStr,rankDataDay["videoData"],"VideoData/")
outputJson(dateStr,rankDataDay["userData"],"UserData/")
outputJson(dateStr,rankDataDay["user_videoData"],"User_VideoData/")
#結果
データ収集が完了。JSON形式で出力。
次回はこのデータをSQLデータベースにシュウウウウウウ!!!!超!!!エキサイティン!!!!!。
参考
https://www.crummy.com/software/BeautifulSoup/bs4/doc/#installing-beautiful-soup