南びわ湖エリア情報

草津市・守山市・栗東市周辺の情報を発信していきます

pythonでスクレイピング(beautifulsoup 基礎編)

pythonスクレイピング(beautifulsoup)について、独学を進めます。

f:id:minamibiwako:20210305000634p:plain

前回の pythonのつづき、スクレイピング(requests) からの続きになります。

PC作業環境

OS: windows 8.1

pythonインストールできた状態なら

この記事の .pyファイルとバッチファイルをコピペして、バッチファイルをダブルクリックすれば動作確認できますので、ぜひ試してみて下さい!

 

 

目次 

 

 

引き続き、この動画シリーズをベースに進めます。 

beautifulsoup(find)

f:id:minamibiwako:20210305001527p:plain

パース(解析)ライブラリの beautifulsoup を動かして見ていきます。

 

 

まず、取得したhtml が、

beautifulsoup での解析が可能かどうか? 「str」型であるか確認します。

f:id:minamibiwako:20210305020033p:plain

取得した html の「型」を確認します。

 

pythonコード get_html_003.py

# coding: shift_jis
import io,sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')

url = "https://minamibiwako.hatenablog.jp/"
import requests
html = requests.get(url)
print(html.url)
print()
print(html.text)
print()
print("取得したhtmlのtype判定")
html_type = type(html.text)
print(html_type) 

不明文字処理

・「南びわ湖エリア情報」のトップページの html を取得

・その html の型を確認 

 

バッチファイルpythonコードを実行) do_py006.bat

rem do_py006.bat
@echo off
set year=%date:~0,4%
set month=%date:~5,2%
set day=%date:~8,2%
set time2=%time: =0%
set hour=%time2:~0,2%
set minute=%time2:~3,2%
set second=%time2:~6,2%
set logname=%year%-%month%-%day%_%hour%_%minute%_%second%

python get_html_003.py >> %logname%_get_html_003.txt

 

 

出力結果(1)

https://minamibiwako.hatenablog.jp/

<!DOCTYPE html>
<html
lang="ja"

data-admin-domain="//blog.hatena.ne.jp"
data-admin-origin="https://blog.hatena.ne.jp"
data-author="minamibiwako"
data-avail-langs="ja en"
data-blog="minamibiwako.hatenablog.jp"
data-blog-comments-top-is-new="1"
data-blog-host="minamibiwako.hatenablog.jp"
data-blog-is-public="1"
data-blog-name="南びわ湖エリア情報"

省略

 

取得したhtmlのtype判定
<class 'str'>

よって、取得した html の type は「str」型であることが確認できました。

取得した html は、beautifulsoup で解析が可能です。

 

ここで、取得した html を全て出力していると、ダラダラと長くなって見にくいので先頭の12行だけ(data-blog-name=""まで)を表示させてみます。pandasライブラリの

head() が使えそうなので試してみます。

pythonの良い所は、外部ライブラリがたくさん公開されていて、「こんなことをやりたい」と思ってググったら、いろいろ出てきて、簡単に試せるところです。

 

今のインストール済みの「ライブラリを確認」します。これもバッチファイルにしておくと、いつでもバッチファイルをダブルクリックするだけで確認できます。

 

バッチファイル pip_list.bat

rem pip_list.bat
@echo off
set year=%date:~0,4%
set month=%date:~5,2%
set day=%date:~8,2%
set time2=%time: =0%
set hour=%time2:~0,2%
set minute=%time2:~3,2%
set second=%time2:~6,2%
set logname=%year%-%month%-%day%_%hour%_%minute%_%second%

python -m pip list >> %logname%_pip_list.txt

 

出力結果(2)

Package Version
-------------- ----------
beautifulsoup4 4.8.1
certifi 2019.11.28
chardet 3.0.4
idna 2.8
pip 21.0.1
requests 2.22.0
setuptools 41.2.0
soupsieve 1.9.5
urllib3 1.25.7

