2018年8月5日日曜日

Pythonで地下鉄時刻表をスクレイピングする例

スクレイピングの例です。 

※自己流なので別にこういう手順と決まっていません
※追記: 時刻表がリニューアルされていて、htmlページは使えません※
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

大阪メトロ谷町線、天神橋筋六丁目駅の時刻表をスクレイピングします。時刻表の何時何分発という部分だけを抜き出します。

https://kensaku.osakametro.co.jp/subway/dia/jikoku/jikoku2606110201.html
ここを選んだのは特に理由がないのですが、日本一長い商店街があることで有名ですね。



こんな感じで、平日と土日祝で分かれていて、5時から23時までです。

モジュールをインポートします。
from bs4 import BeautifulSoup
import urllib.request

htmlから、BeautifulSoupでデータを取得

方法1 ブラウザからhtmlをダウンロードします。

右クリックで保存したら終了。htmlファイルを残したいときはこれで良いと思います。今回はtenjin6.htmlというファイル名にしました。

file = 'tenjin6.html'

with open(file, encoding='cp932') as f:
    contents = f.read()
    soup = BeautifulSoup(contents, 'lxml')

方法2 request.urllib でhtmlを取得します。

こちらの方が楽ではありますが、失敗することがあります。

・・・これは失敗します。
url = 'https://kensaku.osakametro.co.jp/subway/dia/jikoku/jikoku2606110201.html'
html = urllib.request.urlopen(url).read().decode('utf-8')  
soup= BeautifulSoup(html, "lxml")
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x91 in position 597: invalid start byte
ということで、文字エンコーディングが間違っていて、ソースファイルをよく見るとSHIFT-JISと書いてあります。日本語ならutf-8かshift-jisかEUC-JPのどれかがほとんどです。


url = 'https://kensaku.osakametro.co.jp/subway/dia/jikoku/jikoku2606110201.html'
html = urllib.request.urlopen(url).read().decode('shift-jis')  
soup= BeautifulSoup(html, "lxml")

これでsoupの準備ができました。次はfind_all()でタグで文字を抽出します。


ソースを読んでタグを確認する

抽出するときはその部分が含まれるタグを指定します。そのため、ソースから該当部分を探して、どういうタグで囲まれているかを確認します。


ソースを見ると、時刻は<TR>タグの中に入っているようです。soup.find_all('tr')と小文字で'tr'とします(大文字だとエラー)。

時刻は<th>の中、分表記は<div class="item"><div></div></div>の中にあるようです。
soup.find_all('th')
とすると、どのようにタグで抽出できるか分かります。

find_allの結果はリストになっているので、1つだけを指定するときは[0]が必要です。

tdタグの中に分、thタグの中に時間が入っていることが分かりました。
soup.find_all('td')[10:]
とすると全ての分のリスト、
soup.find_all('th')[6:]
とすると全ての時間のリストが得られます。

div内のクラス指定class="item"はこう書きます。
soup.find_all('div', {'class':'item'})
出力はタグも含めて抽出されるので .text とすると文字だけが得られます。

たとえば
soup.find_all('div', {'class':'item'})[0].text
とすると、スペースがユニコード変換されてしまって\u300014 になってしまいます。ちょっと面倒なので、もう1度、find_allを使います。

soup.find_all('div', {'class':'item'})[0].find_all('div', {'class':None})

最終的にはこうなりました。
hour = [i.text for i in soup.find_all('th')[6:]]
minute = []
for h_sche in soup.find_all('td')[10:]:
    minute.append([m_sche.text for m_sche in h_sche.find_all('div',{'class':None})])
for i,j in zip(hour, minute):
    print(i, j)
Out:
5 ['14', '24', '34', '44', '54']
5 ['14', '24', '34', '44', '54']
6 ['04', '14', '23', '32', '38', '44', '49', '54', '58']
6 ['04', '13', '24', '34', '44', '55']
7 ['01', '04', '08', '12', '16', '19', '23', '26', '29', '32', '35', '38', '41', '44', '47', '50', '53', '56', '58']
7 ['03', '07', '11', '16', '22', '28', '34', '40', '45', '52', '58']
8 ['01', '04', '07', '09', '12', '15', '17', '20', '22', '25', '27', '30', '32', '35', '38', '41', '44', '47', '50', '54', '58']
8 ['04', '09', '14', '19', '23', '29', '35', '41', '48', '54']
9 ['01', '05', '10', '15', '21', '27', '34', '41', '49', '56']
9 ['01', '08', '15', '22', '29', '35', '41', '49', '56']
10 ['04', '11', '19', '26', '34', '41', '49', '56']
10 ['04', '11', '19', '26', '34', '41', '49', '56']
11 ['04', '11', '19', '26', '34', '41', '49', '56']
11 ['04', '11', '19', '26', '34', '41', '49', '56']
12 ['04', '11', '19', '26', '34', '41', '49', '56']
12 ['04', '11', '19', '26', '34', '41', '49', '56']
13 ['04', '11', '19', '26', '34', '41', '49', '56']
13 ['04', '11', '19', '26', '34', '41', '49', '56']
14 ['04', '11', '19', '26', '34', '41', '49', '56']
14 ['04', '11', '19', '26', '34', '41', '49', '56']
////
これで時刻と分だけが抽出できました。
urlを変えていけば他の駅でも可能ですね。
かなり自己流ですが、参考まで。

2 件のコメント:

  1. 突然失礼いたします。
    谷と申します。
    本記事の時刻の取得方法を参考にさせていただき、作成した時刻表表示システムの記事をQiitaに投稿したいと考えております。
    つきましては、このブログのurlをQiitaの記事に貼り付けてもよろしいでしょうか。
    ご検討のほどよろしくお願いします。

    返信削除
  2. こんにちは。リンクは自由にしていただいて大丈夫ですよ。

    返信削除