2年連続ステイホームのゴールデンウィークになりそうです。
もはやゴールデンウィークって普段何してたのか忘れかけてきたので、過去の履歴を漁ってみたら、一昨年は伊豆半島の東側をぐるぐる回りながら下田までいってたみたいです。
こんなどこにも行けない日には、家でデータ分析をするに限りますね!!(鼻息)
統計局が、e-statを使って遊ぶ方法も教えてくれるそうなので、ご興味がある方は是非!
統計として公開されているデータを眺めてみるのも面白いっちゃ面白いのですが、データ分析の醍醐味は統計処理される前の生々しいデータを弄くり倒すところにあると思うんすよ。
ただKaggleでランカー目指すぞい!!と殴り込みに行くにはハードルが高いなあとお思いでしたら、目の前になかなかいじり甲斐のありそうなデータがあるじゃあないですか。コイツです。

はてブデータ収集ツールの使い方
定期的に「はてなスターランキング」と称した記事を書いておりますが、このランキングは、はてなの公開しているAPIを叩いて出てきたデータを使用しています。これらのデータは、数年前にたまたまそのとき勉強していたJavaで書いた自家製ツールで収集していました。ただメンテ性も良くないし、いろいろと直しておきたい箇所もあったので昨年末にPythonで書き直してみました。
かなり扱い易くなったはずですし、個人的な備忘も兼ねて、データ収集の方法について整理したいと思います。
ツールの開発・実行環境は、
- iMac 27-inch,Late 2013(そろそろ買い替えたい)
- mac OS Catalina 10.15.7
- Python 3.7.1 (Catalinaの標準)
- SQLiteStudio 3.3.3
データの収集・集計には、SQLを使用しますが、特に難しく考えなくて平気です。(むしろちょうどよいSQLの練習になるかも)
1〜3まではMacなら勝手についてきているはずです。Windows・Linuxの似たようなバージョンを突っ込んでください。4のSQLiteStudioは下記から別途インストールしておきましょう。
インストールできたら、データを保存するデータベースファイルを作りましょう。


- Page(対象となるページの情報)
- Bookmark(個別のブックマーク情報)
- Star(ブックマークに対するスター)
Page
CREATE TABLE Page ( EID VARCHAR (4000) PRIMARY KEY, DATE DATE, CATEGORY VARCHAR (4000), ENTRYRANK NUMERIC (5), COUNT NUMERIC (5), TITLE VARCHAR (4000), URL VARCHAR (4000) );
Bookmark
CREATE TABLE Bookmark ( EID VARCHAR (4000), BOOKMARKUSER VARCHAR (4000), URL VARCHAR (4000), STARCOUNT NUMERIC (5), TIMESTAMP DATETIME, COMMENT TEXT, TAG TEXT, PRIMARY KEY ( EID, BOOKMARKUSER ) );
Star
CREATE TABLE Star ( STARUSER VARCHAR (4000), COLOR VARCHAR (4000), BOOKMARKUSER VARCHAR (4000), EID VARCHAR (4000), QUOTE TEXT );作ったテーブル構造の説明もしておきたいところですが、データが入った段階じゃないとイメージがつきづらいでしょう。細かい解説は後回しにして、まずデータを取得しましょう。
さてここで、今回Python化した収集スクリプトの登場です。
import sys | |
import requests | |
import urllib.parse | |
import json | |
import sqlite3 | |
import time | |
from bs4 import BeautifulSoup | |
from datetime import datetime as dt | |
pdate = sys.argv[1] | |
db = sys.argv[2] | |
url = “https://b.hatena.ne.jp/hotentry/all/” + pdate | |
r = requests.get(url) | |
soup = BeautifulSoup(r.text, ‘html.parser’) | |
conn = sqlite3.connect(db) | |
c = conn.cursor() | |
elems = soup.find_all(“div” , class_ = “entrylist-contents”) | |
print(“★★★★★” + pdate + “★★★★★”) | |
for i,e in enumerate(elems): | |
purl = e.find(“a”).get(“href”) | |
burl = “https://b.hatena.ne.jp/entry/jsonlite/?url=” + urllib.parse.quote(purl) | |
Page = requests.get(burl).json() | |
count = Page[“count”] | |
title = Page[“title”] | |
eid = Page[“eid”] | |
category = e.find(“li” , class_ = “entrylist-contents-category”).text.strip() | |
#EUCに変換できない文字が含まれる場合は標準出力出来ないため、エラーの場合は適当にスルー | |
try: | |
print(str(i+1) + “:” + str(count) + “:” + str(title)) | |
except Exception as e: | |
print(str(i+1) + “:” + str(count)) | |
check = c.execute(‘select count(*) “count” from Page where EID = ?’,(eid,)).fetchone()[0] | |
if check == 1: | |
continue | |
c.execute(“delete from Bookmark where EID = ?”,(eid,)) | |
c.execute(“delete from Star where EID = ?”,(eid,)) | |
c.execute(“insert into Page(EID,Date,category,Entryrank,Count,Title,URL) values(?,?,?,?,?,?,?)”,(eid,dt.strptime(pdate,‘%Y%m%d’),category,i+1,count,title,purl)) | |
for bookmark in Page[“bookmarks”]: | |
buser = bookmark[“user”] | |
btags = bookmark[“tags”] | |
bcomment = bookmark[“comment”] | |
btimestamp = dt.strptime(bookmark[“timestamp”], ‘%Y/%m/%d %H:%M’) | |
surl = “https://s.hatena.com/entry.json?uri=” + urllib.parse.quote(“https://b.hatena.ne.jp/” + buser + “/” + btimestamp.strftime(‘%Y%m%d’) + “#bookmark-“ + eid) | |
StarData = [] | |
err_count = 0 | |
star_count = 0 | |
if(len(bcomment)!=0): | |
while err_count < 10 : | |
try: | |
Bookmark = requests.get(surl).json() | |
stars = Bookmark[“entries”] | |
if len(stars)!=0: | |
for star in stars[0][“stars”]: | |
StarData.append((star[“name”],None,buser,eid,star[“quote”])) | |
star_count += 1 | |
if(“colored_stars” in stars[0]): | |
for color_stars in stars[0][“colored_stars”]: | |
for color_star in color_stars[“stars”]: | |
star_count += 1 | |
StarData.append((color_star[“name”],color_stars[“color”],buser,eid,color_star[“quote”])) | |
break | |
except Exception as e: | |
print(surl) | |
print(“エラー発生:” + str(err_count)) | |
err_count += 1 | |
time.sleep(err_count) | |
c.executemany(‘Insert into Star(STARUSER,COLOR,BOOKMARKUSER,EID,QUOTE) values(?,?,?,?,?)’,StarData) | |
c.execute(‘Insert into Bookmark(EID,BOOKMARKUSER,URL,STARCOUNT,TIMESTAMP,COMMENT) values(?,?,?,?,?,?)’,(eid,buser,purl,star_count,btimestamp,bcomment)) | |
conn.commit() | |
conn.close() |
Hatena
非エンジニアが「動けば良い」の崇高な精神のもと書いたスクリプトなので、ツッコミどころは満載だと思いますが、気にせず適当な名前で保存してください。そうですね “hatena.py” にでもしましょうか。
このスクリプトは、標準のライブラリに加えてBeautifulSoupを使います。ターミナルを起動し、以下のコマンドでインストールしましょう
pip install beatifulsoup4これで準備完了です!!
このスクリプトは、
- 第一引数 取得対象の年月日(YYYYMMDD)
- 第二引数 格納先のデータベース
python /Users/xxx/Documents/hatena.py 20210101 /Users/xxx/Documents/Hatena.dbこんな感じで、データの取得が始まれば成功です。