pandasライブラリが入っていないことが確認できました。 

 

pandasのインストール

「pip install pandas」

f:id:minamibiwako:20210306025445p:plain

pandas のインストールが完了しました。

外部ライブラリのインストール(pip)などについては、ここで復習できます。

blog.livedoor.jp

 

ライブラリのリストを確認します。 

バッチファイル pip_list.bat

rem pip_list.bat
@echo off
set year=%date:~0,4%
set month=%date:~5,2%
set day=%date:~8,2%
set time2=%time: =0%
set hour=%time2:~0,2%
set minute=%time2:~3,2%
set second=%time2:~6,2%
set logname=%year%-%month%-%day%_%hour%_%minute%_%second%

python -m pip list >> %logname%_pip_list.txt

 

出力結果(3)

 Package Version
--------------- ----------
beautifulsoup4 4.8.1
certifi 2019.11.28
chardet 3.0.4
idna 2.8
numpy 1.20.1
pandas 1.2.3
pip 21.0.1
python-dateutil 2.8.1
pytz 2021.1
requests 2.22.0
setuptools 41.2.0
six 1.15.0
soupsieve 1.9.5
urllib3 1.25.7

pandas がインストールできたことが確認できました。

pandas の他にも、いろいろ入ってるw けど、気にせず進みます。 

 

f:id:minamibiwako:20210305022357p:plain

beautifulsoup のインポート

 

f:id:minamibiwako:20210306131911p:plain

実行時にプロンプトに何かのワーニングが出てるけど、そのまま進めます。

parse_html_001.py:33: UserWarning: No parser was explicitly specified, so I'm us
ing the best available HTML parser for this system ("html.parser"). This usually
isn't a problem, but if you run this code on another system, or in a different
virtual environment, it may use a different parser and behave differently.

The code that caused this warning is on line 33 of the file parse_html_001.py. T
o get rid of this warning, pass the additional argument 'features="html.parser"'
to the BeautifulSoup constructor.

soup = BeautifulSoup(html.text)
続行するには何かキーを押してください . . .

最適な使い方でないようですが、特に問題ないのでこのままいきます。

UserWarning:パーサーが明示的に指定されていないため、このシステムで利用可能な最良のHTMLパーサー( "html.parser")を使用しています。
これは通常、問題ではありませんが、このコードを別のシステムまたは別のシステムで実行する場合、仮想環境では、異なるパーサーを使用し、動作が異なる場合があります。

 

f:id:minamibiwako:20210306133331p:plain

h2タグのテキストの取得のやり方まで分かったので、実際に動かしてみます。

取得する html は、びわ湖エリア情報の記事(h3タグを使ってる)にします。

南びわ湖街道ウォーカー - 南びわ湖エリア情報


pythonコード parse_html_001.py

# coding: shift_jis
import io,sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')

url = "https://minamibiwako.hatenablog.jp/entry/2021/02/21/084946"
import requests
html = requests.get(url)
print("@@@@@@@@@@_1")
print("htmlを取得したurl")
print(html.url)

print("@@@@@@@@@@_2")
print("取得したhtml")
print("長いので先頭の12行のみ表示")
myfile = open("output.txt", "w", encoding="utf-8")
print(html.text, file=myfile)
myfile.close()
import pandas as pd
data = pd.read_table("output.txt")
print(data.head(12))

print("@@@@@@@@@@_3")
print("取得したhtmlのtype判定")
html_type = type(html.text)
print(html_type)

print("@@@@@@@@@@_4")
from bs4 import BeautifulSoup
soup = BeautifulSoup(html.text)
print(soup.h3.text)
print(soup.find("h3").text)

不明文字処理

・1. htmlを取得したurl

・2. 取得したhtml

・3. 取得したhtmlのtype判定

・4. html解析(h3タグ取得)

 

バッチファイル do_py007.bat

