web初心者がpythonでスクレイピングをやってみた。
■イントロ
こんにちは。
私のお気に入りのブログ(ラプトブログ)の記事タイトル一覧を作成したいと思い、調べていたところスクレイピングがpythonでできる記事を見つけたので、それを参考にトレースしてみました。
自分の忘備録用です。
●参考にしたページ(というより、そのままトレース)
https://dividable.net/python/python-scraping/#i
●スクレイピングしたリンク先(ラプトブログ)
http://rapt-neo.com/?page_id=30947
●この記事を書いた人
・pythonは触ったことがあり、基本構文はわかる
・webの知識(HTML,CSSなど)はなし
●環境
・anaconda
・spyder 3.3.1
・python 3.7
・windows 10
■手順
--1.各種インポート
from bs4 import BeautifulSoup
import requests
import pandas as pd
--2.カテゴリURLとカテゴリ名を持った辞書型オブジェクトを作成
まず、c_pathでセレクトするタグだけを指定する。
今回は、nav ul li a
そして、htmlからテキスト取得。
これはほぼ決まり文句。
そして、辞書作成。
このように辞書(文字列の集まり)にすることで、forで回してすべてのカテゴリのリスト取得することができるようです。
c_path = "nav ul li a"
res = requests.get(url).text #html取得、テキスト
soup = BeautifulSoup(res, 'html.parser')
category_html_list = soup.select(c_path) #cssセレクタでpath設定部分のみと抽出、ヘッダ部
category_dict = {}
for ch in category_html_list:
category_dict[ch.get("href")] = ch.string #URLとカテゴリ名をセット
--3.カテゴリを一つ一つ取り出して、ページャーの最後まで記事を取得
forで、key: url、value: カテゴリ名で、それぞれをループで回してすべてのカテゴリのリストを取得します。
以下のように、記述することで、すべてのリストを取得できるようです。
for key, value in category_dict.items():
次に、カテゴリページにアクセスし、必要な情報を取得します。
カテゴリページのリンク先は、keyの中のurl情報を使用します。
page_countをつけることで、「次へ」がある場合のリンク先も取得できます(後述)。
soup.selectで必要な情報が書いてあるタグのみを抽出(tags)。
今回は、抽出したいタグがdiv, post_text_innerだったのでそれを指定しました。
その後、for文でその中の情報(title,url,detail)を抽出します。
今回は、それぞれh5, a, pタグでしたのでそれを指定。
category_res = requests.get(key + "&paged=" + str(page_count)).text #カテゴリページのhtml取得
soup = BeautifulSoup(category_res, 'html.parser') # BeautifulSoupの初期化
tags = soup.select("div.post_text_inner") #post_text_innerをセレクト
for tag in tags:
title=tag.select("h5")[0].text # name
o_url=tag.select("a")[0].get("href") # url
detail=tag.select("p")[0].text # detail
抽出したテキストを、dfにします。
pd.seriesで行を作成し、appendで結合していきます。
#df作成
se = pd.Series([value, title, o_url, detail], columns)
df = df.append(se, ignore_index=True)
最後に、「次へ」があるかを確認し、あればそのページへ遷移(次のループ)するようにします。
find_allと指定タグで「次へ」に該当する項目があるか確認します。
ここで、対象としたブログの「次へ」のタグが「liで、class="next next_last"」となっていたので、それを指定しました。
タグがあった場合には、page_countを+1して、continueで次のループへ。
ない場合には、breakをしてwhile文を抜けます。
また、対象としたブログでは、nextとnext next_lastと2種類あったので、2種類指定しました。
#「次へ」があれば、次のページに遷移
a_next_tag= soup.find_all("li", {"class": "next next_last"}) #次へがあるか確認する
if a_next_tag:
page_count += 1
continue
a_next_tag= soup.find_all("li", {"class": "next"}) #次へがあるか確認する_その2
if a_next_tag:
page_count += 1
continue
break
--4.保存
"utf-8-sig"はおまじないのようにつけています。
df.to_csv(filename, encoding = 'utf-8-sig')
■できるようになったこと
・辞書作成
・辞書ループ
・htmlからテキスト抽出
・htmlから意図した部分の抽出
・df(DataFrame)の結合、配列作成
・dfのcsv保存
・ヘッダ部分の抽出
・次へ がなくなるまで、移動して記事のタイトルを取得しリスト化
いろいろできるようになりました^^
■完成したコード
# -*- coding: utf-8 -*-
#インポート
from bs4 import BeautifulSoup
import requests
import pandas as pd
url="https://...." ←対象となるurlを入力
filename = "result.csv"
#url, title, categoryを列に持ったDataFrameを作成
columns = ["category","title" ,"url", "detail"]
df = pd.DataFrame(columns=columns)
#カテゴリURLとカテゴリ名を持った辞書型オブジェクトを作成
#辞書型(dictionary)のデータに、URLとカテゴリを変換
c_path = "nav ul li a"
res = requests.get(url).text #html取得、テキスト
soup = BeautifulSoup(res, 'html.parser')
category_html_list = soup.select(c_path) #cssセレクタでpath設定部分のみと抽出、ヘッダ部
print(category_html_list)
category_dict = {}
for ch in category_html_list:
category_dict[ch.get("href")] = ch.string #URLとカテゴリ名をセット
#forで回してすべてのカテゴリのリスト取得するために辞書使う
##カテゴリを一つ一つ取り出して、ページャーの最後まで記事を取得し。
for key, value in category_dict.items():
"""
key: url
value: カテゴリ名
辞書型のkey, valueをfor文でかけています。
dict.items()を利用すると、forの要素に、keyとvalueを二つ代入して、それぞれを回せます。
"""
print ("------カテゴリ: {} ------".format(value))
page_count = 1
category_res = ""
soup = ""
while True:
print ("------------{} ページ目------------".format(page_count))
category_res = requests.get(key + "&paged=" + str(page_count)).text #カテゴリページのhtml取得
soup = BeautifulSoup(category_res, 'html.parser') # BeautifulSoupの初期化
#内容を取得
tags = soup.select("div.post_text_inner") #post_text_innerをセレクト
for tag in tags:
title=tag.select("h5")[0].text # name
o_url=tag.select("a")[0].get("href") # url
detail=tag.select("p")[0].text # detail
#df作成
se = pd.Series([value, title, o_url, detail], columns)
df = df.append(se, ignore_index=True)
#「次へ」があれば、次のページに遷移
a_next_tag= soup.find_all("li", {"class": "next next_last"}) #次へがあるか確認する
if a_next_tag:
page_count += 1
continue
a_next_tag= soup.find_all("li", {"class": "next"}) #次へがあるか確認する_その2
if a_next_tag:
page_count += 1
continue
break
print ("完了")
# CSVに出力
df.to_csv(filename, encoding = 'utf-8-sig') #encoding指定しないと、エラーが起こります。おまじないだともって入力します。
■実行結果
無事リスト化に成功しました。
「カテゴリ、タイトル、リンク先、詳細」
という風に分けました。
csv化した後、エクセルでフィルタリングすると、指定のカテゴリのみ抽出もできます。
■感想
今後、どこかで活用していきます。
それにしてもラプトブログの記事は、分量がすごい。
2000行以上になりました。