「はてなブログ」の新着を知りたい」という記事を昨年末にアップしました。これはこれで使いみちがあります。その後、「はてなブログ グループをスクレイピングして調べてみた」という記事を書いています。
このスクレイピングが「はてなブログ」の新着を知るのに便利なので、今はこちらを主に使っています。今回の記事でこの時のpython3スクリプトを載せます。
「はてなブログ グループ」に参加していて、python3が使える環境をお持ちの方でしたら利用できる筈です。ただ私の手元にWindowsのpython3とExcelが無いものですから、linux 環境で書かせて頂きます。多分、スクリプトの起動方法が違うだけで動かせると思います。
機能
自分が参加している「はてなブログ グループ」で新しくアップされた記事のURL、記事タイトル等を取り出して記事一覧をCSVファイルとして出力します。
出力されたCSVファイルを表計算ソフトに取り込んで一覧で確認すると興味のある記事を見つけやすくなります。また表計算ソフトにはハイパーリンクの機能がありますので、見つけたページを楽に閲覧することができます。
スクリプトの説明
必要なライブラリー
- requests 別途インストールが必要です
参照 https://requests-docs-ja.readthedocs.io/en/latest/user/install/ - HTMLParser 標準ライブラリです
参照 https://docs.python.org/ja/3/library/html.parser.html - csv 標準ライブラリです
参照 https://docs.python.org/ja/3/library/csv.html
スクレイピングについて書かれている多くの記事ではBeautifulSoupが紹介されていますが、できるだけ標準ライブラリーだけにしたかったので、HTMLParserを使っています。
ログイン情報
自分が参加しているグループにアクセスするためにログインする必要が有ります。ログインに必要なIDとパスワードはスクリプト内に埋め込みます。外部ファイルにしても良かったけれど、管理の必要がない埋め込みにしました。
この記事の末尾にあるスクリプト最後の方の行に記述してある次の文の文字列、はてなIDとパスワードを自分のものに置き換えて下さい。
if __name__ == '__main__': myid = 'はてなID' mypassword = 'パスワード'
仕様
- python3で実行できます。
- 対象グループはログインしたはてなIDで参加しているグループ全部です。
無料で参加できるグループ数は3つだったと思います。 - グループに含まれているタイトルは当日分と前日分の2日分のみを取り出します。
全部を対象とすると大量になりすぎる恐れがあるため制限を掛けています。 - それでも大量になる可能性があるので念の為、それぞれのグループ内のページ数を100件に制限しています。
- 1ページあたり10件の記事になります。
- サーバーに負荷を掛けないように1ページ取り込む毎に0.3秒の待機時間を入れています。
- 非公開になっているブログが見つかった場合は無視し、出力されません。
- 出力は項目をカンマで区切ったCSV形式で出力します。
- 結果は現在いるカレントディレクトリに、ファイル名"Group参加blog一覧.csv"で出力されます。
使い方
linuxの場合はスクリプトファイルに実行権限を与えておきます。そうするとGUIでスクリプトファイルのアイコンをダブルクリックするだけで起動できます。
表計算(Calc)での使い方
"Group参加blog一覧.csv"をダブルクリックするとCalcが起動します。WindowsならExcelが起動します。Calcしか持ってないのでCalcで説明しますがExcelでも同等のことが出来ると思います。
Calcの場合
Calcが起動するとテキストのインポート画面になり、読み込むことが出来ます。linuxの場合はエンコードはUnicode(UTF-8)で読み込めます。たまにWindowsで作られたファイルを扱うとShift-JISまたはWindows-932になっている場合が有り、そのまま読み込むと文字化けすることがあります。その場合はUnicode(UTF-8)にして読み込みます。
読み込むとセル幅が調整されてなくてすべての列が表示されないので、セル幅調整をします。
項目は次の様になっています。
グループ名, url, id, ブログ名, 記事日付, 記事URL, 記事タイトル 実際には、url, id, ブログ名は必要ないと言えば必要ありません。最低限、記事日付、記事URL,記事タイトルが見えるようにします。
記事URLから該当記事を訪れるには、記事URLにハイパーリンクをつかっています。設定する操作が必要です。
- 記事URLのセル内のURL文字列の最後にカーソルを起きます。
- そのままマウスでクリックしたままに他のセル上に移動してクリックするとハイパーリンクが設定されて記事URL内の文字列の色が水色変わります。
- ハイパーリンクが設定されると、Ctrlキーを押しながら記事URLのセルをクリックすると目的のブログが開きます。
同じIDの人の記事が複数のグループに現れる場合が有ります。並べ替えるなどして適当に処置して下さい。
スクリプト
以下のコードを取り出し、テキストファイルに保存した後、実行権限を付与して下さい。
ファイル名はget_hatena_group.pyとしました。
#!/usr/bin/env python3
"""
get_hatena_group.py
はてなブログの「読者参加しているグループ」の一覧を作成する
Created on Wed Jan 15 11:59:45 2020
@author: koukaforest
"""
import requests
import time
from html.parser import HTMLParser
import csv
import datetime
sess = ''
login = ''
'''
ログイン
'''
def HatenaLogin(myid, mypassword):
global sess, login
login_url = 'https://www.hatena.ne.jp/login?via=200125'
login_data = {'name': myid, 'password': mypassword, }
sess = requests.session()
sess.get(login_url)
try:
login = sess.post(login_url, data = login_data)
except Exception as err:
print('login 失敗!', err)
return False
return True
'''
各グループのページのパース
'''
class GroupHTMLParser(HTMLParser):
def __init__(self):
HTMLParser.__init__(self)
self.li = False
self.a_user_id = False
self.a_blog_name = False
self.a_title = False
self.span_time = False
self.span_title = False
self.time = False
self.p_more = False
self.nextpage = ''
self.blog_list = []
self.i = -1
def handle_starttag(self, tag, attrs):
attrs = dict(attrs) # タプルだと扱いにくいので辞書にする
if tag == 'li':
if ('class' in attrs) :
if attrs['class'] == 'hentry':
self.li = True
if tag == 'a' and self.li :
if ( 'class' in attrs) :
if attrs['class'] == 'user-id':
# ブログトップurlの取り出し
self.blog_list.append([attrs['href']])
self.i = self.i + 1
self.a_user_id = True
if attrs['class'] == 'blog-name':
self.a_blog_name = True
if attrs['class'] == "btn btn-large btn-full":
if self.p_more:
self.nextpage = attrs['href']
else:
if self.span_title:
# 記事urlの取り出し
self.blog_list[self.i].append(attrs['href'])
self.a_title = True
if tag == 'a' and (self.li == False):
if ('class' in attrs) :
if attrs['class'] == "btn btn-large btn-full":
if self.p_more:
self.nextpage = attrs['href']
if tag == 'span' and self.li:
if ('class' in attrs) :
if attrs['class'] == 'blog-timestamp':
self.span_time = True
if attrs['class'] == 'blog-entry-title':
self.span_title = True
if tag == 'time' and self.li and self.span_time:
if ('class' in attrs):
# 更新日の取り出し
#self.blog_list[self.i].append(attrs['class'])
if attrs['class'] == 'updated':
self.time = True
if tag == 'p' and ('class' in attrs ) :
if attrs['class']== 'more-blogs more':
self.p_more = True
def handle_endtag(self, tag):
if tag == 'li' and self.li :
self.li = False
if tag == 'a' :
self.a_user_id = False
self.a_blog_name = False
self.a_title = False
if tag == 'span' :
self.span_time = False
self.span_title = False
if tag == 'p' :
self.p_more = False
if tag == 'time' and self.span_time:
self.time = False
def handle_data(self, data):
if self.a_user_id:
# ユーザーIDの取り出し
self.blog_list[self.i].append(data)
if self.a_blog_name:
# ブログ名の取り出し
self.blog_list[self.i].append(data)
if self.a_title :
# 記事タイトルの取り出し
self.blog_list[self.i].append(data)
if self.time :
# 更新日の取り出し
self.blog_list[self.i].append(data[0:10])
def get_list(self):
return self.blog_list
def get_nextpage_url(self):
return self.nextpage
'''
「読者参加しているグループ」のページのパース
'''
class GrouplistHTMLParser(HTMLParser):
def __init__(self):
HTMLParser.__init__(self)
self.div = False
self.a = False
self.group_list = []
self.i = -1
def handle_starttag(self, tag, attrs):
attrs = dict(attrs) # タプルだと扱いにくいので辞書にする
if tag == 'div':
if ('class' in attrs) :
if attrs['class'] == 'circle-name':
self.div = True
if tag == 'a' and self.div:
# グループのURL
self.group_list.append([attrs['href']])
self.i = self.i + 1
self.a = True
def handle_endtag(self, tag):
if tag == 'div' :
self.div = False
if tag == 'a' :
self.a = False
def handle_data(self, data):
if self.a :
# グループ名
self.group_list[self.i].append(data)
def get_list(self):
return self.group_list
'''
「参加しているグループ」からリストを取り出す。
'''
def get_group_list(url):
lst = []
# 今日の年月を取得して前日を取得設定
date1 = datetime.datetime.now() - datetime.timedelta(days=1)
date_ymd1 = datetime.datetime.date(date1)
while url != '':
try:
r = sess.get(url, timeout=3.5) #timeout 3.5sec
except ConnectionError:
print('reqestsで接続エラー! url=', url)
return []
print('「参加しているグループ」に接続しました')
htmlbody=r.text
parser = GrouplistHTMLParser()
parser.feed(htmlbody)
parser.close()
for l in parser.get_list():
lst.append(l)
url=''
group_blog_list=[]
for l in lst:
page_cnt = 0
url = l[0]
group_name = l[1]
while url != '' :
if page_cnt < 100:
page_cnt = page_cnt + 1
else :
break
try:
r = sess.get(url, timeout=3.5)
except ConnectionError:
print('reqestsで接続エラー! url=', url)
return []
print(group_name, 'のページ', page_cnt, 'に接続しました。')
htmlbody=r.text
parser = GroupHTMLParser()
parser.feed(htmlbody)
parser.close()
blogs = parser.get_list()
time.sleep(0.3)
change_sw = False
for x in blogs:
x.insert(0, l[1])
'''
0:グループ, 1:ブログurl, 2:id, 3:ブログ名, 4:更新日
5:記事url, 6:記事title
'''
if len(x) == 7:
# 記事登録日
date2 = datetime.datetime.strptime(x[4], '%Y-%m-%d')
date_ymd2 = datetime.datetime.date(date2)
date_sa = (date_ymd1 - date_ymd2)
if date_sa.days > 0: # マイナスでなければ前日以前
change_sw = True # このグループ最後まで無視
break
group_blog_list.append(x)
else:
print('非公開に設定されたブログを無視しました。', x[1])
if change_sw :
url =''
else:
nextpage = parser.get_nextpage_url()
if nextpage == '':
url = ''
else:
p = url.find('?', 0)
if p == -1:
nextpage_url=url
else:
nextpage_url = url[0: p]
url = nextpage_url + nextpage
with open('Group参加blog一覧.csv', 'w') as f:
writer = csv.writer(f)
writer.writerow(['グループ名', 'url', 'id', 'ブログ名', '記事日付',
'記事URL', '記事タイトル'])
for l in group_blog_list:
writer.writerow(l)
return lst
if __name__ == '__main__':
myid = 'はてなID'
mypassword = 'パスワード'
url = 'https://blog.hatena.ne.jp/-/group/'
if HatenaLogin(myid, mypassword) :
# ログインに成功したら
group_list = get_group_list(url)
else :
print('ログインに失敗しました')
print('終了しました。')
input('Enterキーを押して下さい')
参考記事: 「はてなブログ」の新着を知りたい https://koukaforest.hatenablog.com/entry/2019/12/08/01000
「はてなブログ グループをスクレイピングして調べてみた」 https://koukaforest.hatenablog.com/entry/2020/01/18/233000
html.parser--- HTML および XHTML のシンプルなパーサー https://docs.python.org/ja/3/library/html.parser.html
Python3 HTMLParserによるウェブスクレイピング実践入門 https://qiita.com/Taillook/items/a0f2c59d8e17381fc835
Ubuntu スクリプトをダブルクリックで実行する方法 https://koukaforest.hatenablog.com/entry/2019/11/21/010000