note.nkmk.me

Scrapy, はてなブックマークAPIで件数やコメントを一括取得

Date: 2018-08-27 / tags: Python, Scrapy, スクレイピング, Web API

はてなブックマークAPIを使うと、特定のURLのブックマーク数や詳細情報(ブックマークした日時やユーザー、コメントなどの一覧)をシンプルなGETリクエストで取得できる。

指定したURLとそれ以下のパスのURL(=指定したサイト内のURL)のブックマークの詳細情報はScrapyと組み合わせると一括で取得できる。ブックマークの合計数はAPIで取得可能。

以下の内容について説明する。

  • はてなブックマークAPIの種類と使い方
    • はてなブックマーク件数取得API
    • はてなブックマークエントリー情報取得API
  • はてなブックマークWebページで取得できる情報
  • Scrapyでクローリング・スクレイピング
    • サイト内のURLのブックマーク数を一括取得
    • サイト内のURLのブックマーク日時・コメントなどを一括取得
  • pandasによる分析

なお、サンプルコードは2018年8月27日時点のはてなブックマークAPIおよびはてなブックマークWebページの仕様が対象。仕様に変更があった場合、動かなくなる可能性があるので注意。

スポンサーリンク

はてなブックマークAPIの種類と使い方

はてなブックマーク関連のAPIには以下の2つがある。

  • はてなブックマーク件数取得API
  • はてなブックマークエントリー情報取得API

はてなブックマーク件数取得API(entry.count, entry.counts, entry.total_count)

はてなブックマークの件数を取得するAPI。

以下の3通りの件数を取得できる。

  • entry.count: 単独のURLのブックマーク数
  • entry.counts: 複数のURLのブックマーク数
  • entry.total_count: 指定したURLとそれ以下のパスのURLのブックマークの合計数

いずれの場合もクエリパラメータurlに対象のURLを指定してGETリクエストを行う。URLはエスケープ / エンコードして指定する必要がある。下記のPythonのサンプルコードではRequestsが適宜処理してくれている。

利用制限はないが、繰り返しリクエストする場合はリクエストごとに数秒の間隔をあけるなどの配慮が必要。

API へのリクエストに際して、1クライアントあたりの上限回数はいまのところありません。ただし、過度なリクエストはサーバーの過負荷ならびにサービスのレスポンス低下に繋がりますので、繰り返しリクエストされる場合は、リクエスト毎に数秒の間隔をあけていただくなど、サーバーリソースの節約にご協力ください。
はてなブックマーク件数取得API - Hatena Developer Center

entry.count: 単独のURLのブックマーク数

URLを指定してブックマーク数を取得できる。

http://api.b.st-hatena.com/entry.count?url=<URL>
http://api.b.st-hatena.com/entry.count?url=https%3A%2F%2Fnote.nkmk.me

ブックマーク数がそのまま返される

Pythonコードの例。

import requests

url = 'https://note.nkmk.me'

hb_count = 'http://api.b.st-hatena.com/entry.count'

r = requests.get(hb_count, params={'url': url})

print(r.url)
# http://api.b.st-hatena.com/entry.count?url=https%3A%2F%2Fnote.nkmk.me

print(r.text)
# 5

print(type(r.text))
# <class 'str'>

print(int(r.text))
# 5

print(type(int(r.text)))
# <class 'int'>

entry.counts: 複数のURLのブックマーク数

複数のURLのブックマーク数をまとめて取得できる。クエリパラメータurlを複数指定する。指定できるURLは一度に50個まで。

http://api.b.st-hatena.com/entry.counts?url=<URL1>&url=<URL2>
http://api.b.st-hatena.com/entry.counts?url=https%3A%2F%2Fwww.google.co.jp&url=https%3A%2F%2Fwww.yahoo.co.jp

個別のURLをキー、ブックマーク数を値としたJSON形式で返される。

Pythonコードの例。Requestsで同じ名前のクエリパラメータを指定する場合は、辞書の値をリストで指定する。

