PythonでWebスクレイピングを行う方法を解説します。
Webサイト内の複数ページを、リンクを辿りながらクロール(巡回)して情報を取得して、Excelに出力します。またWebサイトをスクレイピングする際の注意点も説明していきます。
使用ライブラリ
PythonでWebスクレイピングを行うためのライブラリでよく利用されているものは Requests
, Beautiful Soup
, Selenium
, Scrapy
があります。
ここでは、 Requests
, Beautiful Soup
を用いた方法を解説します。
ライブラリの基本的な使い方は以下の記事を参照してください。
・関連記事:【入門】PythonによるWebスクレイピング
インストール:必要
インストール:必要
スクレイピングする際の注意点
Webサイトをスクレイピングする際には、以下の点を確認してからルールや制限を守って行います。注意すべき点としては「著作権法」「動産不法侵入」「偽計業務妨害罪」「利用規約」などが挙げられます。
具体的には以下のような内容に注意してください。
- Webページに記載している内容をデータ化しても良いか
- そのサイトにWebブラウザーではなくプログラムからアクセスしても良いか
- サイトの利用規約でスクレイピングを禁止していないか
- ページのアクセスに対する制限・指示がないか
- サーバに過剰な負荷をかけないか
ページのアクセスに対する制限や指示を確認するには、robots.txt を確認します。主な記載内容は以下になります。
- User-Agent
- Disallow
- Allow
- Crawl-delay
- Sitemap
User-Agentは、どのクローラーの動きを制御するかを指定します。Googlebotと指定した場合、GoogleのWebクロールを制御することになります。
Disallowは、クローラーのアクセスを制御するファイルを指定するものです。Disallowで指定されたファイルやディレクトリはアクセスを禁止しています。
Allowは、Disallowで指定しているディレクトリ配下のサブディレクトリ・特定ページのクロールを許可するためのものです。
Crawl-delayは、アクセス間隔を秒数で指定しています。この指定が無い場合もサーバの負荷を考慮するために、最低1秒以上は間隔を空けるようにしましょう。
2010年には、岡崎市立図書館のシステムに障害を発生させた事で、クロールしていた開発者が逮捕されるという事件も発生していますので、リスクを十分に理解して行いましょう。(参考:岡崎市立中央図書館事件)
Sitemapは、ページのサイトマップ(sitemap.xml)の場所をクローラーに伝えるものです。SitemapSpider でサイトマップを読み込ませることで簡単にWebページのスクレイピングを行うことができます。
robots.txt の確認
robots.txt を確認してみます。通常はトップページURL/robots.txt
にアクセスする事で確認できます。技術評論社 のWebサイトでは https://gihyo.jp/robots.txt で表示する事ができます。
(以下は2022年3月5日時点のもの)
User-agent: * の指示内容を確認して、スクレイピングする対象のURLがDisallowに含まれていないか確認します。今回は「https://gihyo.jp/book/list」 のページをクロールするので禁止されていないことが分かります。
対象のURLが許可されているか、Python標準ライブラリのurllib.robotparser
を使って確認すること可能です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | # ライブラリ設定 import urllib.robotparser import urllib.request # urllibにデフォルトと異なるユーザーエージェントを使う UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36' opener = urllib.request.build_opener() opener.addheaders = [('User-agent', UA')] urllib.request.install_opener(opener) # robots.txtをrobotparserで読み込み許可されているか確認 url = 'https://gihyo.jp/robots.txt' rp = urllib.robotparser.RobotFileParser() rp.set_url(url) rp.read() # 対象のURLを指定して許可されているか確認 print(rp.can_fetch('*', 'https://gihyo/book/list')) #True |
複数ページをクロールして情報を取得する
ここでは例として技術評論社の新刊書籍一覧の全ページの情報を取得する方法を解説します。
赤枠内の情報をスクレイピング → 次ページのリンク取得 → 赤枠情報をスクレイピングを繰り返し、Excelに情報を出力します。
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | # PRG1:ライブラリ設定 import requests from bs4 import BeautifulSoup import time import openpyxl as px from openpyxl.styles import Alignment # PRG2:クロール処理開始 def main(): top_url = 'https://gihyo.jp/book/list' booklist = [['タイトル', '著者', '価格', 'URL']] booklist = get_list(top_url, booklist) # PRG3 :Excel出力 wb = px.Workbook() ws = wb.active ws.column_dimensions['A'].width = 70 ws.column_dimensions['B'].width = 35 ws.column_dimensions['C'].width = 30 ws.column_dimensions['D'].width = 45 for y, row in enumerate(booklist): for x, cell in enumerate(row): ws.cell(row= y+1, column= x+1, value=cell) ws.cell(row= y+1, column= x+1).alignment = Alignment(wrapText=True) wb.save('./新刊書籍一覧.xlsx') print('end') # PRG4:対象URLのhtmlから書籍情報の取得 def get_list(url, booklist): response = requests.get(url) soup = BeautifulSoup(response.text, 'html.parser') for item in soup.find_all("li", class_="clearfix"): list = [] title = item.find("h3").text author = item.find("p", class_="author").text author = author.replace('\u3000', ' ') price = item.find("p", class_="price").text link = 'https://gihyo.jp' + item.find("a").get("href") list.extend([title, author, price, link]) booklist.append(list) # PRG5:次ページがある場合はPRG4の書籍情報取得を再帰処理 next_url = get_next_page(soup) if next_url is None: return booklist else: print(next_url) time.sleep(5) booklist = get_list(next_url, booklist) return booklist # PRG6:次ページのURLを取得する関数 def get_next_page(soup): next_link = soup.find("p", class_="next") if next_link.find('a') is None: return else: next_url = next_link.find("a").get("href") return 'https://gihyo.jp/' + next_url # PRG7:スクリプトとして実行された場合の処理 if __name__ == '__main__': main() |
Excelに出力した結果です。
プログラム解説
プログラムのポイントを解説します。
プログラムは上から順に説明しますが、スクリプトを実行するとPRG1のライブラリ設定を読み込んだ後に、PRG7からmain()
が実行されます。
PRG1:ライブラリ設定
1 2 3 4 5 6 | # PRG1:ライブラリ設定 import requests from bs4 import BeautifulSoup import time import openpyxl as px from openpyxl.styles import Alignment |
from bs4 import BeautifulSoup
HTMLのダウンロードと解析を行うためにrequests
BeautifulSoup
をインポートします。
インストールや基本的な使い方は以下の記事を参照してください。
・関連記事:【入門】PythonによるWebスクレイピング
クロールの間隔を空けるための待ち時間の設定に使用します。
from openpyxl.styles import Alignment
Excelに取得したデータを出力するためにopenpyxl
ライブラリを使用します。
PRG2:クロール処理開始
1 2 3 4 5 | # PRG2:クロール処理開始 def main(): top_url = 'https://gihyo.jp/book/list' booklist = [['タイトル', '著者', '価格', 'URL']] booklist = get_list(top_url, booklist) |
1行ずつ解説します。
関数main()
を定義します。
クロールを開始するURLを指定します。
取得した書籍一覧データを格納するリストをbooklist
として、Excelに出力する際のヘッダーを設定します。
PRG4のget_list
関数を呼び出します。引数はクロール対象URLtop_url
とデータ格納用リストbooklist
を指定します。
PRG3:Excel出力
Excelの操作方法についてはこちらの記事を参考にしてください。
・参考記事:【Python入門】openpyxlによるExcel操作まとめ
1 2 3 4 5 6 7 8 9 10 11 12 13 | # PRG3 :Excel出力 wb = px.Workbook() ws = wb.active ws.column_dimensions['A'].width = 70 ws.column_dimensions['B'].width = 35 ws.column_dimensions['C'].width = 30 ws.column_dimensions['D'].width = 45 for y, row in enumerate(booklist): for x, cell in enumerate(row): ws.cell(row= y+1, column= x+1, value=cell) ws.cell(row= y+1, column= x+1).alignment = Alignment(wrapText=True) wb.save('./新刊書籍一覧.xlsx') print('end') |
ws = wb.active
新しいExcelファイルを作成してブックをwb
、wb
のアクティブなシートをws
と設定します。
ws.column_dimensions[‘B’].width = 35
ws.column_dimensions[‘C’].width = 30
ws.column_dimensions[‘D’].width = 45
出力した結果を見やすくするために、シートのA列~D列の幅を設定します。
booklistから1要素ずつ取り出して処理します。enumerate
関数でインデックス番号をy
として取得し、Excelに出力する際の行番号に使用します。
booklistは以下のような多次元配列になるため[‘タイトル’, ‘著者’, ‘価格’, ‘URL’]のリスト を順次取り出してrow
に格納します。
[‘・・・’, ‘・・・’, ‘・・・’, ‘・・・’],
[‘・・・’, ‘・・・’, ‘・・・’, ‘・・・’],
[‘・・・’, ‘・・・’, ‘・・・’, ‘・・・’],
・・・
]
取り出したリストrow
の要素を一つずつ処理していきます。
Excelの列指定用にenumerate
でリスト要素のインデックス番号を取得してx
に格納しています。
要素の値はcell
に格納します。
・関連記事:繰り返し処理(for文)
ws.cell(row= y+1, column= x+1).alignment = Alignment(wrapText=True)
シートのセルに要素を書き出します。x
, y
は インデックス番号で0から始まるため、1を足してセルのアドレスとして使用します。Alignment(wrapText=True)
で文字列の折返しを設定します。
print(‘end’)
ワークブックを、スクリプトと同じディレクトリに名前を付けて保存します。
最後に処理の終了としてend
を表示しています。
PRG4:対象URLのhtmlから書籍リストの取得
1 2 3 4 5 6 7 8 9 10 11 12 13 | # PRG4:対象URLのhtmlから書籍情報の取得 def get_list(url, booklist): response = requests.get(url) soup = BeautifulSoup(response.text, 'html.parser') for item in soup.find_all("li", class_="clearfix"): list = [] title = item.find("h3").text author = item.find("p", class_="author").text author = author.replace('\u3000', ' ') price = item.find("p", class_="price").text link = 'https://gihyo.jp' + item.find("a").get("href") list.extend([title, author, price, link]) booklist.append(list) |
指定したURLからデータを取得して、リストに格納する関数です。
1行ずつ解説します。
第一引数url
、第二引数booklist
として get_list()
関数を定義します。
Requests
ライブラリのget()
メソッドでHTMLデータをダウンロードして、response
に格納します
responseのHTMLをBeautifulSoupのhtml parserで解析して、結果をsoupに格納します。
これでタグ名などで簡単に要素を取得することが可能になります。
書籍一覧の要素は、タグ「li」クラス「clearfix」となっているため、find_all
で要素を取得します。
取得した複数の要素をfor文で一つずつitem
として取り出して処理を行います。
・関連記事:繰り返し処理(for文)
項目毎の書籍情報を一時的に格納するためのリストを準備します。
ここから書く情報を取得するため、タイトル、著者、価格、リンクの要素を開発者ツールで確認し、タグ名やクラス名を確認します。
開発者ツールの使用方法はこちらを確認ください
・【入門】PythonによるWebスクレイピング-5.1.2 クラスによる要素の検索
書籍タイトルは、h3タグのテキスト情報から取得します。
著者は、タグ「p」クラス「author」のテキスト情報から取得します。
著者のテキスト情報の全角空白が\u3000と表示されてしまうため、半角スペースに置換しています。
・関連記事:文字列処理-文字列の置換(replace)
価格は、タグ「p」クラス「price」のテキスト情報から取得します。
書籍詳細のリンクURLは、タグ「a」要素からget("href")
で取得できますが、ヘッダURLが不足しているため追加しています。
一時格納用のリストlist
にextend()
メソッドで複数要素をまとめて追加します。
・関連記事:リスト(配列)-複数の要素をリストに追加する
書籍一覧リストbooklist
に一時格納リストを追加します。
PRG5:次ページがある場合はPRG4の書籍リスト取得を再帰処理
1 2 3 4 5 6 7 8 | # PRG5:次ページがある場合はPRG4の書籍情報取得を再帰処理 next_url = get_next_page(soup) if next_url is None: return booklist else: time.sleep(5) booklist = get_list(next_url, booklist) return booklist |
次のページのリンクを取得するために、PRG6のget_nex_page()
を呼び出して、結果をnext_url
に格納します。
return booklist
次のページのリンクが無い場合に、next_url
の結果がNone
となるため、その場合はbooklist
を返して処理を終了します。
time.sleep(5)
booklist = get_list(next_url, booklist)
return booklist
next_url
がNone以外の場合=次のページのリンクが見つかった場合は、5秒間停止した後に、PRG4の書籍情報の取得関数get_list
を再帰的に呼び出します。
結果のbooklist
をreturn
で返します。
今回の再帰処理は以下のような流れになります。
PRG6:次ページのURLを取得する関数
1 2 3 4 5 6 7 8 | # PRG6:次ページのURLを取得する関数 def get_next_page(soup): next_link = soup.find("p", class_="next") if next_link.find('a') is None: return else: next_url = next_link.find("a").get("href") return 'https://gihyo.jp/' + next_url |
次ページのURLを取得する関数をget_next_page()と定義します。引数はbeautifulsoupオブジェクトです。
次のページへのリンク要素、タグ「p」クラス「next」を検索して、結果をnext_link
に格納します。
return
next_link
要素からタグ「a」を検索した結果がNone
の場合は、何もせずに処理を終了します。戻り値はNone
になります。
next_url = next_link.find(“a”).get(“href”)
return ‘https://gihyo.jp/’ + next_url
タグ「a」が見つかった場合は、get(“href”)でリンク情報を取得し、ヘッダURL'https://gihyo.jp/'
を追加した値を返して処理を終了します。
PRG7:スクリプトとして実行された場合に実行
1 2 3 | # PRG7:スクリプトとして実行された場合の処理 if __name__ == '__main__': main() |
main()
Pythonには __name__
という特殊な変数があり、プログラムの実行状況により自動的に変数の内容が決まります。Pythonファイルをスクリプトとして直接実行した場合は、__name__
変数は "__main__"
という文字列になります。
ここでは、スクリプトを直接実行した場合に、main()
関数を実行するように設定しています。
PythonでWebサイト内のページをクロール(巡回)して情報を取得する方法を解説しました。
・関連記事:【入門】PythonによるWebスクレイピング