Go to list of users who liked
Share on X(Twitter)
Share on Facebook
More than 5 years have passed since last update.
Re:ゼロからFlaskで始めるHeroku生活 〜Selenium & PhantomJS & Beautifulsoup〜
はじめに
初めてPythonのFlaskとHerokuを使って、スクレイピングした情報をjsonで返すAPIを作ったので、その際におこなった方法をまとめたいと思います。
herokuでHelloWorldまでに使用するものやPythonの環境構築などは前編にあたる
Re:ゼロからFlaskで始めるHeroku生活 〜環境構築とこんにちは世界〜
にて、
今回作るプログラムをHerokuにデプロイするまでは後編にあたる
Re:ゼロからFlaskで始めるHeroku生活 〜PhantomJSをHerokuへ〜
にて書いているので合わせてご覧ください
今回やること
勉強になれば車輪の再発明でもいいじゃない
今回はSlideShareを題材として、SeleniumをとPhantomJSを使ったスクレイピングのやり方を書きます。
1つの記事にまとめた際長くなってしまった為、Herokuにデプロイする流れは後編に分けています。
具体的な動き
[HerokuURL]/api/[検索ワード]/[ページ数]
例 : ~herokuapp.com/api/python/2
とアクセスすると、
Heroku で PhantomJS が動き Slideshareの検索ページを開く
URLの[検索ワード]を検索ページの検索欄に入力し検索
検索結果の言語設定を日本語に変更
スクレイピングし終わったらjson形式にして投げる!
という事をするAPIを作りたいと思います。
今回使用したもの
- Mac OSX El Capitan 10.11.6
- Python3.5.1
- Flask
- selenium
- 検索欄に入力したり次へボタンをクリックできる
- beautifulsoup4
- Webデータを抽出したりする
- lxml
- beautifulsoupとセットで使う
- CORS
- クロスドメイン制約回避
- Heroku
- Herokuにアクセスして、スクレイピングしたデータをJson形式にして返すようにする
- PhantomJS
- Herokuでseleniumを使うために使う
- いわゆるGUIがない裏で動くブラウザ
準備
今回する前にやっておく環境構築
Re:ゼロからFlaskで始めるHeroku生活 〜環境構築とこんにちは世界〜
でHelloWorldをするまでにインストールしている
FlaskとGunicornは入れておいてください
環境などはpyenv-virtualenvなどのお好きなものを用意してください。
今回するために追加でする環境構築
PhantomJS
Herokuで動かす前にローカルで動作確認をするためにローカルにもPhantomJSを入れます。
コードから操作できるGUIがないブラウザという認識でいいと思います。
参考 :PhantomJSを使って色々試してみる
$ brew install phantomjsSelenium
クロスブラウザ、クロスプラットフォームのUIテストツール、らしいです。
普通のスクレイピングだと指定したURLで表示されている分しかできませんが、seleniumを使うことで次のページに行くボタンを押させたり、文字を入力して検索ボタンを押したりすることができます。すごい
Rubyですがどういうものか参考になる記事 :
WebのUIテスト自動化 - Seleniumを使ってみる
$ pip install seleniumbeautifulsoup
取得したWebページのデータを加工する時に使います。
参考 :PythonとBeautiful Soupでスクレイピング
$ pip install beautifulsoup4lxml
beautifulsoupと合わせて使います。
$ pip install lxmlcorsでクロスドメイン制約をよけるやつ
普通にJsonを返すAPIを作ってもクロスドメイン制約云々で、対策を行っていないAPIをChromeで使う際に使おうとすると面倒なのでどうせなので対策をしておきます。
コード説明にてリンク置いておきます。
$ pip install -U flask-corsできたもの
# -*- coding: utf-8 -*-importjson# ここからスクレイピング必要分frombs4importBeautifulSoup# ここからseleniumでブラウザ操作必要分fromseleniumimportwebdriverfromselenium.webdriver.common.keysimportKeys# 文字を入力する時に使う# ここからflaskの必要分importosfromflaskimportFlask# ここからflaskでcorsの設定 ajaxを使う時のクロスドメイン制約用fromflask_corsimportCORS,cross_originapp=Flask(__name__)CORS(app)@app.route('/')defindex():return"使い方 : /api/検索する単語/取得ページ数"@app.route('/api/<string:word>/<int:page>')# 検索ワード/ページ数をパスから変数に受け取るdefslide(word,page):driver=webdriver.PhantomJS()# PhantomJSを使うdriver.set_window_size(1124,850)# PhantomJSのサイズを指定するdriver.implicitly_wait(20)# 指定した要素などがなかった場合出てくるまでdriverが最大20秒まで自動待機してくれるURL="http://www.slideshare.net/search/"driver.get(URL)# slideshareのURLにアクセスするdata_list=[]# 全ページのデータを集める配列search=driver.find_element_by_id("nav-search-query")# 検索欄要素を取得search.send_keys(word)# 検索ワードを入力search.submit()# 検索をsubmitするlang=driver.find_element_by_xpath("//select[@id='slideshows_lang']/option[@value='ja']")# 言語選択リストの日本語の部分を抽出lang.click()# 言語選択の日本語を選択foriinrange(0,page):print(str(i+1)+u"ページ目")data=driver.page_source.encode('utf-8')# ページ内の情報をutf-8で用意するsoup=BeautifulSoup(data,"lxml")# 加工しやすいようにlxml形式にするslide_list=soup.find_all("div",class_="thumbnail-content")# スライド単位で抽出forslideinslide_list:slide_in={}# スライドの情報を辞書形式でまとめる# スライドの投稿者の名前を入手name=slide.find("div",class_="author").textslide_in["name"]=name.strip()# strip()は両端の空白と改行をなくしてくれる# スライドのタイトルを入手title=slide.find("a",class_="title title-link antialiased j-slideshow-title").get("title")# 指定したタグ&クラス内のtitleを出すslide_in["title"]=title# スライドのリンクを入手link=slide.find("a",class_="title title-link antialiased j-slideshow-title").get("href")# 指定したタグ&クラス内のhrefを出すslide_in["link"]="http://www.slideshare.net"+link# スライドのサムネのリンクを入手imagetag=slide.find("a",class_="link-bg-img").get("style")# 指定したタグ&クラス内のstyleを出すimage=imagetag[imagetag.find("url(")+4:imagetag.find(");")]# いらない部分を取り除くslide_in["image"]=image# スライドのページ数であるslidesとlikesを入手info=slide.find("div",class_="small-info").string# slidesとlikesの文字列を入手slides=info[7:info.find("slides")]# slides部分を抽出slide_in["slides"]=slides.strip()# strip()は両端の空白と改行をなくしてくれるif"likes"ininfo:likes=info[info.find(",")+2:info.find("likes")]# likes部分を抽出else:likes="0"slide_in["likes"]=likes.strip()# strip()は両端の空白と改行をなくしてくれるdata_list.append(slide_in)# data_listに1ページ分の内容をまとめるdriver.execute_script('window.scrollTo(0, 3000)')# ページャーのある下に移動next=driver.find_element_by_xpath("//li[@class='arrow']/a[@rel='next']")# ページャーのNEXT要素を抽出next.click()# Nextボタンをクリックdriver.close()# ブラウザ操作を終わらせるjsonstring=json.dumps(data_list,ensure_ascii=False,indent=2)# 作った配列をjson形式にして出力するreturnjsonstring# bashで叩いたかimportで入れたかを判定するif__name__=='__main__':app.run()コード解説
コード内にもコメントを書いていますが、上から大事そうな箇所を説明していきたいと思います。インポートは書いているそのままなので省略
CORS
ChromeなどでAPIを使用したプログラムが動かない!という経験はあると思います。
せっかくAPIを作成するのですから、対策をしておいてあげましょう。
from flask_cors import CORS, cross_originapp = Flask(__name__)CORS(app)と書くとなんやかんやで対策してくれるそうです。べんり
参考 :https://flask-cors.readthedocs.io/en/latest/
Flaskのパスから引数を取る
Flaskのrouteのところに<型名:変数名>と書いて、defの()内にその変数名を書くとパスの中身を引数として受け取ることができます。
@app.route('/api/<string:word>/<int:page>') # 検索ワード/ページ数をパスから変数に受け取る def slide(word,page):参考 :Flask を使いこなそう
PhantomJSのブラウザサイズを決める
driver.set_window_size(1124, 850)ブラウザサイズを決めなかった場合、うまく要素をとったり、スクロールすることができません。
サイズの数値の値は調べた時に書いてあったままなので理由は不明。
要素の読み込み待ち処理
driver.implicitly_wait(20)こう書くことで、以降のdriver.find~~のIDやclassを指定して要素を取得&操作する際に最大10秒間待ってくれ、読み込みが完了してくれたら即実行してくれる便利な状態になってくれます。
いちいち明示的にtime.sleep(3)などと予想待機時間をかかなくて良いのでseleniumで操作する際すごく便利です。
参考 :このサイトのImplicit Waitsの箇所に書かれています
テキストをフォームのボックスに入力しsubmit
search = driver.find_element_by_id("nav-search-query") # 検索欄要素を取得search.send_keys(word) # 検索ワードを入力search.submit() # 検索をsubmitするブラウザからidでinput要素などを取得した後は、send_keys("hoge")などで値を入れることができます。
その要素がformの中であれば、.submit()をつけることで送信することができます。
ドロップダウンを選択する
lang = driver.find_element_by_xpath("//select[@id='slideshows_lang']/option[@value='ja']") # 言語選択リストの日本語の部分を抽出lang.click() # 言語選択の日本語を選択今回は要素の指定方法をidやclassではなくXPATHで指定しています。
理由としては、idが複数、または親にしかidがついていない子要素を選択する時にidやclass以外の部分で指定する必要があるからです。
ちなみに、このように日本語に切り替えなければローカルでは日本語のものをとってきてくれていても、Heroku側の方では全言語のスライドをとってきてしまいます。
idが複数あった場合
lang = driver.find_elements_by_id("slideshows_lang")lang[1].find_elements_by_tag_name("option")# 複数抽出する場合はelementからelementsになります親にしかidがない場合
lang = driver.find_element_by_id("slideshows_lang")lang.find_element_by_tag_name("option")XPATHの書き方や他の抽出方法は参考をみてください。
参考 :Locating Elements
スクレイピングできる状態にする
data = driver.page_source.encode('utf-8') # ページ内の情報をutf-8で用意するsoup = BeautifulSoup(data,"lxml") # 加工しやすいようにlxml形式にするwebdriverで入手したWebサイトのページデータをutf-8でエンコードした後、BeautifulSoupと相性のいいlxmlを使ってスクレイピングをし易い状態にします。
forの中に入れているのはページが変わった時は毎回読み込む必要があるためです。
スクロール
driver.execute_script('window.scrollTo(0, 3000)') # ページャーのある下に移動これでPhantomJSをJavaScriptで下に3000pixelスクロールさせることができます。
PhantomJSってGUIないならスクロール意味なくね?と思うかもしれませんが、スクロールさせなかった場合はエラーが出ます。
3000にしている理由は一番下にいってほしかったのでとりあえず3000にしてみました。
複数存在するクラス名の下にあるidもclassもないaタグ対策
next = driver.find_element_by_xpath("//li[@class='arrow']/a[@rel='next']") # ページャーのNEXT要素を抽出next.click() # NextボタンをクリックSlideshareのページャー部分のNextを押そうとした時、 PreviousとNextどちらにもclass="arrow"がついていて、その中のaタグにはidもclassもついていませんでした。子要素のaタグにはrel="next"と書いていたので、その部分を親とrelまで含めて指定できるXPATHにしています。
作った配列をjson形式にする
jsonstring = json.dumps(data_list,ensure_ascii=False,indent=2) # 作った辞書をjson形式にして出力するreturn jsonstringjson.dumps(配列,辞書データ,日本語が含まれる場合False,インデントで整理)配列、または辞書を渡すとjson形式にしてくれる。indentは省略可能でindent=2とすると半角スペース2文字でインデントし整理して見やすいようにしてくれます。
参考 :[Python] JSONを扱う
動きを確認してからHerokuにデプロイするまでの流れ
ローカルでFlaskを立ち上げ確認する
必要なものが全てインストールされているとします。
フォルダとFlaskの準備
$ mkdir slide$ cd slide$ touch api.py Procfile# flaskのファイルと設定を書くファイルを作るFlaskを立ち上げる時に必要なファイル
Procfile
web: gunicorn hello:app --log-file=-上記を参照まずはFlaskで動きを確認してみる
$ python api.py * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)http://127.0.0.1:5000/api/python/2
このパスでアクセスして、slideshareでpythonで検索したページを2枚分とってきてもらいましょう。
結果
Safariではjsonは羅列して見えていますが、ChromeでJSONViewなどを入れて見るときれいに見れます。
参考
Pythonでクローリング・スクレイピングに使えるライブラリいろいろ
Herokuにデプロイする
Herokuにデプロイする流れは、後編である
Re:ゼロからFlaskで始めるHeroku生活 〜PhantomJSをHerokuへ〜
にて書いているのでよろしくおねがいします。
あとがき
とりあえず基本的なSeleniumでのブラウザの操作(文字入力、submit、ドロップダウンリストの選択、要素のクリック、XPATH指定)とスクレイピング(テキスト、画像、URL、文字列の加工とjson化)
のやり方を書くことができたと思うので、誰かの参考になれば嬉しく思います。
改善点でしたり、間違い等ございましたらコメント欄などでご指摘していただけたら幸いです。
Twitter:@ymgn_ll
Register as a new user and use Qiita more conveniently
- You get articles that match your needs
- You can efficiently read back useful information
- You can use dark theme