hb_counts = 'http://api.b.st-hatena.com/entry.counts'

r = requests.get(hb_counts, params={'url': ['https://www.google.co.jp', 'https://www.yahoo.co.jp']})

print(r.url)
# http://api.b.st-hatena.com/entry.counts?url=https%3A%2F%2Fwww.google.co.jp&url=https%3A%2F%2Fwww.yahoo.co.jp

j = r.json()

print(j)
# {'https://www.google.co.jp': 1385, 'https://www.yahoo.co.jp': 313}

print(type(j))
# <class 'dict'>

entry.total_count: 指定したURLとそれ以下のパスのURLのブックマークの合計数

あるサイトに付けられたブックマークの合計数を取得できる。

http://api.b.st-hatena.com/entry.total_count?url=<URL>
http://api.b.st-hatena.com/entry.total_count?url=https%3A%2F%2Fnote.nkmk.me

urltotal_bookmarksのキーを持つJSON形式で返される。

Pythonコードの例。

hb_total_count = 'http://api.b.st-hatena.com/entry.total_count'

r = requests.get(hb_total_count, params={'url': url})

print(r.url)
# http://api.b.st-hatena.com/entry.total_count?url=https%3A%2F%2Fnote.nkmk.me

j = r.json()

print(j)
# {'url': 'https://note.nkmk.me', 'total_bookmarks': 324}

print(j['total_bookmarks'])
# 324

このAPIで取得できるのはブックマークの合計数のみ。個別ページの件数の内訳を取得したい場合はWebサイトをスクレイピングする。後述。

はてなブックマークエントリー情報取得API(json, jsonlite)

はてなブックマークの詳細情報(ブックマークしたユーザーやコメントの一覧)を取得するAPI。

関連エントリー情報を含む/entry/jsonと含まない/entry/jsonlite/がある。

http://b.hatena.ne.jp/entry/json/?url=<URL>
http://b.hatena.ne.jp/entry/json/?url=https%3A%2F%2Fnote.nkmk.me
http://b.hatena.ne.jp/entry/jsonlite/?url=<URL>
http://b.hatena.ne.jp/entry/jsonlite/?url=https%3A%2F%2Fnote.nkmk.me

各種情報を含むJSONが返される。

キーbookmarksに日時やコメント、ユーザー名などの詳細情報の辞書を要素とするリストが格納されている。非公開IDの詳細情報は含まれていないので、このリストの要素数とURLのブックマーク数は一致しない場合がある。

Pythonコードの例。

辞書をきれいに表示するためにpprintを使っている。

hb_entry = 'http://b.hatena.ne.jp/entry/jsonlite/'

r = requests.get(hb_entry, params={'url': url})

print(r.url)
# http://b.hatena.ne.jp/entry/jsonlite/?url=https%3A%2F%2Fnote.nkmk.me

j = r.json()

import pprint

pprint.pprint(j)
# {'bookmarks': [{'comment': '',
#                 'tags': ['*Python'],
#                 'timestamp': '2018/06/02 10:52',
#                 'user': 'pkdick'},
#                {'comment': '',
#                 'tags': [],
#                 'timestamp': '2018/05/22 16:24',
#                 'user': 'ilford400'},
#                {'comment': '',
#                 'tags': ['python'],
#                 'timestamp': '2018/05/02 14:12',
#                 'user': 'yem3399op'}],
#  'count': 5,
#  'eid': '356280517',
#  'entry_url': 'http://b.hatena.ne.jp/entry/s/note.nkmk.me/',
#  'screenshot': 'http://b.hatena.ne.jp/images/v4/public/common/noimage.png',
#  'title': 'nkmk note',
#  'url': 'https://note.nkmk.me/'}

print(type(j['bookmarks']))
# <class 'list'>

print(type(j['bookmarks'][0]))
# <class 'dict'>

for b in j['bookmarks']:
    print(b['timestamp'])