rem do_py007.bat
@echo off
set year=%date:~0,4%
set month=%date:~5,2%
set day=%date:~8,2%
set time2=%time: =0%
set hour=%time2:~0,2%
set minute=%time2:~3,2%
set second=%time2:~6,2%
set logname=%year%-%month%-%day%_%hour%_%minute%_%second%

python parse_html_001.py >> %logname%_parse_html_001.txt

pause 

 

出力結果(4)

@@@@@@@@@@_1
htmlを取得したurl
https://minamibiwako.hatenablog.jp/entry/2021/02/21/084946
@@@@@@@@@@_2
取得したhtml
長いので先頭の12行のみ表示
<!DOCTYPE html>
0 <html
1 lang="ja"
2 data-admin-domain="//blog.hatena.ne.jp"
3 data-admin-origin="https://blog.hatena.ne.jp"
4 data-author="minamibiwako"
5 data-avail-langs="ja en"
6 data-blog="minamibiwako.hatenablog.jp"
7 data-blog-comments-top-is-new="1"
8 data-blog-host="minamibiwako.hatenablog.jp"
9 data-blog-is-public="1"
10 data-blog-name="南びわ湖エリア情報"
11 data-blog-owner="minamibiwako"
@@@@@@@@@@_3
取得したhtmlのtype判定
<class 'str'>
@@@@@@@@@@_4
「知識知恵+さんぽ」=聖地巡礼
「知識知恵+さんぽ」=聖地巡礼

head()が正しく動作していることが確認できました。
指定した url の h3タグの1個目が抽出できていることが分かります。 

 

f:id:minamibiwako:20210306140123p:plain

タグを取得するとき:soup.find()

テキストを取得するとき:soup.find().text

 

 

【演習】ブログタイトルをテキストで取得する

pythonコード parse_html_002.py

# coding: shift_jis
import io,sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')

url = "https://minamibiwako.hatenablog.jp/entry/2021/02/21/084946"
import requests
html = requests.get(url)
print("@@@@@@@@@@_1")
print("htmlを取得したurl")
print(html.url)

print("@@@@@@@@@@_2")
print("取得したhtml")
print("長いので先頭の12行のみ表示")
myfile = open("output.txt", "w", encoding="utf-8")
print(html.text, file=myfile)
myfile.close()
import pandas as pd
data = pd.read_table("output.txt")
print(data.head(12))

print("@@@@@@@@@@_3")
print("取得したhtmlのtype判定")
html_type = type(html.text)
print(html_type)

print("@@@@@@@@@@_4")
from bs4 import BeautifulSoup
soup = BeautifulSoup(html.text)
print(soup.find("title").text)

不明文字処理

・1. htmlを取得したurl

・2. 取得したhtml

・3. 取得したhtmlのtype判定

・4. html解析(titleタグ取得)

 

 

バッチファイル do_py008.bat

rem do_py008.bat
@echo off
set year=%date:~0,4%
set month=%date:~5,2%
set day=%date:~8,2%
set time2=%time: =0%
set hour=%time2:~0,2%
set minute=%time2:~3,2%
set second=%time2:~6,2%
set logname=%year%-%month%-%day%_%hour%_%minute%_%second%

python parse_html_002.py >> %logname%_parse_html_002.txt

rem pause

 

出力結果(5) 

@@@@@@@@@@_1
htmlを取得したurl
https://minamibiwako.hatenablog.jp/entry/2021/02/21/084946
@@@@@@@@@@_2
取得したhtml
長いので先頭の12行のみ表示
<!DOCTYPE html>
0 <html
1 lang="ja"
2 data-admin-domain="//blog.hatena.ne.jp"
3 data-admin-origin="https://blog.hatena.ne.jp"
4 data-author="minamibiwako"
5 data-avail-langs="ja en"
6 data-blog="minamibiwako.hatenablog.jp"
7 data-blog-comments-top-is-new="1"
8 data-blog-host="minamibiwako.hatenablog.jp"
9 data-blog-is-public="1"
10 data-blog-name="南びわ湖エリア情報"
11 data-blog-owner="minamibiwako"
@@@@@@@@@@_3
取得したhtmlのtype判定
<class 'str'>
@@@@@@@@@@_4
びわ湖街道ウォーカー - 南びわ湖エリア情報

