Python
Selenium
スクレイピング
Python3
BeautifulSoup

入門系記事にはない、実践/現場のPythonスクレイピング流儀【2019年最新】

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

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

  • 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

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(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(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="商品詳細")

あるリンクの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"]

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

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

ログインが必要な場合、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(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"))

いかがでしょうか?実際にアクセスして、ユーザー名を抜き出す事が出来たでしょうか。
もし成功できなかったら、コメントで質問してみてください。
長文になりそうだったらTwitterをフォローして頂いてDMでも大丈夫です。

スクレイピングブロックを突破する方法

さて、グレーな実践的内容になってきました。
やはりプログラマーたるもの、こういうブロッカーを突破でいる最強エンジニアに憧れますよね。
まずは色々な種類のブロッカーの対策法をそれぞれ見ていきましょう。

reCAPTCHA系

ここはグレーな範囲なので簡単にお話します。
python-selenium-scraping

「あれ、これ普通にクリックできそう?」と思ったあなた、実はこれiframeの中にあって、CSSセレクトで探す事ができないのです。
突破方法の一つとして、有料サービスでトークン発行する2CAPTCHAなどがありますがスマートではありません。

という事で無料で突破できると思われる方法を二種類、提案(⇐ここ重要)します。
これは技術的可能性の話であって、決してサイト規約にスクレイピング禁止と明言されているサイトなどで実行しようなどと思わないでください。ちなみに価格コムなどがスクレイピングを禁止しています。


https://github.com/ecthros/uncaptcha


op.add_argument("--window-size=1920,1080");
で、サイズ固定してからの
https://github.com/asweigart/pyautogui

後は自己責任で・・・・・・

無限スクロール系

例えばツイッターや、Adwordsのキーワードプランナーなどが当てはまります。
Lazy-loadを使っているサイトも当てはまりますね。
今回はマイベストさんで試してみましょう。

ch4_infinite-scroll.py
URL      = "https://my-best.com/36"
# ランキング内の画像がターゲット
Selector = ".c-slider__inner > div"

# 必須
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(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と出たら、「該当したものがないので、リストは空ですよ」という意味です。つまり見つけられなかったという事ですね。

なぜならマイベストさんはLazy-Loadという、サイト速度を向上させるために画像を遅延読込するプラグインを用いているからです。
これを突破するためには、スクリプトを一行追加してやりましょう。

ch4_infinite-scroll.py
URL      = "https://my-best.com/36"
# ランキング内の画像がターゲット
Selector = ".c-slider__inner > div"

# 必須
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(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後の画像が描写されるわけです。

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

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

  • スペックが弱い場合

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

対策としては、2つ考えられます。


curl + xpathのコンボを使いましょう。
趣旨が違うので今回は割愛しますが、もしご希望の方が多ければ別記事で説明します。


Colaboratoryで実行する。
これもColaboratoryの説明が膨大になりますので、割愛致します。
ご希望の方が多ければ別記事を作成します。

まとめ

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

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

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

今後、よりユースケースを網羅するためにリライト予定です。
「こういったユースケースの情報がほしい」という方はどしどしコメントかツイッターでご連絡ください。

Why do not you register as a user and use Qiita more conveniently?
  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
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