僕の世界観を変えてみる

文系男子が趣味でプログラミングを勉強していくブログです。他にも日常で起きたどうでもいいことや愚痴を書いていきたいです。座右の銘は和を以て貴しとなすです。仲良くやろうよ。

【Python3】BeautifulSoupとRequestsを使ったスクレイピング

f:id:htmllifehack:20171006071153j:plain

Pythonいじっていてあれこれどう書くんだっけ?ってことがよくありますよね。

僕もそんなわけなんでググるんですけど欲しい情報がなかなか出てこなかったり、

というかそこに時間を割きたくないので自分のブログをノート代わりに残そうと思います。

Pythonフォルダにたくさん入れておくのも嫌なので。

今回はBeautifulSoupとRequestsについてです。



BeautifulSoupの使い方

htmlから情報を引き出すモジュールです。

urlからじゃなくてhtmlからってところが肝です。

1.まずはインポート

from bs4 import Beautifulsoup

現在はBeautifulSopu4.2とかが出ています。

3はもう打ち切られたようなのでドキュメントは4系のものを見てください。

kondou.com - Beautiful Soup 4.2.0 Doc. 日本語訳 (2013-11-19最終更新)

バージョンが4系なのでfrom bs4です。

2.ソースを打ち込む

from bs4 import Beautifulsoup

html = '''
<html lang=ja>
<head>
    <meta charset="utf-8">
<title>文系男子がPython3で画像収集するスクリプトを作ってみた。bs4・requests編 - 僕の世界観を変えてみる</title>
</head>
<body>
    <div class="section">
        <h3 id="コード解説">コード解説</h3>
            <p>僕と同じ初心者の方がつまづかないように一応僕なりに解説しておきます。</p>
            <p>間違ってるかもしれないんですけど、大目に見てくだされ。</p>
            <p>デフォルトのhtml.parserでも結果は変わりません。</p>
            <p>次、<span class="markb"><b>for文でimgタグ</b></span>を取得します。</p>
            <p>最後が<span class="markb"><b>ローカルフォルダに画像データを保存する方法</b></span>です。</p>
            <p>まずリストに入れた画像データを読み込む必要があるのでrequests.getを使うんですけど</p>
            <p>images = </span>のリストそのままだと読んでくれません。</p>
      <p>なんかよくわからないけどサポートしてないだとかなんかエラーが出たような?</p>
            <p>なのでfor文を使って新しい変数targetに入れます。</p>
            <p>そしてtargetをrequests.getで読み込みそれをさらにrespに入れます。</p>
    </div>
</body>]
</html>
'''

サイト上でCtrl+iを押すとソースが表示されますがそれをここに打ち込む必要があります。

これがめんどくさいのでRequestsやurllibを使うんですけどね。

僕はRequests派です。

import requests
from bs4 import BeautifulSoup

url = 'http://www.htmllifehack.xyz/'
soup = BeautifulSoup(requests.get(url).content,'html.parser')

こうするとソースを張り付ける必要がないのでコードもごちゃごちゃしないで済みます。

3.スープを作る

soup = Beautifulsoup(html,'html.parser')

最初のfrom bs4 import BeautifulSoupをimport bs4だけにするとsoup = bs4.BeautifulSoupと書く必要があります。

まあ最初に書くかあとに書くかの違いなんですけど、結局soupに代入するので手間は変わらないのかな?

ちなみに、bs4のドキュメントにも書かれていますがパーサーはlxmlが爆速らしいです。

pip install lxmlでインストールすれば使えるようになります。

パーサーはパースするという意味で、パースは解析って意味です。

lxmlはxmlのパーサーなんですがxmlがまたわからないですよね。

xmlはマークアップ言語の一種でHTMLに近しいものです。

HTMLの場合は見出しにh2やh3を使いますが、xmlの場合は<見出し>とかとかわかりやすい表示をしたものです。

5分でわかる!XML超入門 第01回 何はともあれXMLって何? | CMSとITアウトソーシングを提供する、XMLソリューションカンパニー

4.スープから要素を取り出す。

要素を見つけるにはfindかfind_allを使います。