ブログタイトル「南びわ湖街道ウォーカー」が取得できていることが分かります。

 

 

 

指定したタグの1個目は取得できたので、複数個の取得について見ていきます。

beautifulsoup(find_all) 

 

f:id:minamibiwako:20210306145047p:plain

find_all は、for文を使って、それぞれのテキストを取得します。
 

pythonコード parse_html_003.py 

# coding: shift_jis
import io,sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')

url = "https://minamibiwako.hatenablog.jp/entry/2021/02/21/084946"
import requests
html = requests.get(url)
print("@@@@@@@@@@_1")
print("htmlを取得したurl")
print(html.url)

print("@@@@@@@@@@_2")
print("取得したhtml")
print("長いので先頭の12行のみ表示")
myfile = open("output.txt", "w", encoding="utf-8")
print(html.text, file=myfile)
myfile.close()
import pandas as pd
data = pd.read_table("output.txt")
print(data.head(12))

print("@@@@@@@@@@_3")
print("取得したhtmlのtype判定")
html_type = type(html.text)
print(html_type)

print("@@@@@@@@@@_4")
from bs4 import BeautifulSoup
soup = BeautifulSoup(html.text)
print(soup.find("title").text)

print("@@@@@@@@@@_5")
for h3_tag in soup.find_all("h3"):
print(h3_tag.text) 

不明文字処理

・1. htmlを取得したurl

・2. 取得したhtml

・3. 取得したhtmlのtype判定

・4. html解析(titleタグ取得)

・5. html解析(h3タグ取得) 

 

バッチファイル do_py009.bat

rem do_py009.bat
@echo off
set year=%date:~0,4%
set month=%date:~5,2%
set day=%date:~8,2%
set time2=%time: =0%
set hour=%time2:~0,2%
set minute=%time2:~3,2%
set second=%time2:~6,2%
set logname=%year%-%month%-%day%_%hour%_%minute%_%second%

python parse_html_003.py >> %logname%_parse_html_003.txt

rem pause

 

出力結果(6)

@@@@@@@@@@_1
htmlを取得したurl
https://minamibiwako.hatenablog.jp/entry/2021/02/21/084946
@@@@@@@@@@_2
取得したhtml
長いので先頭の12行のみ表示
<!DOCTYPE html>
0 <html
1 lang="ja"
2 data-admin-domain="//blog.hatena.ne.jp"
3 data-admin-origin="https://blog.hatena.ne.jp"
4 data-author="minamibiwako"
5 data-avail-langs="ja en"
6 data-blog="minamibiwako.hatenablog.jp"
7 data-blog-comments-top-is-new="1"
8 data-blog-host="minamibiwako.hatenablog.jp"
9 data-blog-is-public="1"
10 data-blog-name="南びわ湖エリア情報"
11 data-blog-owner="minamibiwako"
@@@@@@@@@@_3
取得したhtmlのtype判定
<class 'str'>
@@@@@@@@@@_4
びわ湖街道ウォーカー - 南びわ湖エリア情報
@@@@@@@@@@_5
「知識知恵+さんぽ」=聖地巡礼
東海道さんぽ 
中山道さんぽ 
びわ湖さんぽ 
はてなブログをはじめよう!

find_all を使って、h3タグをすべて取得できていることが確認できました。 

 

 

f:id:minamibiwako:20210306173229p:plain

確かに、上の出力結果(6)には、「はてなブログをはじめよう!」というノイズが含まれていますね。

 

南びわ湖街道ウォーカー - 南びわ湖エリア情報

f:id:minamibiwako:20210306173355p:plain

この記事を書いている時点での見出し(h3タグ)は3つ
東海道さんぽ 
中山道さんぽ 
びわ湖さんぽ 

