Edited at

入門系記事にはない、実践/現場の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

(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)


無事に下画像のようにタイトルが表示されたでしょうか?



今あなたは最初のクローリングとスクレイピングに成功したのです。

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)




いかがですか?ターミナル上に、先程と同じようにタイトルが表示されましたか?

もしうまく行かなかった場合は、コメントにエラー内容を記載してください。

それでは、簡単なサンプルが終わったことですので

早速ケース別のコピペ集を見ていきましょう。


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

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


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


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"

うまく行きましたね!

上記は、セレクターで選べるものは全て可能です。

では次に、もっと複雑なケースの抽出方法を見ていきましょう。


対象が特殊な条件の場合

ここから先は、先程の『対象がセレクターで選べる場合』の「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"))


いかがでしょうか?実際にアクセスして、ユーザー名を抜き出す事が出来たでしょうか。

もし成功できなかったら、コメントで質問してみてください。

長文になりそうだったらTwitterをフォローして頂いてDMでも大丈夫です。


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

さて、グレーな実践的内容になってきました。

やはりプログラマーたるもの、こういうブロッカーを突破でいる最強エンジニアに憧れますよね。

まずは色々な種類のブロッカーの対策法をそれぞれ見ていきましょう。


reCAPTCHA系

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

「あれ、これ普通にクリックできそう?」と思ったあなた、実はこれiframeの中にあって、CSSセレクトで探す事ができないのです。

突破方法の一つとして、有料サービスでトークン発行する2CAPTCHAなどがありますがスマートではありません。

という事で無料で突破できると思われる方法を二種類、提案(⇐ここ重要)します。

これは技術的可能性の話であって、決してサイト規約にスクレイピング禁止と明言されているサイトなどで実行しようなどと思わないでください。ちなみに価格コムなどがスクレイピングを禁止しています。

①音声系

https://github.com/ecthros/uncaptcha

②クリック系

op.add_argument("--window-size=1920,1080");

で、サイズ固定してからの

https://github.com/asweigart/pyautogui

③文字系

https://www.scrapehero.com/how-to-solve-simple-captchas-using-python-tesseract/

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


無限スクロール系

例えばツイッターや、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(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)


まず、これを動かしてみてください。



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(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)


今度は正常に読み込みが成功しますね。

seleniumでは、javascriptを実行するexecute_script()関数が用意されています。

ここではターゲットのheightを取得して、一番下までスクロールするという動きを実現しています。

一番下までスクロールするので、Lazy-Load後の画像が描写されるわけです。

以上をもって、ユースケース別サンプルの紹介を終わります。

もし他に希望がありましたら、コメントで教えてください。


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

(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のコンボを使いましょう。

趣旨が違うので今回は割愛しますが、もしご希望の方が多ければ別記事で説明します。



Colaboratoryで実行する。

これもColaboratoryの説明が膨大になりますので、割愛致します。

ご希望の方が多ければ別記事を作成します。


まとめ

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

個人的にスクレイピングをしたいという方は少ないと思いますが、

冒頭でも書いた通り、世の中にはスクレイピング関係の仕事は山程あります。

もしPythonを使ってキャリアアップしたい方は、

今回の記事のサンプルを一通り触っておくと、仕事を得るチャンスは増えるでしょう。

今後、よりユースケースを網羅するためにリライト予定です。

もし今回の記事の内容が分かりづらく、ご質問があるという方は、

ここのコメントか、私のTwitter / Facebookでメッセージを頂ければ返答/追記していきますのでお気軽にご連絡ください。


おまけ 次の記事を読む


【71個掲載】Pythonを学ぶなら見るべき参考本/サイト/情報の、学べる分野や金額など徹底網羅

https://qiita.com/ryuta69/items/fdb5e227fa5dcbcc4691

今回は『Python』の参考書とか勉強サイトとか多すぎてよくわからない!という方の為に


  • 参考書

  • 動画学習サイト

  • テキスト学習サイト

  • フォローすべきqiitaアカウント

  • その他、無料で転がってる有益資料

をそれぞれ徹底網羅しました!

Pythonに興味ある人は是非御覧ください!