Help us understand the problem. What is going on with this article?

実践/現場のPythonスクレイピング流儀

(この記事は入門者の方向けにも、最初のインストールから説明があります。)

え?
もう自分は基本的なスクレイピングはできる』?
だから忘れた関数がある時だけググれば仕事はやっていける』?
ちょっと待ってください。

  • driverがサイトを開いた後にtime.sleep()で適当な時間待ってる人
  • 無限スクロールの対処がわからなくて、手動でマウススクロールしてる人
  • reCAPTCHAは突破不可能だと諦めてる人

あなた達もこの記事の対象です!

今回の記事は、過去の他の方の記事に比べ、下記の内容を充実させた2019年最新版になっています。

  • Python3
  • 抜け漏れ対策のwait.until()関数 => 実務では最重要。time.sleepはご法度
  • IDやClassが無くても、AltやPlaceholderなどから力技で抽出する技
  • パスワード突破方法
  • reCAPTCHA突破方法
  • 無限スクロール系突破方法
  • Pythonを使うべきではないケース例

勿論、初心者の方の為に導入部分から丁寧に説明していきます。
Pythonでスクレイピングしたいけど、何をすればいいのかわからない
そのような方向けに、今回はケース別に、コピペですぐ実行できるPythonの実践的スクレイピングのコードを紹介しています。

世の中にはスクレイピング関係の仕事が山程あります。
もしPythonを使ってキャリアアップしたい方は、必ず習得しておいた方が良いでしょう。

必ず確認!必要なプログラムをまずは揃える

Python3.7

https://www.python.org/downloads/
Python2だと、for-loopの変数名がグローバルに漏れたりする危険があります。
過去記事ではPython2が多かったと思いますが、早めにPython3へ移行しましょう。

ChromeDriver

https://sites.google.com/a/chromium.org/chromedriver/downloads
(※お使いのchromeと同じバージョンをインストールしてください。2019年3月現在は73が公式最新版です。)

ダウンロードして解凍したファイルを、環境変数が通っているフォルダに保存してください。
Mac, Windows両方において、「/usr/local/bin/」に置けば大丈夫です。

これはスクレイピングのエンジンのようなものです。
通常のブラウザと違い、ChromeDriverはブラウザを立ち上げずに、目に見えないシステム側でサイトアクセスして、プログラムで操作する事が可能です。

ちなみに、下記のコマンドでも可能という記事がありますが、

terminal
pip install chromedriver-binary

この方法でインストールすると、公式版を通り越して、開発版を優先するケースがあります。
そのため正常に動作しないドライバーがインストールされる事があるので、極力使わないでください。

Selenium, BeautifulSoup

(scrapyというフレームワークにご興味がある方は、コメントで「非推奨である理由」を記載しておりますので御覧ください。 3/20追記 @reon777 さん質問より )
Seleniumは、上記ChromeDriverを操作する為のプログラムです。
BeautifulSoupは、サイトのHTMLコードの中から、お目当ての情報を抽出するソフトです。

terminal
pip install selenium
pip install beautifulsoup4

ここまでインストール出来たら十分です。

最初のスクレイピング 一番簡単な基本

試しに下記コードを実行して、ブラウザが立ち上がるか見てみましょう。

ch1_first-scraping.py
import time
from selenium import webdriver

# 仮想ブラウザ起動、URL先のサイトにアクセス
driver = webdriver.Chrome()
driver.get('https://www.google.com/')
time.sleep(2)

# サイト内から検索フォームを探す。
# Googleでは検索フォームのNameが「q」です。
el = driver.find_element_by_name("q")
# 検索フォームに文字を入力
el.send_keys('Qiitaで記事投稿楽しいぞ!!!')
time.sleep(2)

# 検索フォーム送信(Enter)
el.submit()

from bs4 import BeautifulSoup
soup = BeautifulSoup(driver.page_source, features="html.parser")
# タイトルをターミナル上に表示
print(soup.title.string)

無事に下画像のようにタイトルが表示されたでしょうか?
python-selenium-scraping
今あなたは最初のクローリングとスクレイピングに成功したのです。
Google検索ページをクロールして、タイトルをスクレイピングした訳ですね。