はてブデータ収集ツールの仕組み
このツールがどんな手順でデータを取得しているのかは、中のデータの特性を抑えるためにも必要だと思うので、簡単に説明いたします。はてなブックマークには、デイリーのランキングページがあります。例えば、2021年1月1日だとこんな感じ。末尾の部分が指定した引数と同じになります。
https://b.hatena.ne.jp/hotentry/all/20210101ページを開いてデベロッパーツールで眺めてみると、”entrylist-contents”の中に、それぞれの要素が格納されていることが見て取れます。

APIの詳細は下記をご参照ください。
こうしてウェブページからデータをぶっこ抜いてくる手法を「スクレイピング」と言います。詳細は、さいとーさんの本が大変評判が良いので気になる人はご一読ください。

スクレイピング・ハッキング・ラボ Pythonで自動化する未来型生活 (技術の泉シリーズ(NextPublishing))
- 作者:齊藤 貴義
- 発売日: 2020/09/04
- メディア: Kindle版
- デイリーランキングに乗っているページが対象
- 取得したページから、コメント付きのブックマークに対してスターを取得
- デイリーランキングに乗り切らなかったページへのコメント
- タグだけ、コメントを後で消したコメントへのスター
はてブのデータを見てみよう
SQLiteStudioで、まずは単純に取得したページのデータを見てみましょう。以下の手順で操作してください。
- EID
- DATE
- CATEGORY
- ENTRYRANK
- COUNT
- TITLE
- URL
なおDATEはランキングに表示された日付(≠記事が投稿された日付)で、RANKは当日ランキング上の表示順です(≠ブクマ数順位)。
次に、特定ページに対するコメントをスター数の多い順番に並び替えて見ましょう。

もう少し複雑なデータを取得する場合は、SQL文を書く必要があります。例えば下記は、「lacucarachaのブックマークに対してスターをつけた人を、ページ毎の重複ブックマークを除いて集計し、降順で表示する」といった命令になります。
select staruser,count(distinct EID) from Star where bookmarkuser = 'lacucaracha' group by staruser order by count(distinct EID) desc

取得したデータを個人の趣味の範囲で利用するぶんには問題ないと思いますが、念の為API規約についてもご一読ください。
この程度でどうにかなる、はてなのサーバーではないと思いますが、「当社または他者のサーバーに過剰に負荷をかける機能」にならない範囲でお楽しみくださいまし。
ではでは、今日はこのへんで