だいたいfind_allでOkなんじゃないですかね。

例えばh3のタグを取り出したい場合は下記のようなコードになります。

print(soup.find_all('h3'))
実行結果
[<h3 id="コード解説">コード解説</h3>]

実行結果を見るとわかるように[]で囲まれています。

リストみたいになっているのでtypeで確認するのと他にもpとtitleも取り出してみます。

print(type(soup.find_all('h3')))
print('-----------------------------------------')
print(soup.find_all('p'))
print('-----------------------------------------')
print(soup.find_all('title'))
実行結果
<class 'bs4.element.ResultSet'>
-----------------------------------------
[<p>僕と同じ初心者の方がつまづかないように一応僕なりに解説しておきます。</p>, <p>間違ってるかもしれないんですけど、大目に見てくだされ。</p>, <p>デフォルトのhtml.parserでも結果は変わりません。</p>, <p>次、<span class="markb"><b>for文でimgタグ</b></span>を取得します。</p>, <p>最後が<span class="markb"><b>ローカルフォルダに画像データを保存する方法</b></span>です。</p>, <p>まずリストに入れた画像データを読み込む必要があるのでrequests.getを使うんですけど</p>, <p>images = </p>, <p>なんかよくわからないけどサポートしてないだとかなんかエラーが出たような?</p>, <p>なのでfor文を使って新しい変数targetに入れます。</p>, <p>そしてtargetをrequests.getで読み込みそれをさらにrespに入れます。</p>]
-----------------------------------------
[<title>文系男子がPython3で画像収集するスクリプトを作ってみた。bs4・requests編 - 僕の世界観を変えてみる</title>]

bs4.element.ResultSetとなっているのでたぶんセットの一種なんでしょう。

を取り出してみると要素が,で区切られリストになっていますね。

このままだと見づらいので1行ずつ表示していきます。

for p in soup.find_all('p'):
    print(p)
実行結果
<p>僕と同じ初心者の方がつまづかないように一応僕なりに解説しておきます。</p>
<p>間違ってるかもしれないんですけど、大目に見てくだされ。</p>
<p>デフォルトのhtml.parserでも結果は変わりません。</p>
<p>次、<span class="markb"><b>for文でimgタグ</b></span>を取得します。</p>
<p>最後が<span class="markb"><b>ローカルフォルダに画像データを保存する方法</b></span>です。</p>
<p>まずリストに入れた画像データを読み込む必要があるのでrequests.getを使うんですけど</p>
<p>images = </p>
<p>なんかよくわからないけどサポートしてないだとかなんかエラーが出たような?</p>
<p>なのでfor文を使って新しい変数targetに入れます。</p>
<p>そしてtargetをrequests.getで読み込みそれをさらにrespに入れます。</p>

stringを使うとタグを取り除いたテキストだけの表示もできます。

for p in soup.find_all('p'):
    print(p.string)
実行結果
僕と同じ初心者の方がつまづかないように一応僕なりに解説しておきます。
間違ってるかもしれないんですけど、大目に見てくだされ。
デフォルトのhtml.parserでも結果は変わりません。
None
None
まずリストに入れた画像データを読み込む必要があるのでrequests.getを使うんですけど
images = 
なんかよくわからないけどサポートしてないだとかなんかエラーが出たような?
なのでfor文を使って新しい変数targetに入れます。
そしてtargetをrequests.getで読み込みそれをさらにrespに入れます。

要素が見つからないとnoneが返ってくるらしいです。

pあるのにおかしいよね。

この結果からタグが入れ子になっているとnoneが返ってくるようですな。

なんでなのか教えてほしい。

spanの要素が欲しい場合も同じように指定すればいいだけです。

print(soup.find_all('span',class_='markb'))
for span in soup.find_all('span'):
    print(span.string)
実行結果
[<span class="markb"><b>for文でimgタグ</b></span>, <span class="markb"><b>ローカルフォルダに画像データを保存する方法</b></span>]
for文でimgタグ
ローカルフォルダに画像データを保存する方法

これはspanのclassが同一なので関係ないんですが、classが複数あるようならclass_='class名'で指定できます。

