このガイドでは、PythonでAutoScraperを使用してWebスクレイピングを行う方法を説明します。
- AutoScraperとは?
- プロジェクトのセットアップ
- ターゲットWebサイトの選択
- AutoScraperでシンプルなデータをスクレイピング
- 複雑なデザインのWebサイトからデータを抽出する
- AutoScraperでよくある課題
AutoScraperは、例として与えたクエリに基づいてWebサイトからデータを自動的に特定・抽出することで、手動でのHTML調査を不要にし、Webスクレイピングを簡素化するPythonライブラリです。最小限のセットアップで動的なWebサイトも効率的に扱えます。動的Webサイトのスクレイピングについては、詳細をこちらでご確認ください。
Python 3以降がインストールされている前提で、プロジェクトディレクトリを作成して移動します。
mkdir auto-scrape && cd auto-scrape仮想環境を作成します。
python -m venv env次に、有効化します。LinuxとmacOSの場合:
source env/bin/activateWindowsの場合:
venv\Scripts\activateautoscraper と pandas ライブラリをインストールします。
pip install autoscraper pandas公開Webサイトをスクレイピングする際は、そのサイトがスクレイピングを許可していることを確認するために、サイトの利用規約(ToS)または robots.txt ファイルを必ず確認してください。
このガイドでは、スクレイピングツールのテスト向けに設計された初心者に優しいサンドボックスである、Scrape This SiteのCountries of the World: A Simple Exampleページからデータをスクレイピングする想定で進めます。
このページは構造がシンプルなため、データ抽出の基本を学ぶのに最適な出発点です。基本概念に慣れたら、より複雑な例として、Hockey Teams: Forms, Searching, and Paginationページに進みます。
次のスクリプトを使用して、国名のリストとあわせて首都、人口、面積をスクレイピングします。
# 1. Import dependencies
from autoscraper import AutoScraper
import pandas as pd
# 2. Define the URL of the site to be scraped
url = "https://www.scrapethissite.com/pages/simple/"
# 3. Instantiate the AutoScraper
scraper = AutoScraper()
# 4. Define the wanted list by using an example from the web page
# This list should contain some text or values that you want to scrape
wanted_list = ["Andorra", "Andorra la Vella", "84000", "468.0"]
# 5. Build the scraper based on the wanted list and URL
scraper.build(url, wanted_list)
# 6. Get the results for all the elements matched
results = scraper.get_result_similar(url, grouped=True)
# 7. Display the keys and sample data to understand the structure
print("Keys found by the scraper:", results.keys())
# 8. Assign columns based on scraper keys and expected order of data
columns = ["Country Name", "Capital", "Area (sq km)", "Population"]
# 9. Create a DataFrame with the extracted data
data = {columns[i]: results[list(results.keys())[i]] for i in range(len(columns))}
df = pd.DataFrame(data)
# 10. Save the DataFrame to a CSV file
csv_filename = 'countries_data.csv'
df.to_csv(csv_filename, index=False)
print(f"Data has been successfully saved to {csv_filename}")上記のスクリプトは、必要なライブラリであるAutoScraperとpandasのインポートから始まります。次に、ターゲットWebサイトのURLを定義し、AutoScraperのインスタンスを作成します。
ここがAutoScraperの特長です。XPathやCSSセレクターといった明示的な指示が必要な従来のスクレイパーとは異なり、AutoScraperでは例となるデータを提供できます。ページ上のどこに要素があるかを指定する代わりに、抽出したいサンプル値をいくつか列挙するだけです。この wanted_list と呼ばれるリストには、国名、首都、人口、面積などの代表的なデータポイントを含めます。
wanted_list を設定したら、指定したURLとサンプルデータを使ってスクレイパーを構築します。AutoScraperはWebページを解析してパターンを特定し、スクレイピングルールを生成します。これらのルールにより、同一ページまたは他のWebページから同様のデータを認識して抽出できるようになります。
スクリプトの後半では、get_result_similar メソッドを呼び出して、AutoScraperが学習したパターンに一致するすべてのデータを取得します。スクレイパーがデータ構造をどのように解釈しているかを理解しやすくするために、抽出されたデータに関連付けられたルールIDを出力します。出力は次のようになります。
Keys found by the scraper: dict_keys(['rule_4y6n', 'rule_gghn', 'rule_a6r9', 'rule_os29'])
コメント8と9では列名を定義し、抽出データをpandasのDataFrameに整形します。コメント10で、このデータをCSVファイルとして保存します。
スクリプトを実行すると(python script.py)、プロジェクトディレクトリに countries_data.csv ファイルが作成され、次のような内容になります。
Country Name,Capital,Area (sq km),Population
Andorra,Andorra la Vella,84000,468.0
United Arab Emirates,Abu Dhabi,4975593,82880.0
...246 collapsed rows
Zambia,Lusaka,13460305,752614.0
Zimbabwe,Harare,11651858,390580.0前の手法は、類似した値が多数あるテーブルを含むHockey Teamsページのような複雑なWebサイトではうまくいかない場合があります。同じ方法でチーム名、年、勝利数、その他のフィールドを抽出してみると、この課題を観察できます。
幸い、AutoScraperはbuildステップ中に不要なルールを刈り込むことでモデルを調整(refine)できます。以下はその方法です。
from autoscraper import AutoScraper
import pandas as pd
# Define the URL of the site to be scraped
url = "https://www.scrapethissite.com/pages/forms/"
def setup_model():
# Instantiate the AutoScraper
scraper = AutoScraper()
# Define the wanted list by using an example from the web page
# This list should contain some text or values that you want to scrape
wanted_list = ["Boston Bruins", "1990", "44", "24", "0.55", "299", "264", "35"]
# Build the scraper based on the wanted list and URL
scraper.build(url, wanted_list)
# Get the results for all the elements matched
results = scraper.get_result_similar(url, grouped=True)
# Display the data to understand the structure
print(results)
# Save the model
scraper.save("teams_model.json")
def prune_rules():
# Create an instance of Autoscraper
scraper = AutoScraper()
# Load the model saved earlier
scraper.load("teams_model.json")
# Update the model to only keep necessary rules
scraper.keep_rules(['rule_hjk5', 'rule_9sty', 'rule_2hml', 'rule_3qvv', 'rule_e8x1', 'rule_mhl4', 'rule_h090', 'rule_xg34'])
# Save the updated model again
scraper.save("teams_model.json")
def load_and_run_model():
# Create an instance of Autoscraper
scraper = AutoScraper()
# Load the model saved earlier
scraper.load("teams_model.json")
# Get the results for all the elements matched
results = scraper.get_result_similar(url, grouped=True)
# Assign columns based on scraper keys and expected order of data
columns = ["Team Name", "Year", "Wins", "Losses", "Win %", "Goals For (GF)", "Goals Against (GA)", "+/-"]
# Create a DataFrame with the extracted data
data = {columns[i]: results[list(results.keys())[i]] for i in range(len(columns))}
df = pd.DataFrame(data)
# Save the DataFrame to a CSV file
csv_filename = 'teams_data.csv'
df.to_csv(csv_filename, index=False)
print(f"Data has been successfully saved to {csv_filename}")
# setup_model()
# prune_rules()
# load_and_run_model()このスクリプトでは、setup_model、prune_rules、load_and_run_model の3つのメソッドを定義します。
setup_model メソッドは、スクレイパーインスタンスを作成し、wanted_list を定義してスクレイパーをbuildし、ターゲットURLからデータをスクレイピングし、収集されたルールIDを出力し、モデルを teams_model.json としてプロジェクトディレクトリに保存します。
スクリプトを実行するには、スクリプト内の # setup_model() 行をアンコメントし、ファイル(例: script.py)を保存して、python script.py で実行します。
出力は次のようになります。
{'rule_hjk5': ['Boston Bruins', 'Buffalo Sabres', 'Calgary Flames', 'Chicago Blackhawks', 'Detroit Red Wings', 'Edmonton Oilers', 'Hartford Whalers', 'Los Angeles Kings', 'Minnesota North Stars', 'Montreal Canadiens', 'New Jersey Devils', 'New York Islanders', 'New York Rangers', 'Philadelphia Flyers', 'Pittsburgh Penguins', 'Quebec Nordiques', 'St. Louis Blues', 'Toronto Maple Leafs', 'Vancouver Canucks', 'Washington Capitals', 'Winnipeg Jets', 'Boston Bruins', 'Buffalo Sabres', 'Calgary Flames', 'Chicago Blackhawks'], 'rule_uuj6': ['Boston Bruins', 'Buffalo Sabres', 'Calgary Flames', 'Chicago Blackhawks', 'Detroit Red Wings', 'Edmonton Oilers', 'Hartford Whalers', 'Los Angeles Kings', 'Minnesota North Stars', 'Montreal Canadiens', 'New Jersey Devils', 'New York Islanders', 'New York Rangers', 'Philadelphia Flyers', 'Pittsburgh Penguins', 'Quebec Nordiques', 'St. Louis Blues', 'Toronto Maple Leafs', 'Vancouver Canucks', 'Washington Capitals', 'Winnipeg Jets', 'Boston Bruins', 'Buffalo Sabres', 'Calgary Flames', 'Chicago Blackhawks'], 'rule_9sty': ['1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1991', '1991', '1991', '1991'], 'rule_9nie': ['1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1991', '1991', '1991', '1991'], 'rule_41rr': ['1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1991', '1991', '1991', '1991'], 'rule_ufil': ['1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1991', '1991', '1991', '1991'], 'rule_ere2': ['1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1991', '1991', '1991', '1991'], 'rule_w0vo': ['1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1991', '1991', '1991', '1991'], 'rule_rba5': ['1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1991', '1991', '1991', '1991'], 'rule_rmae': ['1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1991', '1991', '1991', '1991'], 'rule_ccvi': ['1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1991', '1991', '1991', '1991'], 'rule_3c34': ['1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1991', '1991', '1991', '1991'], 'rule_4j80': ['1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1991', '1991', '1991', '1991'], 'rule_oc36': ['1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1991', '1991', '1991', '1991'], 'rule_93k1': ['1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1991', '1991', '1991', '1991'], 'rule_d31n': ['1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1991', '1991', '1991', '1991'], 'rule_ghh5': ['1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1991', '1991', '1991', '1991'], 'rule_5rne': ['1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1991', '1991', '1991', '1991'], 'rule_4p78': ['1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1991', '1991', '1991', '1991'], 'rule_qr7s': ['1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1991', '1991', '1991', '1991'], 'rule_60nk': ['1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1991', '1991', '1991', '1991'], 'rule_wcj7': ['1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1991', '1991', '1991', '1991'], 'rule_0x7y': ['1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1990', '1991', '1991', '1991', '1991'], 'rule_2hml': ['44', '31', '46', '49', '34', '37', '31', '46', '27', '39', '32', '25', '36', '33', '41', '16', '47', '23', '28', '37', '26', '36', '31', '31', '36'], 'rule_swtb': ['24'], 'rule_e8x1': ['0.55', '14', '0.575', '0.613', '-25', '0', '-38', '0.575', '-10', '24', '8', '-67', '32', '-15', '0.512', '-118', '0.588', '-77', '-72', '0', '-28', '-5', '-10', '-9', '21'], 'rule_3qvv': ['24', '30', '26', '23', '38', '37', '38', '24', '39', '30', '33', '45', '31', '37', '33', '50', '22', '46', '43', '36', '43', '32', '37', '37', '29'], 'rule_n07w': ['24', '30', '26', '23', '38', '37', '38', '24', '39', '30', '33', '45', '31', '37', '33', '50', '22', '46', '43', '36', '43', '32', '37', '37', '29'], 'rule_qmem': ['0.55', '0.388', '0.575', '0.613', '0.425', '0.463', '0.388', '0.575', '0.338', '0.487', '0.4', '0.312', '0.45', '0.412', '0.512', '0.2', '0.588', '0.287', '0.35', '0.463', '0.325', '0.45', '0.388', '0.388', '0.45'], 'rule_b9gx': ['264', '278', '263', '211', '298', '272', '276', '254', '266', '249', '264', '290', '265', '267', '305', '354', '250', '318', '315', '258', '288', '275', '299', '305', '236'], 'rule_mhl4': ['299', '292', '344', '284', '273', '272', '238', '340', '256', '273', '272', '223', '297', '252', '342', '236', '310', '241', '243', '258', '260', '270', '289', '296', '257'], 'rule_24nt': ['264', '278', '263', '211', '298', '272', '276', '254', '266', '249', '264', '290', '265', '267', '305', '354', '250', '318', '315', '258', '288', '275', '299', '305', '236'], 'rule_h090': ['264', '278', '263', '211', '298', '272', '276', '254', '266', '249', '264', '290', '265', '267', '305', '354', '250', '318', '315', '258', '288', '275', '299', '305', '236'], 'rule_xg34': ['35', '14', '81', '73', '-25', '0', '-38', '86', '-10', '24', '8', '-67', '32', '-15', '37', '-118', '60', '-77', '-72', '0', '-28', '-5', '-10', '-9', '21']}
この出力は、ターゲットWebサイトに対して get_result_similar を呼び出した際にAutoScraperが収集したデータを示しています。AutoScraperは関係性を推測し、データをルールにグルーピングしようとするため、重複が含まれます。正しくグルーピングできれば、類似サイトからデータを容易に抽出できます。
しかしこのサイトでは数値が多いため、AutoScraperは多数の誤った相関を作ってしまい、重複を含む大きなデータセットになってしまいます。
次に進むには、データを分析し、正しいデータ(つまり、正しい順序で1列分のデータに一致するもの)を含むルールを選択します。このケースでは、以下のルールが正しいデータを含んでいました(各ルールに25個のデータポイントが含まれていることを確認して判断します。これはターゲットページの行数に対応します)。
['rule_hjk5', 'rule_9sty', 'rule_2hml', 'rule_3qvv', 'rule_e8x1', 'rule_mhl4', 'rule_h090', 'rule_xg34']
prune_rules メソッドを更新してください。次に、スクリプト内の setup_model() 行をコメントアウトし、prune_rules() 行をアンコメントします。実行すると、teams_model.json からモデルを読み込み、不要なルールを削除して、更新したモデルを保存します。保存されたルールを確認するには、teams_model.json の内容を確認できます。
次に、load_and_run_model メソッドを実行するには、prune_rules の行をコメントアウトし、load_and_run_model の行をアンコメントしてスクリプトを再実行します。これにより、正しいデータが抽出されてプロジェクトディレクトリの teams_data.csv に保存され、次の出力が表示されます。
Data has been successfully saved to teams_data.csv
成功後の teams_data.csv ファイルは次のようになります。
Team Name,Year,Wins,Losses,Win %,Goals For (GF),Goals Against (GA),+/-
Boston Bruins,1990,44,0.55,24,299,264,35
Buffalo Sabres,1990,31,14,30,292,278,14
...21 more rows
Calgary Flames,1991,31,-9,37,296,305,-9
Chicago Blackhawks,1991,36,21,29,257,236,21この記事で開発したコードはすべて、このGitHub repoで利用できます。
AutoScraperは、小規模なデータセットでデータポイントが明確に区別できるシンプルなユースケースに適しています。一方で、テーブルのスクレイピングのようなより複雑なシナリオでは扱いづらい場合があります。また、JavaScriptレンダリングをサポートしていないため、Splash、Selenium、またはPuppeteerなどのツールと統合する必要があります。
IPブロックに遭遇したり、カスタムヘッダーが必要になったりする場合、AutoScraperではrequestsモジュール向けに追加のリクエストパラメータを指定できます。例は次のとおりです。
# build the scraper on an initial URL
scraper.build(
url,
wanted_list=wanted_list,
request_args=dict(proxies=proxies) # this is where you can pass in a list of proxies or customer headers
)たとえば、AutoScraperでスクレイピングする際にカスタムuser agentとプロキシを設定する方法は次のとおりです。
request_args = {
"headers: {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 \
(KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36" # You can customize this value with your desired user agent. This value is the default used by Autoscraper.
},
"proxies": {
"http": "http://user:[email protected]:3128/" # Example proxy to show you how to use the proxy port, host, username, and password values
}
}
# build the scraper on an initial URL
scraper.build(
url,
wanted_list=wanted_list,
request_args=request_args
)AutoScraperはPythonのrequestsライブラリを使用しており、レート制限をサポートしていません。レート制限に対応するには、手動でスロットリングを実装するか、ratelimitライブラリのような既製のソリューションを使用できます。
AutoScraperは非動的Webサイトでのみ動作するため、CAPTCHAで保護されたサイトを扱えません。このようなケースでは、LinkedIn、Amazon、Zillowのようなサイトからの構造化データ抽出をサポートするBright Data Web Scraping APIのような、より堅牢なソリューションの利用をご検討ください。
今すぐサインアップして、無料トライアルを含むBright Dataの製品をぜひお試しください!