# 2018/06/02 10:52
# 2018/05/22 16:24
# 2018/05/02 14:12

はてなブックマークWebページで取得できる情報

指定したURLとそれ以下のパスのURLのブックマーク情報はWebページで確認できる。

http://b.hatena.ne.jp/entrylist?url=<URL>
http://b.hatena.ne.jp/entrylist?url=https%3A%2F%2Fnote.nkmk.me

&sort=countを付けるとブックマーク数が多い順にソートされる。例えばQiitaで最もはてなブックマーク数が多いURLは以下で確認できる。

http://b.hatena.ne.jp/entrylist?url=https%3A%2F%2Fqiita.com%2F&sort=count

新着順(デフォルト)およびブックマーク数順(&sort=count)の場合はブックマーク数が3以上のURLしか掲載されないが、&sort=eidとするとブックマーク数が3未満のURLも掲載される。

指定したURLとそれ以下のパスのURLのブックマーク情報を取得したい場合は&sort=eidとしたページをクローリング・スクレイピングすればOK。後述。

なお、ブックマークされたURLが多いサイトの場合は限界がある模様。Qiitaの場合、345ページ目でサーバーエラーになった。

また、&mode=rssとするとRSSが取得できる。&sort=eidの1ページ目の情報が格納されている。

http://b.hatena.ne.jp/entrylist?url=<URL>&mode=rss
http://b.hatena.ne.jp/entrylist?url=https%3A%2F%2Fnote.nkmk.me&mode=rss

ブックマークが初めて付いた日時が新しい順に並んでいるので、このRSSをRSSリーダーなどに登録しておけば対象のサイトの新しいページにブックマークが付いたときに通知が受け取れる。すでにブックマークが付いているページに新たにブックマークが付いた場合は通知されないので注意。

Scrapyでクローリング・スクレイピング

上述のURLごとのブックマーク情報が掲載されたWebページをクローリング・スクレイピングすることで、APIでは取得できない、指定したURLとそれ以下のパスのURLのブックマーク数の内訳や詳細情報を取得できる。

ここではScrapyを使う。Scrapyの基本的な使い方は以下の記事を参照。

リポジトリは以下。

サイト内のURLのブックマーク数を一括取得

指定したURLとそれ以下のパスのURLのブックマーク数の内訳を取得するスパイダースクリプトは以下の通り。

「次のページ」のリンクを取得してクローリングして、URLとブックマーク数を抽出している。

import scrapy

class CountsSpider(scrapy.Spider):
    name = 'counts'
    allowed_domains = ['b.hatena.ne.jp']

    def __init__(self, url, *args, **kwargs):
        super(CountsSpider, self).__init__(*args, **kwargs)
        self.start_urls = ['http://b.hatena.ne.jp/entrylist?sort=eid&url=' + url]

    def parse(self, response):
        for li in response.css('div.entrylist-main li.js-keyboard-selectable-item'):
            d = {}
            d['url'] = li.css('h3.entrylist-contents-title a::attr(href)').extract_first()
            d['count'] = li.css('span.entrylist-contents-users a span::text').extract_first()
            yield d

        next_url = response.css('p.entrylist-readmore a::attr(href)').extract_first()
        if next_url:
            yield scrapy.Request('http://b.hatena.ne.jp' + next_url)
source: counts.py

scrapy crawlコマンドの引数でURLを指定するようになっている。

以下のコマンドで、指定したURLとそれ以下のパスのURLの個別のブックマーク数がCSVとして保存される。プロジェクトディレクトリ(scrapy.cfgがあるディレクトリ)で実行する。

$ scrapy crawl counts -a url="https://note.nkmk.me" -o results/counts.csv

ほかのPythonスクリプトから処理を実行したい場合は以下の記事を参照。

サイト内のURLのブックマーク日時・コメントなどを一括取得

指定したURLとそれ以下のパスのURLのブックマークの詳細情報を取得するスパイダースクリプトは以下の通り。