発展編 ブラウザを起動しないでスクレイピング

次に、こちらのサンプルを動かしてください。
先程のサンプルに、Optionを載せてブラウザを起動させずにプログラムが動きます。

ch1_hidden-browser.py
import time
from selenium import webdriver

from selenium.webdriver.chrome.options import Options
op = Options()
# --headlessだけではOSによって動かない、プロキシが弾かれる、
# CUI用の省略されたHTMLが帰ってくるなどの障害が出ます。
# 長いですが、これら6行あって最強かつどんな環境でも動きますので、必ず抜かさないようにしてください。
op.add_argument("--disable-gpu");
op.add_argument("--disable-extensions");
op.add_argument("--proxy-server='direct://'");
op.add_argument("--proxy-bypass-list=*");
op.add_argument("--start-maximized");
op.add_argument("--headless");
driver = webdriver.Chrome(chrome_options=op)

#driver = webdriver.Chrome()
driver.get('https://www.google.com/')
time.sleep(2)

el = driver.find_element_by_name("q")
el.send_keys('Qiitaで記事投稿楽しいぞ!!!')
time.sleep(2)

el.submit()

from bs4 import BeautifulSoup
soup = BeautifulSoup(driver.page_source, features="html.parser")
print(soup.title.string)

python-selenium-scraping
いかがですか?ターミナル上に、先程と同じようにタイトルが表示されましたか?
もしうまく行かなかった場合は、コメントにエラー内容を記載してください。

それでは、簡単なサンプルが終わったことですので
早速ケース別のコピペ集を見ていきましょう。

ログインが要らない、普通のサイト

一番簡単です。下をコピペするだけで大丈夫です。

対象がセレクターで選べる場合

ch2_base-scraping.py
URL      = "https://qiita.com" # <= ここにスクレイピングしたい対象URLを書いてください
Selector = "h1.nl-Hero_title"  # <= ここにスクレイピングしたい対象のCSSセレクタを書いてください

# 必須
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from bs4 import BeautifulSoup

# Selenium用オプション
op = Options()
op.add_argument("--disable-gpu");
op.add_argument("--disable-extensions");
op.add_argument("--proxy-server='direct://'");
op.add_argument("--proxy-bypass-list=*");
op.add_argument("--start-maximized");
op.add_argument("--headless");
driver = webdriver.Chrome(chrome_options=op)

# Seleniumでサイトアクセス
# スクレイピングしたい対象が描写されるまでWait
# time.sleep()はご法度!指定した時間待っても描写されない事はままあるので。
driver.get(URL)
WebDriverWait(driver, 30).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, Selector))
)

soup = BeautifulSoup(driver.page_source, features="html.parser")
el = soup.select(Selector)[0].string
print(el)

うまく動作したでしょうか?
試しに、他のサイトでも試してみてください。

例えばここから・・・
URL = "http://blog.livedoor.jp/lalha/archives/50105281.html"
Selector = "h1.article-title a"
python-selenium-scraping

うまく行きましたね!
上記は、セレクターで選べるものは全て可能です。
では次に、もっと複雑なケースの抽出方法を見ていきましょう。

対象が特殊な条件の場合

ここから先は、先程の『対象がセレクターで選べる場合』の「ch2_base-scraping.py」を元に、一部だけを変えます。

まずは「ch2_base-scraping.py」の冒頭にあるSelectorは、「body」だけを代入してください。
ここからbeautifulsoupの指定方法だけ変えれば、ありとあらゆるもの全て取得できます。

特定のテキストを含む要素だけ取得したい

el = soup.find(text="プログラムが上手くなりたい")

特定のテキストを含む『div』だけ取得したい

el = soup.find("div", text="プログラムが上手くなりたい")

正規表現で特定のテキストを含む『div』だけ取得したい

el = soup.find("div", text=re.compile("^(?=.*円)(?!.*:)"))

特定のaltを含むimgだけを取得したい

el = soup.find("img", alt="商品詳細")

(3/20追記 @megmism さん情報)

