はてなブログのblog一覧を作成するPythonプログラム
Python言語で、初めてプログラミングしてみました。
作成したプログラムは、はてなブログの自分のブログ一覧を作成するものです。
Python言語自体は、Rubyに比べると書きにくい感じでしたが、強力なライブラリーがあることですんなり作成する事が出来ました。
ここでは、はてなブログの自分のトップ・ページをwebスクレイピングして、作成したブログの一覧をHTMLの表の形にファイル出力する、Pythonプログラムを記録したいと思います。
概要
はてなブログのトップ・ページ
自分のトップ・ページ(プログラマ・アゲイン blog)では、作成したブログの新しいもの順に、まず15件が表示されます。
16件目以降は、ページの一番下にある「>」または「次のページ」をクリックすると、別のページに表示されます。
この繰り返しで、最後のブログまで表示する事が出来ます。
プログラムでは、これらのページを追いかけていき、それぞれのHTMLを取得して処理します。
ブログ情報の取得
ブログの情報としては、「タイトル」、「ページのurl」、「日付」、「カテゴリー」を取得します。
はてなブログを対象にして、情報の取得はタグの固有の属性値から行っています。したがって、将来タグの属性値が変更された場合には、プログラムも変更する必要があります。
Pythonの環境
使用したPythonとライブラリーのバージョンは、以下です。
Python 3.10.5 Package Version ------------------ ----------- beautifulsoup4 4.11.1 certifi 2022.6.15 charset-normalizer 2.1.0 cycler 0.11.0 fonttools 4.34.2 idna 3.3 kiwisolver 1.4.3 matplotlib 3.5.2 numpy 1.23.0 opencv-python 4.6.0.66 packaging 21.3 Pillow 9.2.0 pip 22.1.2 pyparsing 3.0.9 python-dateutil 2.8.2 requests 2.28.1 six 1.16.0 soupsieve 2.3.2.post1 urllib3 1.26.9
Pythonの参考書
Pythonプログラムをコーディングするにあたって、以下の本で勉強しました。
webスクレイピングが一番最後に載っているので、参考にしています。
プログラム処理
Pythonのソース
Pythonのソースの全体は、以下のようになりました。
import requests
from bs4 import BeautifulSoup
# get blog list(title,pageurl,datetime,category) function
def getbloglist(soup):
title = []
pageurl = []
datetime = []
category = []
# select header tag from page html
for header_tag in soup.select('header'):
# select <h1 class="entry-title"> ... </h1>
h1_tag = header_tag.select_one('h1.entry-title')
if h1_tag != None:
title.append(h1_tag.text.replace('\n',''))
pageurl.append(h1_tag.select_one('a').get('href'))
else:
continue
# select time tag from page html
div_tag = header_tag.select_one('div.date')
datetime.append(div_tag.select_one('a').select_one('time').get('datetime'))
# select category div tag from page html
div_tag = header_tag.select_one('div.entry-categories')
cate = []
for c in div_tag.find_all('a'):
cate.append(c.text)
category.append(cate)
return (title,pageurl,datetime,category)
# get blog count function
def checkend(soup):
endflag = 0
nexturl = ''
# select year title tag and calculate total
nextpage = soup.select_one('span.pager-next')
if nextpage != None:
nexturl = nextpage.select_one('a').get('href')
else:
endflag = 1
return (endflag, nexturl)
def writehtmlfile(title,pageurl,datetime,category):
with open('hatena_blog_list.html', 'w', encoding='UTF-8') as out_file:
out_file.write('<table>' + '\n')
out_file.write('<tbody>' + '\n')
out_file.write('<tr>' + '\n')
out_file.write('<th>' + 'no.' + '</th>' + '\n')
out_file.write('<th>' + 'タイトル' + '</th>' + '\n')
out_file.write('<th>' + '日付' + '</th>' + '\n')
out_file.write('<th>' + 'カテゴリー' + '</th>' + '\n')
out_file.write('</tr>' + '\n')
for i in range(0, len(title)):
out_file.write('<tr>' + '\n')
out_file.write('<td>' + str(i + 1) + '</td>' + '\n')
out_file.write('<td><a href="' + pageurl[i] + '">' + title[i] + '</a></td>' + '\n')
out_file.write('<td>' + datetime[i][0:10] + '</td>' + '\n')
out_file.write('<td>' + ','.join(category[i]) + '</td>' + '\n')
out_file.write('</tr>' + '\n')
out_file.write('</tbody>' + '\n')
out_file.write('</table>' + '\n')
# main routine
# get and parse first page
url = 'https://pagain.hatenablog.com/'
title = []
pageurl = []
datetime = []
category = []
endflag = 0
while endflag == 0:
html = requests.get(url)
soup = BeautifulSoup(html.text, 'html.parser')
t, p, d, c = getbloglist(soup)
title += t
pageurl += p
datetime += d
category += c
endflag, url = checkend(soup)
# output html file
writehtmlfile(title,pageurl,datetime,category)
次に各処理について、メモします。
ライブラリー読み込み
まず最初に、必要となるライブラリーを読み込みます。
import requests
from bs4 import BeautifulSoup
requestsライブラリーは、インターネットからHTMLファイルを取得するために使用します。
BeautifulSoupライブラリーは、HTMLファイルを解析して情報を抽出するために使用します。bs4(Beautiful Soup バージョン4)は、BeautifulSoup4のモジュール名です。
これらのライブラリーは、事前に導入しておきます。
メイン処理
メイン処理は、以下の部分です。
# main routine
# get and parse first page
url = 'https://pagain.hatenablog.com/'
title = []
pageurl = []
datetime = []
category = []
endflag = 0
while endflag == 0:
html = requests.get(url)
soup = BeautifulSoup(html.text, 'html.parser')
t, p, d, c = getbloglist(soup)
title += t
pageurl += p
datetime += d
category += c
endflag, url = checkend(soup)
# output html file
writehtmlfile(title,pageurl,datetime,category)
3行目で、自分のブログのトップ・ページのURLをセットしています。
9行目から17行目で、ループしてブログのページの読み込みと解析処理を繰り替えします。最後のページまで処理すると、ループを抜けるようにフラグを使っています。
10行目で、URLからHTMLファイルを取得します。
11行目で、取得したHTMLファイルを解析して、BeautifulSoupのオブジェクトを生成します。
12行目で、BeautifulSoupオブジェクトから、必要な情報をリスト・オブジェクトに抜き出すユーザー関数を使用します。
13行目から16行目までで、1ページから抜き出した情報を、蓄積していきます。
17行目で、最後のページの判定をするユーザー関数を使用します。
19行目で、取得した情報から、HTMLの表を作成し、ファイルに出力するユーザー関数を使用します。
ブログリストの取得
ユーザー関数の getbloglist で、BeautifulSoupオブジェクトからページ内にあるブログの情報を取得します。
# get blog list(title,pageurl,datetime,category) function
def getbloglist(soup):
title = []
pageurl = []
datetime = []
category = []
# select header tag from page html
for header_tag in soup.select('header'):
# select <h1 class="entry-title"> ... </h1>
h1_tag = header_tag.select_one('h1.entry-title')
if h1_tag != None:
title.append(h1_tag.text.replace('\n',''))
pageurl.append(h1_tag.select_one('a').get('href'))
else:
continue
# select time tag from page html
div_tag = header_tag.select_one('div.date')
datetime.append(div_tag.select_one('a').select_one('time').get('datetime'))
# select category div tag from page html
div_tag = header_tag.select_one('div.entry-categories')
cate = []
for c in div_tag.find_all('a'):
cate.append(c.text)
category.append(cate)
return (title,pageurl,datetime,category)
2行目で、ユーザー関数の getbloglist を宣言し、引数としてsoupという変数を指定しています。
8行目から24行目まで、各ブログ・ページが記述されているheaderタグを、有るだけ処理します。
因みに、headerタグの例としては、以下のようなものです。
<header id="blog-title" data-brand="hatenablog"> <div id="blog-title-inner" style="background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/h/hanasakag/20220304/20220304125513.png'); background-position: center 0px;"> <div id="blog-title-content"> <h1 id="title"><a href="https://pagain.hatenablog.com/">プログラマ・アゲイン blog</a></h1> <h2 id="blog-description">還暦を過ぎたけどプログラマ復帰を目指してブログ始めました</h2> </div> </div> </header> <header class="entry-header"> <div class="date entry-date first"> <a href="https://pagain.hatenablog.com/archive/2022/06/17" rel="nofollow"> <time datetime="2022-06-17T08:30:57Z" title="2022-06-17T08:30:57Z"> <span class="date-year">2022</span><span class="hyphen">-</span><span class="date-month">06</span><span class="hyphen">-</span><span class="date-day">17</span> </time> </a> </div> <h1 class="entry-title"> <a href="https://pagain.hatenablog.com/entry/2022/06/17/173057" class="entry-title-link bookmark">古いGitリポジトリ―をGithubにpushしようとしたらセキュリティに引っ掛かりました</a> </h1> <div class="entry-categories categories"> <a href="https://pagain.hatenablog.com/archive/category/%E9%96%8B%E7%99%BA%E7%92%B0%E5%A2%83" class="entry-category-link category-開発環境">開発環境</a> </div> </header> ・・・
10行目で、headerタグ内のh1タグのオブジェクトを、h1_tag変数に取得しています。その際、class="entry_title" のものを指定しています。
最初のheaderタグには class="entry_title" が無いため、h1_tagの値は None になります。
11行目では、h1_tagの値が None でない場合に12、13行目を処理します。
12行目では、titleリストに、h1タグの改行コードを除いたtext部を追加します。
13行目では、pageurlリストに、h1タグの下のaタグのhref属性値を追加します。
17行目で、headerタグ内のdivタグでclass="data"のオブジェクトを、div_tag変数に取得しています。
18行目では、datetimeリストに、divタグの下のaタグの下のtimeタグのdatetime属性値を追加します。
20行目で、headerタグ内の別のdivタグでclass="entry-category"のオブジェクトを、div_tag変数に取得しています。
21行目から24行目では、categoryリストに、divタグの下のaタグのtext部を追加します。
ただ、1つのブログでカテゴリーが複数ある場合があり、その場合はそれに応じてaタグが複数あります。
そのため、全てのaタグをfor分で処理して、cateリストにカテゴリーをまとめた後、そのcateリストをcategoryリストに追加します。
31行目で、取得したタイトル、URL、日時、カテゴリーのリストをユーザー関数の戻り値として返します。
ブログリストの終わり判定
ユーザー関数の checkend で、BeautifulSoupオブジェクトから該当ページが最終ページかどうかを判定します。
# get blog count function
def checkend(soup):
endflag = 0
nexturl = ''
# select year title tag and calculate total
nextpage = soup.select_one('span.pager-next')
if nextpage != None:
nexturl = nextpage.select_one('a').get('href')
else:
endflag = 1
return (endflag, nexturl)
ちょっと、コメントがおかしいですが、ご容赦ください。
6行目で、class="pager-next" の spanタグを nextpage変数に取得します。
spanタグは、以下の divタグの下のようになっています。
<div class="pager autopagerize_insert_before"> <span class="pager-next"> <a href="https://pagain.hatenablog.com/?page=1629858208" rel="next">次のページ</a> </span> </div>
7行目から10行目までは、nextpageがNoneかどうか(「次のページ」が有るかどうか)により、処理を変えています。
Noneでない(「次のページ」が有る)場合は、次のページのURLをspanタグの下のaタグのhref属性から取得します。
そうでない場合には、endflagを立てます。
11行目で、エンドフラグと次のURLを、ユーザー関数の戻り値として返します。
HTMLファイルの作成
ユーザー関数の writehtmlfile で、取得したタイトル、URL、日時、カテゴリーの情報から、ブラウザー上で表の形で表示されるHTMLを作成し、ファイルに書き出します。
def writehtmlfile(title,pageurl,datetime,category):
with open('hatena_blog_list.html', 'w', encoding='UTF-8') as out_file:
out_file.write('<table>' + '\n')
out_file.write('<tbody>' + '\n')
out_file.write('<tr>' + '\n')
out_file.write('<th>' + 'no.' + '</th>' + '\n')
out_file.write('<th>' + 'タイトル' + '</th>' + '\n')
out_file.write('<th>' + '日付' + '</th>' + '\n')
out_file.write('<th>' + 'カテゴリー' + '</th>' + '\n')
out_file.write('</tr>' + '\n')
for i in range(0, len(title)):
out_file.write('<tr>' + '\n')
out_file.write('<td>' + str(i + 1) + '</td>' + '\n')
out_file.write('<td><a href="' + pageurl[i] + '">' + title[i] + '</a></td>' + '\n')
out_file.write('<td>' + datetime[i][0:10] + '</td>' + '\n')
out_file.write('<td>' + ','.join(category[i]) + '</td>' + '\n')
out_file.write('</tr>' + '\n')
out_file.write('</tbody>' + '\n')
out_file.write('</table>' + '\n')
2行目で、with文を使って "hatena_blog_list.html"というファイルを書き出しでopenします。
3行目から10行目までで、tableタグ、tbodyタグ、テーブルのヘッダー行を書き出します。
11行目から17行目までで、titleリストの要素数分繰り返し、テーブルのデータ行を書き出します。
13行目は、番号を文字にしています。
14行目は、タイトルにURLのリンクも付けています。
15行目は、日時から日付部分だけ抜き出しています。
16行目は、categoryリストを「,」区切りの文字にしています。
18行目から19行目までは、tbodyタグとtableタグの終わりを書き出しています。
結果として、以下のようなHTMLファイルが書き出されます。(抜粋)
<table> <tbody> <tr> <th>no.</th> <th>タイトル</th> <th>日付</th> <th>カテゴリー</th> </tr> <tr> <td>1</td> <td><a href="https://pagain.hatenablog.com/entry/2022/06/17/173057">古いGitリポジトリ―をGithubにpushしようとしたらセキュリティに引っ掛かりました</a></td> <td>2022-06-17</td> <td>開発環境</td> </tr> ・・・
Pythonの勉強として、webスクレイピングしてHTMLを作成するプログラミングをしてみましたが、いちいちHTML表をブログに貼り付けるのは面倒です。
そのうち、ページ表示時に、動的にwebスクレイピングで取得してHTMLの表を作成し表示するような、JavaScriptを開発したいと思います。