「次のページ」のリンクを取得してクローリングして、URLを抽出し、それぞれのURLに対して「はてなブックマークエントリー情報取得API」を呼んでいる。time.sleep()でリクエストの間隔をあけている。

キーbookmarksに格納されている辞書をupdate()メソッドで結果の辞書に追加、タグはjoin()メソッドを使ってリストをカンマ区切りの文字列に変換している。

import time
import requests
import scrapy

class DetailsSpider(scrapy.Spider):
    name = 'details'
    allowed_domains = ['b.hatena.ne.jp']

    def __init__(self, url, sleep_sec=1, *args, **kwargs):
        super(DetailsSpider, self).__init__(*args, **kwargs)
        self.start_urls = ['http://b.hatena.ne.jp/entrylist?sort=eid&url=' + url]
        self.sleep_sec = sleep_sec

    def parse(self, response):
        for li in response.css('div.entrylist-main li.js-keyboard-selectable-item'):
            d = {}
            url = li.css('h3.entrylist-contents-title a::attr(href)').extract_first()
            d['url'] = url

            time.sleep(self.sleep_sec)
            r = requests.get('http://b.hatena.ne.jp/entry/jsonlite/', params={'url': url})
            j = r.json()
            d['title'] = j['title']
            for b in j['bookmarks']:
                d.update(b)
                d['tags'] = ','.join(d['tags'])
                yield d

        next_url = response.css('p.entrylist-readmore a::attr(href)').extract_first()
        if next_url:
            yield scrapy.Request('http://b.hatena.ne.jp' + next_url)
source: details.py

以下のコマンドで、指定したURLとそれ以下のパスのURLに付いたブックマークの日時・コメントなどがCSVとして保存される。プロジェクトディレクトリ(scrapy.cfgがあるディレクトリ)で実行する。

待ち時間を指定したい場合は-a sleep_sec=<sec>を追加すればOK。

$ scrapy crawl details -a url="https://note.nkmk.me" -o results/details.csv

上述の通り、日時・コメントなどの情報は公開IDによるもののみなので、この結果の件数(CSVの行数)とブックマークの合計数は一致しない。

pandasによる分析

Scrapyで取得した詳細情報のデータをpandasで分析する例を示す。

件数を集約したい場合は値が1の列を追加しておくと便利。

import pandas as pd

df = pd.read_csv('results/details.csv')

df['count'] = 1

groupby()でURLやユーザーごとのブックマーク件数を集計(以下のサンプルコードはURLごと)。URLが省略されないようにpd.optionsを変更している。

なお、例のためにURLごとに集計したが、URLごとの件数は上述の1つ目のクローラーで取得可能。そちらのほうが非公開ID分もカウントされているので正しい件数になっている。

pd.options.display.max_colwidth = 80

print(df.groupby('url').sum().sort_values('count', ascending=False).head())
#                                                                  count
# url                                                                   
# https://note.nkmk.me/python-pandas-datareader-stock-population/     37
# https://note.nkmk.me/python-pandas-read-csv-tsv/                     8
# https://note.nkmk.me/python-pandas-web-html-table-scraping/          7
# https://note.nkmk.me/python-matplotlib-seaborn-basic/                6
# https://note.nkmk.me/pandas/                                         5

日時データに従って期間ごとにブックマーク件数を集計。時系列データに変換してresample()を使う。

df_ts = df.set_index('timestamp')
df_ts.index = pd.to_datetime(df_ts.index)

print(df_ts.resample('MS').sum())
#             count
# timestamp        
# 2017-11-01      1
# 2017-12-01      1
# 2018-01-01      9
# 2018-02-01     45
# 2018-03-01      8
# 2018-04-01     18
# 2018-05-01     37
# 2018-06-01     30
# 2018-07-01     42
# 2018-08-01     32

Qiitaのいいね数を題材にした、pandasを使ったデータ分析のより詳しい例は以下の記事を参照。

スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事