# select関数を用いる場合
el = soup.select('img[alt="商品詳細"]')[0]

あるリンクのhrefを取得したい

el = soup.find(~~~ a).get("href")

ページ内の全てのh2を取得したい

# 返り値はリスト
el = soup.find_all("h2")

ページ内の全てのh2, h3を取得したい

# 返り値はリスト
el = soup.find_all(['h2', 'h3'])

ページ内の全てのh系タグを取得したい

el = soup.find_all(re.compile("^h[0-9]"))

ある要素の親要素を取得したい

el = soup.find(~~~).parent

ある要素のn番目のdiv子要素を取得したい

el = soup.find(~~~).select("div:nth-of-type(2)")[0]

og:imageを取得したい

el = soup.find('meta', attrs={'property': 'og:image', 'content': True})["content"]

(3/20追記 @megmism さん情報)

# select関数を用いる場合
el = soup.select("meta[property='og:image']")[0]

まだまだどんな方法でも取得する事が可能です。
もし希望があったら、コメントで希望属性を教えてください。

ログインが要る、会員制サイト

ログインが必要な場合、IDとパスワードの入力が必要になります。
まず、Qiitaで試してみましょう。

ch3_login.py
URL      = "https://qiita.com/login" # <= ここにスクレイピングしたい対象URLを書いてください
ID       = ""                        # <= ここにログインIDを書いてください
ID_sel   = "#identity"               # <= ここにログインID欄のCSSセレクタを書いてください
PASS     = ""                        # <= ここにログインパスワードを書いてください
PASS_sel = "#password"               # <= ここにログインパスワード欄のCSSセレクタを書いてください
Selector = ".st-Header_loginUser img"# <= ここにスクレイピングしたい対象URLを書いてください

# 必須
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from bs4 import BeautifulSoup

# Selenium用オプション
op = Options()
op.add_argument("--disable-gpu");
op.add_argument("--disable-extensions");
op.add_argument("--proxy-server='direct://'");
op.add_argument("--proxy-bypass-list=*");
op.add_argument("--start-maximized");
op.add_argument("--headless");
driver = webdriver.Chrome(chrome_options=op)

# ログインページアクセス
driver.get(URL)
WebDriverWait(driver, 30).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, PASS_sel))
)
driver.find_elements_by_css_selector(ID_sel)[0].send_keys(ID)
driver.find_elements_by_css_selector(PASS_sel)[0].send_keys(PASS)
driver.find_elements_by_css_selector(PASS_sel)[0].send_keys(Keys.ENTER)

# ターゲット出現を待機
WebDriverWait(driver, 30).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, Selector))
)
soup = BeautifulSoup(driver.page_source, features="html.parser")
el = soup.select(Selector)[0]
print(el.get("alt"))

いかがでしょうか?実際にアクセスして、ユーザー名を抜き出す事が出来たでしょうか。
もし成功できなかったら、コメントで質問してみてください。

無限スクロール系

Lazy-loadを使っているサイトも当てはまります。
今回はYoutubeで試してみましょう。

ch4_infinite-scroll.py
URL      = "https://www.youtube.com"
Selector = "#secondary > img"

# 必須
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from bs4 import BeautifulSoup

# Selenium用オプション
op = Options()
op.add_argument("--disable-gpu");
op.add_argument("--disable-extensions");
op.add_argument("--proxy-server='direct://'");
op.add_argument("--proxy-bypass-list=*");
op.add_argument("--start-maximized");
op.add_argument("--headless");
driver = webdriver.Chrome(chrome_options=op)

# ページアクセス
driver.get(URL)
WebDriverWait(driver, 30).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, Selector))
)
soup = BeautifulSoup(driver.page_source, features="html.parser")
el = soup.select(Selector)[0].select("img")[0]["src"]
print(el)

まず、これを動かしてみてください。
python-selenium-scraping
list index out of rangeと出たら、「該当したものがないので、リストは空ですよ」という意味です。つまり見つけられなかったという事ですね。

これを突破するためには、スクリプトを一行追加してやりましょう。

ch4_infinite-scroll.py
URL      = "https://www.youtube.com"
Selector = "#secondary > img"