※2021年2月21日時点の 出力結果(6)ですが、

今後、追記していく記事なので、h3タグが変化している可能性があります。その点、注意願います!
 

 

f:id:minamibiwako:20210315071609p:plain

取得する範囲を狭くすることで、ノイズを消すために

articleタグをmybodyに入れます。


pythonコード parse_html_004.py  

# coding: shift_jis
import io,sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')

url = "https://minamibiwako.hatenablog.jp/entry/2021/02/21/084946"
import requests
html = requests.get(url)
print("@@@@@@@@@@_1")
print("htmlを取得したurl")
print(html.url)

print("@@@@@@@@@@_2")
print("取得したhtml")
print("長いので先頭の12行のみ表示")
myfile = open("output.txt", "w", encoding="utf-8")
print(html.text, file=myfile)
myfile.close()
import pandas as pd
data = pd.read_table("output.txt")
print(data.head(12))

print("@@@@@@@@@@_3")
print("取得したhtmlのtype判定")
html_type = type(html.text)
print(html_type)

print("@@@@@@@@@@_4")
from bs4 import BeautifulSoup
soup = BeautifulSoup(html.text)
print(soup.find("title").text)

print("@@@@@@@@@@_5")
for h3_tag in soup.find_all("h3"):
print(h3_tag.text)

print("@@@@@@@@@@_6_1")
mybody = soup.find("article")
#print(soup.find("article"))

print("@@@@@@@@@@_6_2")
for h3_tag in mybody.find_all("h3"):
print(h3_tag.text)

  

バッチファイル do_py010.bat 

rem do_py010.bat
@echo off
set year=%date:~0,4%
set month=%date:~5,2%
set day=%date:~8,2%
set time2=%time: =0%
set hour=%time2:~0,2%
set minute=%time2:~3,2%
set second=%time2:~6,2%
set logname=%year%-%month%-%day%_%hour%_%minute%_%second%

python parse_html_004.py >> %logname%_parse_html_004.txt

rem pause

 

出力結果(7)

@@@@@@@@@@_1
htmlを取得したurl
https://minamibiwako.hatenablog.jp/entry/2021/02/21/084946
@@@@@@@@@@_2
取得したhtml
長いので先頭の12行のみ表示
<!DOCTYPE html>
0 <html
1 lang="ja"
2 data-admin-domain="//blog.hatena.ne.jp"
3 data-admin-origin="https://blog.hatena.ne.jp"
4 data-author="minamibiwako"
5 data-avail-langs="ja en"
6 data-blog="minamibiwako.hatenablog.jp"
7 data-blog-comments-top-is-new="1"
8 data-blog-host="minamibiwako.hatenablog.jp"
9 data-blog-is-public="1"
10 data-blog-name="南びわ湖エリア情報"
11 data-blog-owner="minamibiwako"
@@@@@@@@@@_3
取得したhtmlのtype判定
<class 'str'>
@@@@@@@@@@_4
びわ湖街道ウォーカー - 南びわ湖エリア情報
@@@@@@@@@@_5
「知識知恵+さんぽ」=聖地巡礼
東海道さんぽ 
中山道さんぽ 
びわ湖さんぽ 
はてなブログをはじめよう!
@@@@@@@@@@_6_1
@@@@@@@@@@_6_2
「知識知恵+さんぽ」=聖地巡礼
東海道さんぽ 
中山道さんぽ 
びわ湖さんぽ 

取得する範囲を狭くすることで、ノイズを消せていることが確認できました。

該当する要素を削除する方法(extractを使う)は「破壊的な」方法になるのでなるべく使わないほうが良いとのことです。 

 

 


つづきは、pythonでスクレイピング(beautifulsoup 応用編) で

滋賀を盛り上げていく旅は続く

 

 

 

■■■■■■■■■■■■■■■■■■■■■■■■■■■■

■■■■■■■■■■■■■■■■■■■■■■■■■■■■

以下、ネット上の反応