classだけでなくidも指定できますが残念ながらBeautifulSoupはxpathで指定できないようです。

無理やりxpathを指定する方法もあるようですけどね。

idやclassの書き方はいくつかあるようですが僕はこの書き方が簡単なので気に入ってます。

User-Agentを偽装する

スクレイピングしていると要素があるのに取り出せないことがあります。

その原因の一つがHTTP403エラーです。

linuxにあるwgetコマンドを使うとわかりやすいんですが、例えば乃木坂46の公式サイトは403が返ってきます。

これはアクセス拒否されたことを意味していて、スクレイピング対策なのかなんからしいです。

簡単に言えばブラウザから見てね、クローラーはお断りだよってことです。

なのでこの場合はUser-Agentを偽装してFireFoxで見ているようにすればいいわけです。

headers = {'User-Agent':'Mozilla/5.0'}
soup = BeautifulSoup(requests.get(url,headers=headers).content,'html.parser')

実践

試しに乃木坂のサイトをスクレイピングしてメンバーが何人いるのか数えてみます。

robots.txtも規約にも書かれてないから平気だよね。

f:id:htmllifehack:20171007011553j:plain

F12でクロームのデベロッパーツールを起動させます。

①のアイコンをクリック

②で取り出したい部分にカーソルを合わせるとソースが表示されます。

③ここでメンバーの名前はdiv class=“main”でくくられているのがわかります。

④で名前はspanで囲まれていることがわかりました。

ここまでわかればコードが書けます。

1.秋元 真夏
2.生田 絵梨花
3.生駒 里奈
4.伊藤 かりん
5.伊藤 純奈
6.伊藤 万理華
7.井上 小百合
8.衛藤 美彩
9.川後 陽菜
10.川村 真洋
11.北野 日奈子
12.齋藤 飛鳥
13.斎藤 ちはる
14.斉藤 優里
15.相楽 伊織
16.桜井 玲香
17.佐々木 琴子
18.白石 麻衣
19.新内 眞衣
20.鈴木 絢音
21.高山 一実
22.寺田 蘭世
23.中田 花奈
24.中元 日芽香
25.西野 七瀬
26.能條 愛未
27.樋口 日奈
28.星野 みなみ
29.堀 未央奈
30.松村 沙友理
31.山崎 怜奈
32.若月 佑美
33.渡辺 みり愛
34.和田 まあや
35.伊藤 理々杏
36.岩本 蓮加
37.梅澤 美波
38.大園 桃子
39.久保 史緒里
40.阪口 珠美
41.佐藤 楓
42.中村 麗乃
43.向井 葉月
44.山下 美月
45.吉田 綾乃 クリスティー
46.与田 祐希


member = []で空のリストを作っておきます。

find_allでclassがunitのdivを取り出して行きます。

forを使うのは繰り返し取り出す必要があるからです。

さらにforを使ってspanを取り出しますがここはもっと簡略化できます。

for div in soup.find_all('div',class_='unit'):
    member.append(div.find('span').string)

人数がわかるように人名の前に数字が入るようにしました。

i = 0 でカウンターを作ります。

forでmemberという名前のリストから一人ずつ取り出していきます。

取り出すごとにiのカウントを+1していきます。

iは数値なので、str(i)と書くことでmember同様文字列に変換します。

そうすると上のような結果になります。

ちゃんと46人いますね。

伊藤万理華ちゃんが卒業するから新しいメンバーが入るんでしょうかね。

まとめ

BeautifulSoupとRequestsの基本的な文法を残しました。

ググってもパンッってコードが貼ってあってこれでできるとか書かれたブログあるじゃないですか。

それだとわからないんですよね。

すごい丁寧に書かれたブログもあるんですけどどれがそうだったかわからなくなるんですよね。

とりあえず基本的な文法を知りたくなったら自分のブログを見ることにします。

あ、あと参考になったブログも忘れないように残しておきます。

PythonとBeautiful Soupでスクレイピング - Qiita
Python で HTML をパースする (Beautiful Soup) | まくまく Python ノート
Requests.get in Python using "User-Agent" not simulating a browser request - Stack Overflow