# 必須
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from bs4 import BeautifulSoup

# Selenium用オプション
op = Options()
op.add_argument("--disable-gpu");
op.add_argument("--disable-extensions");
op.add_argument("--proxy-server='direct://'");
op.add_argument("--proxy-bypass-list=*");
op.add_argument("--start-maximized");
op.add_argument("--headless");
driver = webdriver.Chrome(chrome_options=op)

# ページアクセス
driver.get(URL)
WebDriverWait(driver, 30).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, Selector))
)

# こいつを追加!!!!!!!
driver.execute_script("window.scrollTo(0, document.getElementById('js-content-box').scrollHeight)")

soup = BeautifulSoup(driver.page_source, features="html.parser")
el = soup.select(Selector)[0].select("img")[0]["src"]
print(el)

今度は正常に読み込みが成功しますね。
スクリーンショット 2019-03-18 12.36.56.png

seleniumでは、javascriptを実行するexecute_script()関数が用意されています。
ここではターゲットのheightを取得して、一番下までスクロールするという動きを実現しています。
一番下までスクロールするので、Lazy-Load後の画像が描写されるわけです。

以上をもって、ユースケース別サンプルの紹介を終わります。
もし他に希望がありましたら、コメントで教えてください。

BASIC認証を突破する方法

(2020/06/09追記 @namariver さん情報 )
上記の通り、Seleniumはiframeやpopup alertに対しても操作を可能にする方法が存在します。しかし、BASIC認証に対しては直接操作はできません。その場合は下記の形式を用いてURLアクセスする事でBASIC認証を通過する事が可能です。

driver.get("http://username:password@url")

スクレイピングに失敗してしまった場合の通知方法

(3/22追記 @ma7ma7pipipi さんご質問 )
仮に対象サイトがHTML構成を変えた場合、
もしくは、途中でwifiが何らかの不具合で消えてしまった場合、
こういった時にスクレイピングが失敗してしまいます。

この時、最もオススメする対処法は下記の通りです。
どこでどういったエラーが発生したかをログに残す
それ以降の処理は正常に続ける

まずは①で、エラーを残すログのソースコードを見ましょう。

detect-error.py
# 必須
from selenium.common.exceptions import TimeoutException

try:
    # 対象が画面に表示されるまで、最大30秒待つ
    element = WebDriverWait(driver, 30).until(
        EC.presence_of_element_located((By.CSS_SELECTOR, "#target img"))
    )
    # 以降の処理を記述していく
    # ~~~
except TimeoutException:
    # 30秒経っても対象が表示されなかった場合、エラーログをprintして、続きのプログラムに移行
    print("Assert: 対象物がページにありませんでした index[" + n + "] URL:" + url)
    continue

もしも毎日システム側で自動スクレイピングを行っている場合は、メールに通知する方法がいいかもしれません。
必ずメールサーバーが立ち上がっているかを確認した上で、下記の記事を参考にメール通知をexcept TimeoutException:の後に記載してください。
https://qiita.com/okhrn/items/630a87ce1a44778bbeb1

Pythonを使うべきではないスクレイピングケース例

  • スペックが弱い場合

これに尽きます。
SeleniumはかなりCPUを使います。
例えば1000個のスクレイピングを毎週行っていた私のパソコンは、アプリが何も立ち上がらなくなり、再起動するとMacのDockが消失していました。

対策としては、2つ考えられます。curl + xpathのコンボを使いましょう。

まとめ

今回のサンプルを使い、スクレイピングを回す事はできたでしょうか?

個人的にスクレイピングをしたいという方は少ないと思いますが、
冒頭でも書いた通り、世の中にはスクレイピング関係の仕事は山程あります。

もしPythonを使ってキャリアアップしたい方は、
今回の記事のサンプルを一通り触っておくと、仕事を得るチャンスは増えるでしょう。

もし今回の記事の内容が分かりづらく、ご質問があるという方は、
ここのコメントでメッセージを頂ければ返答/追記していきますのでお気軽にご連絡ください。

ryuta69
ᓚᘏᗢ zzz...。oO
https://github.com/ryuta69
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした