ページャーが存在するページのスクレイピング をしてみよう
学習内容
- Pythonを利用した、Webスクレイピング を学びます。
- 環境構築も不要で、Googleアカウントがあれば簡単に実行できる、Google Colaboratoryを利用します。
受講における必須条件
- Windows / MacなどのPCが利用できる方
- Google Chromeをインストールできる方
- Pythonの基礎的な文法がわかる方
- Python スクレイピング チュートリアルを完了されている方
このチュートリアルで完成するもの
今回は、環境構築一切不要でスクレイピングを学べるように解説します。 このコンテンツでは、ブログデータのカテゴリを全て取得して、「次へ」ボタンが存在しなくなるまで全記事収集するという方法を学んでいきたいと思います。
下記が、取得するCSVのイメージとなります。
普通に情報収集すると、一ページずつ「次へ」を押して、それをCSVにコピペして...とやっていく必要があるのですが、これをPythonで自動化していきたいと思います。
対象者
以下のようなニーズがある方には、おすすめです。
- ポータルサイト(転職サイトなど)の全データを収集したいけど、自分の手でやるにはあまりにも時間がかかりすぎる...
- Python スクレイピングコースは学んだけど、もう少し発展的なことを学習してみたい
- 日々の情報収集業務でスクレイピング を利用してみたい
データ取得のイメージ
解説箇所:5:21
それでは、さっそく進めていきましょう。 今回は、ある仕事が業務であるとします。
- Pythonに関連する記事の名前と、URLを全部取得してほしい
実際に、自分の手でやるとしたら、どんな感じになるでしょう。 以下のサイトにアクセスしてみてください。
アクセスすると、カテゴリがヘッダーが存在することがわかります。
それぞれのカテゴリページにアクセスすると、カテゴリ一覧ページに進みます。
Python学習に関するページが必要なので、上記のカテゴリである、「Python学習」にアクセスします。
https://dividable.net/category/python
ページの下の方まで見ると、ページャーと呼ばれる、次のページに移動できるボタンがありますね。
「次へ」を押すと、以下のようなページに遷移します。
https://dividable.net/category/python/page/2
同様に、さらに上記のページで「次へ」を押すと、 https://dividable.net/category/python/page/3 にアクセスされます。
さて、次へが存在しないページである、5ページ目にアクセスしてみましょう。
https://dividable.net/category/python/page/5/
そうすると、以下のページのように、「次へ」が存在しないページにアクセスされます。
みてもらえるとわかるように、Python学習に関わる、全ての記事を取得する場合は、
- Python学習をクリックして、カテゴリページに飛ぶ
- 記事一覧を全てスプレッドシートにコピーする
- 次へがあったら、押して、次のページに移動する
- 同じように記事一覧をコピーする
- 次へがなくなるまでやる...
といった、大変な作業になります。
このページ自体、10ページもないので、なんとかできてしまいますが、例えば
- 転職サイトの求人の特定のカテゴリを取得したい
という場合は、非常に大変ですよね。 ですので、このような仕事を、Pythonで自動化してしまいましょう。
作業をプログラムに落とす
さて、まずは作業を、全て自動化するためのプログラムに落としていきましょう。
さきほどは、Python学習に関するカテゴリを全て取得しましたが、全カテゴリを収集することにしましょう。
さて、そういう場合は、
- 全カテゴリのURLと、カテゴリ名を取得する
- カテゴリURLにアクセスする
- カテゴリURLの記事のタイトルと、URLと、カテゴリ名を列に追加する
- 「次へ」があれば、次のページに遷移する
- 次のページがなくなるまで繰り返す
- 一つにカテゴリが終わったら、次のカテゴリへ移動する
- 全てのカテゴリが終わったら、終了する
といった形にコードを組んでいきます。
①全カテゴリのURLと、カテゴリ名を取得する
それでは、さっそく実装していきましょう。
カテゴリのURLとカテゴリ名を取得しましょう。
画像でいうと、ここの部分です。
- Requests
- Pandas,
- BeautifulSoup
を利用して進めます。
まずは、ライブラリをインポートします。
TODO
- requestsをインポートしてください
- pandasをインポートしてください
- beautifulsoupをインポートしてください
回答
#TODO1 requestsをインポートしてください
import requests
#TODO2 pandasをインポートしてください
import pandas as pd
#TODO3 beautifulsoupをインポートしてください
from bs4 import BeautifulSoup
ヘッダーの中身を取得する
まずは、ヘッダーのカテゴリ部分をとりたいので、 https://dividable.net のヘッダー部分を取得しましょう。
TODO
- requestsのgetメソットを使って、https://dividable.netのHTMLを出力してください
回答
import requests
import pandas as pd
from bs4 import BeautifulSoup
#TODO1 requestsのgetメソッドを使って....
print (requests.get("https://dividable.net").text)
出力結果
ヘッダーの部分は、以下のように出力されることがわかります。
... 中略
<nav id="category" class="col-md-12 col-12">
<ul><li><a href="https://dividable.net/category/career/">IT転職</a></li>
<li><a href="https://dividable.net/category/sidework/">IT副業</a></li>
<li><a href="https://dividable.net/category/freelance/">ITフリーランス</a></li>
<li><a href="https://dividable.net/category/nisotsu-tenshoku/">一般転職</a></li>
<li><a href="https://dividable.net/category/programming-school/">プログラミングスクール</a></li>
<li class="current"><a href="https://dividable.net/category/python/" aria-current="page">Python学習</a></li>
<li><a href="https://dividable.net/try-python-scraping-lp/">無料チュートリアル</a></li>
<li><a href="https://dividable.net/lp1/">DAINOTE</a></li>
</ul>
</nav><!-- col-md-7-->
</div><!-- row -->
</div><!-- container -->
</header>
カテゴリ名、URLを取得する
カテゴリの中のURLと、名前は、 nav#category
> ul > li
> a
タグの中に存在するので、それらのaタグを取得してみます。
aタグをCSS Selectorを利用して取得する場合は、nav#category
ul
li
a
となります。(id=category
のnav
の中の、ul
の中のli
の中のa
タグ)
ヒント:BeautifulSoupのselectという関数の引数には、CSSセレクターを入れると、合致するタグをリスト形式で取得してくれます。
参考)Beautiful Soup のfind_all( ) と select( ) の使い方の違い
TODO
- カテゴリのパスをCSSで指定してください
- requestsでホームページを取得してください
- BeautifulSoupで2で取得したデータをパースしてください
- パースしたデータから、カテゴリのパスの値を取得してください
#TODO1 カテゴリのパスを指定してください
category_li_path = "nav#category ul li a"
#TODO2 requestsでページを取得してください
res = requests.get("https://dividable.net").text
#T0D03 データをBeautifulSoupでパースしてください
soup = BeautifulSoup(res, 'html.parser')
#T0D04 パースしたデータから、カテゴリのパスの値を取得してください
soup.select(category_li_path)
出力結果
実行すると、以下のような出力結果になります。
[<a href="https://dividable.net/category/career/">IT転職</a>,
<a href="https://dividable.net/category/sidework/">IT副業</a>,
<a href="https://dividable.net/category/freelance/">ITフリーランス</a>,
<a href="https://dividable.net/category/nisotsu-tenshoku/">一般転職</a>,
<a href="https://dividable.net/category/programming-school/">プログラミングスクール</a>,
<a href="https://dividable.net/category/python/">Python学習</a>,
<a href="https://dividable.net/try-python-scraping-lp/">無料チュートリアル</a>,
<a href="https://dividable.net/lp1/">DAINOTE</a>]
ループを回しやすいように、辞書型に変換する
これらのURLとカテゴリを、今後ループを回して、それぞれのカテゴリのページを取りに行く都合上、データを扱いやすいように、辞書型(dictionary)のデータに、URLとカテゴリを変換します。
TODO
- 変数category_html_listカテゴリのリストを複数取得した値を代入してください。
- 変数category_dictにからの辞書型オブジェクトを空で宣言してください
- for文でcategory_html_listを回してください
- category_dicに、カテゴリのリンクとテキストを対になるように代入してください
- category_dictを出力してください
#T0D01 変数category_html_listカテゴリのリストを複数取得した値を代入してください。
category_html_list = soup.select(category_li_path)
#T0D02 変数category_dictにからの辞書型オブジェクトを空で宣言してください
category_dict = {}
#TODO3 for文でcategory_html_listを回してください
for category_html in category_html_list:
#TODO4 category_dicに、カテゴリのリンクとテキストを対になるように代入してください
category_dict[category_html.get("href")] = category_html.string
#TODO5 category_dictを出力してください
print (category_dict)
出力結果
出力すると、以下のようになります。
{'https://dividable.net/category/career/': 'IT転職', 'https://dividable.net/category/sidework/': 'IT副業', 'https://dividable.net/category/freelance/': 'ITフリーランス', 'https://dividable.net/category/nisotsu-tenshoku/': '一般転職', 'https://dividable.net/category/programming-school/': 'プログラミングスクール', 'https://dividable.net/category/python/': 'Python学習', 'https://dividable.net/try-python-scraping-lp/': '無料チュートリアル', 'https://dividable.net/lp1/': 'DAINOTE'}
辞書型について簡単に解説すると、key, valueのペアでデータを取得することができます。例えば、IT転職のデータが欲しければ、
category_dict["https://dividable.net/category/career/"]
と出力すると、
IT転職
と返ってきます。
③カテゴリURLにアクセスする
それではカテゴリURLにアクセスしましょう。 最終的には、全てのカテゴリに自動アクセスするコードを書きますが、今後のカテゴリページからデータをとるコードをかくために、Python学習のカテゴリページのみを取得して、出力してみます。
category_res = requests.get("https://dividable.net/category/python/").text
print (category_res)
③カテゴリURLの記事のタイトルと、URLと、カテゴリ名を列に追加する
次に、カテゴリURLの中にある複数の記事の
- タイトル
- URL
- カテゴリ
を自動取得します。 ただし、一旦こちらは後から手をつけていましょう。
③「次へ」があれば、次のページに遷移する
次に、③で記事のデータを取得したとして、「次へ」があれば、次のページに遷移するアクションを実装してみましょう。例えば、現在
にいるとします。 記事に「次へ」があれば、クリックすると
に飛ぶんでしたね。
※ちなみに、https://dividable.net/category/python/page/1 は存在しませんが、https://dividable.net/category/python/page/1にアクセスすると、https://dividable.net/category/python/ にリダイレクト(自動遷移)します。
ですので、「次へ」が存在すれば、page番号を+1してあげてデータを取得すれば良いわけです。これを実装していきます。 まず、「次へ」があるかを確認しますね。 次へに該当するソースコードをみてみましょう。
HTMLをみていきます。
<div class="pagination">
<span class="page_num">Page 1 of 5</span>
<span class="current pager">1</span>
<a href="https://dividable.net/category/python/page/2/" class="pager">2</a>
<a href="https://dividable.net/category/python/page/3/" class="pager">3</a>
<a href="https://dividable.net/category/python/page/2/" class="next">次へ ›</a>
<a href="https://dividable.net/category/python/page/5/" class="last">最後へ »</a>
</div>
ここの
<a href="https://dividable.net/category/python/page/2/" class="next">次へ ›</a>
が、次へに当たる部分のaタグとなっています。 ですので、以下のように実装すると、次へボタンが存在するか確認できます。
soup = BeautifulSoup(category_res, 'html.parser')
a_next_tag= soup.find_all("a", {"class": "next"}) # 次へがあるか確認するコード
それではさっそく、カテゴリページで次へボタンが存在するか確認してみましょう。
TODO
- https://dividable.net/category/python/ のデータをrequestで取得してください
- BeautifulSoupでパースしてください
- 次へボタンを、find_allメソッドで取得してください
- 次へボタンを出力してください
#TODO1 https://dividable.net/category/python/ のデータをrequestで取得してください
category_res = requests.get("https://dividable.net/category/python/").text
#TODO2 BeautifulSoupでパースしてください
soup = BeautifulSoup(category_res, 'html.parser')
#TODO3 次へボタンを、find_allメソッドで取得してください
a_next_tag= soup.find_all("a", {"class": "next"}) # 次へがあるか確認するコード
#TODO4 次へボタンを出力してください
print (a_next_tag)
出力結果
[<a class="next" href="https://dividable.net/category/python/page/2/">次へ ›</a>]
ありましたね。
同様に、Page=3でも、同じような結果になると思います。
category_res = requests.get("https://dividable.net/category/python/page/3").text
soup = BeautifulSoup(category_res, 'html.parser')
a_next_tag= soup.find_all("a", {"class": "next"}) # 次へがあるか確認するコード
print (a_next_tag)
出力結果
[<a class="next" href="https://dividable.net/category/python/page/4/">次へ ›</a>]
一方で、5ページ目になると、記事数がそれ以上ないので、a_next_tagの中身は空になります。
category_res = requests.get("https://dividable.net/category/python/page/5").text
soup = BeautifulSoup(category_res, 'html.parser')
a_next_tag= soup.find_all("a", {"class": "next"}) # 次へがあるか確認するコード
print (a_next_tag)
出力結果
[]
以上を踏まえて、次へボタンが存在する場合は、次のページへ、存在しない場合は処理を止めるようにプログラミングしてあげれば良いわけですね。
また、ヒントですが、次のページは、URLで/category/python/page/1 のように、pageの後の数字を+1してあげれば、次のページにアクセスできるはずです。
次へが存在しなくなるまで、ループを続ける処理を書く
それでは、あるカテゴリのページャーを全て開いて、次へがあるタイミングで処理を止めるコードを書いていきましょう。
TODO
- 次へが存在する場合、次のページを取得する ②存在しない場合、処理を中断する
#TODO1 page_countという変数を指定して、1ページ目から取得できるように値を入れてください
page_count = 1
#TODO2 while文を利用して、ループ処理を始めてください
while True:
#TODO3 https://dividable.net/category/python/1 のように、ページ番号を指定して、requestsで取得してください
category_res = requests.get("https://dividable.net/category/python/" + "page/" + str(page_count)).text
#TODO4 BeautifulSoupでパースしてください
soup = BeautifulSoup(category_res, 'html.parser') # BeautifulSoupの初期化
#TODO5 ページカウントを出力してください
print ("{} ページ目".format(page_count))
#TODO6 a_next_tagという変数を設定し、次へボタンをfind_allメソッドで検索した値を代入してください
a_next_tag= soup.find_all("a", {"class": "next"})
#TODO7 次へタグが存在したら、次のページへ、なければ処理を止めるようにコードを書いてください
if a_next_tag:
page_count += 1
continue
break
print ("完了")
コードを解説すると、まずpage_countという変数を利用して、取得するページを定義しています。そして、次へボタンが存在する場合は、数字に1をプラスしてあげ、continueで再度while文に入っています。 一方で、存在しない場合は、break文で処理を中断させています。 このようにすると、Python学習の全てのページにアクセスすることができます。
③それぞれのカテゴリページから、記事の内容を取り出す
それでは、次にデータ https://dividable.net/category/python/page/2 のような、カテゴリページにアクセスし、その中でそれぞれの記事のURLを確認すると、以下のようになります。
<div class="post">
<div class="post-content__time float-left">
<span class="post-content-time__updated">2019/07/28</span>
</div>
<div class="post-header">
<a href="https://dividable.net/python/python-freelance-beginner/">
<img width="100%" height="auto" class="post-image" src="https://dividable.net/wp/wp-content/uploads/2019/03/data.jpg">
</a> </div><!-- post-header -->
<div class="post-content">
<h3 class="post-content__title" id="single__title">
<a href="https://dividable.net/python/python-freelance-beginner/">【フリーランス】Pythonで独立! 年収・おすすめ求人サイト・心得まとめ</a>
</h3>
<div class="post-content__text">≪この記事で紹介するフリーランス求人サイト一覧≫ 【第1位】ミッドワークス:フリーランスでも正社員並みの福利厚生待遇を受けられるITフリーランス向け求人サイト。 【第2位】レバテックフリーランス : 高報酬な案件が豊富。エージェントがITに詳しい求人サイト。 【第3位】ビッグデー... </div>
<!-- post-content__text -->
</div><!-- post-content -->
...
ここで取得したいのは、タイトルとURLなので、h3タグと、その中のaタグをとればよいわけですね。
h2、aタグが含まれるHTMLのコードとしては、以下の部分となります。
<h3 class="post-content__title" id="single__title">
<a href="https://dividable.net/python/python-freelance-beginner/">【フリーランス】Pythonで独立! 年収・おすすめ求人サイト・心得まとめ</a>
</h3>
ですので、以下のように実装することができます。
page_count = 1
category_res = ""
soup = ""
while True:
print ("------------{} ページ目------------".format(page_count))
category_res = requests.get("https://dividable.net/category/python/" + "page/" + str(page_count)).text
soup = BeautifulSoup(category_res, 'html.parser') # BeautifulSoupの初期化
post_tags = soup.select("div.post")
for post_tag in post_tags:
print (post_tag.select("h3")[0].text) # タイトル
print (post_tag.select("a")[0].get("href")) # url
print ("Python学習")
a_next_tag= soup.find_all("a", {"class": "next"})
if a_next_tag:
page_count += 1
continue
break
print ("完了")
全カテゴリからデータを収集しよう
ここまでくれば、あとは全カテゴリからデータを収集するだけですね。 上記のコードを理解していれば、自分で実装できると思うので、まずは自分で試してみましょう。
TODO
- requests, pandas, beautifulsoupをインポートしてください
- url, title, categoryを列に持ったDataFrameを作成してください
- カテゴリURLとカテゴリ名を持った辞書型オブジェクトを作成してください
- カテゴリを一つ一つ取り出して、ページャーの最後まで記事を取得してください。
- result.csvというファイル名で、CSVに記事を保存してください。
答え
#TODO requests, pandas, beautifulsoupをインポートしてください
import requests
import pandas as pd
from bs4 import BeautifulSoup
#TODO url, title, categoryを列に持ったDataFrameを作成してください
columns = ["url", "title", "category"]
df = pd.DataFrame(columns=columns)
#TODO カテゴリURLとカテゴリ名を持った辞書型オブジェクトを作成してください
category_li_path = "nav#category ul li a"
soup = BeautifulSoup(res, 'html.parser')
category_html_list = soup.select(category_li_path)
category_dict = {}
for category_html in category_html_list:
category_dict[category_html.get("href")] = category_html.string #URL, カテゴリ名をセットにする
## TODO: カテゴリを一つ一つ取り出して、ページャーの最後まで記事を取得し、CSVに記事を保存してください。
for key, value in category_dict.items():
"""
k: url
v: カテゴリ名
"""
print ("------カテゴリ: {} ------".format(value))
page_count = 1
category_res = ""
soup = ""
while True:
print ("------{} ページ目 ------".format(page_count))
category_res = requests.get(key + "page/" + str(page_count)).text
soup = BeautifulSoup(category_res, 'html.parser')
post_tags = soup.select("div.post")
for post_tag in post_tags:
#ページのタイトル, URL,カテゴリを取得する
title = post_tag.select("h3")[0].text
url = post_tag.select("a")[0].get("href")
se = pd.Series([title,url,value], columns)
df = df.append(se, ignore_index=True)
a_next_tag= soup.find_all("a", {"class": "next"})
if a_next_tag:
page_count += 1
continue
break
print ("完了")
#TODO result.csvというファイルとして出力してください。
df.to_csv("result.csv")
おそらく、つなげ合わせるだけなので理解できるかと思いますが、補足を入れますね。
for key, value in category_dict.items():
ここのコードですが、辞書型のkey, valueをfor文でかけています。 dict.items()を利用すると、forの要素に、keyとvalueを二つ代入して、それぞれを回せます。
- key: url
- value: category
が入るようになります。
- Pythonの辞書(dict)のforループ処理(keys, values, items)
なお、CSVファイルとしてダウンロードする場合は、以下のコードを実行すると可能です。
from google.colab import files
files.download("result.csv")
完成
これで完成です!お疲れ様でした!
最終的なコードは以下のようになります。
import requests
import pandas as pd
from bs4 import BeautifulSoup
columns = ["url", "title", "category"]
df = pd.DataFrame(columns=columns)
category_li_path = "nav#category ul li a"
soup = BeautifulSoup(res, 'html.parser')
category_html_list = soup.select(category_li_path)
category_dict = {}
for category_html in category_html_list:
category_dict[category_html.get("href")] = category_html.string #URL, カテゴリ名をセットにする
for key, value in category_dict.items():
page_count = 1
category_res = ""
while True:
print ("------{} ページ目 ------".format(page_count))
category_res = requests.get(key + "page/" + str(page_count)).text
soup = BeautifulSoup(category_res, 'html.parser')
post_tags = soup.select("div.post")
for post_tag in post_tags:
title = post_tag.select("h3")[0].text
url = post_tag.select("a")[0].get("href")
se = pd.Series([title,url,value], columns)
df = df.append(se, ignore_index=True)
a_next_tag= soup.find_all("a", {"class": "next"})
if a_next_tag:
page_count += 1
continue
break
df.to_csv("result.csv")
from google.colab import files
files.download("result.csv")
このチュートリアルはこれで終了です。