プログラマ・アゲイン blog

還暦を過ぎたけどプログラマ復帰を目指してブログ始めました

はてなブログのblog一覧を作成するPythonプログラム

Python言語で、初めてプログラミングしてみました。

作成したプログラムは、はてなブログの自分のブログ一覧を作成するものです。

 

pagain.hatenablog.com

 

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を開発したいと思います。