updated on 2021-04-10
Python でクローラーを実装するためのフレームワーク
設定を簡単に変更できたり、コマンドからスクレイピングを実行できるので、非常に開発が楽
Beautifulsoupやlxmlなどのスクレイピングライブラリと違い、scrapyはスクレイピングを行うコマンド生成からログのパスやレベルの設定、並列処理など、1アプリケーションとして丸ごとの機能を生成してくれる。
Selenium は Web ブラウザの操作を自動化するためのフレームワーク
ブラウザをプログラムで動かす為のドライバー
SeleniumとChromeDriver の2つを組みあわせることで、以下のことが可能になる
・スクレイピング
・ブラウザの自動操作(次へボタンや購入ボタンなどを自動で押すなど)
・システムの自動テスト
・非同期サイトのスクレイピング
筆者環境 python: 3.9
$ pip install chromedriver-binary = "==89.0.4389.23.0" # ここは、自分のchromeのバージョンに合わせる $ pip install Scrapy $ pip install selenium $ pip install twisted # 任意だが、インターネット接続時のエラー処理で使える
import chromedriver_binary from selenium.webdriver import Chrome from selenium.webdriver.chrome.options import Options class QuotesSpider(scrapy.Spider): # 一意なスパイダーの定義 name = "school_picker" def __init__(self, *args, **kwargs): """ :param args: :param kwargs: """ super(QuotesSpider, self).__init__(*args, **kwargs) # headless mode の場合 # headless start # Chrome Optionを設定 options = Options() # headlessで必要な項目 options.add_argument('--headless') options.add_argument('--disable-gpu') # headlessで不要な項目 options.add_argument('--disable-desktop-notifications') options.add_argument('--disable-extensions') options.add_argument('--no-sandbox') # シングルプロセス options.add_argument('--single-process') # HTML5のApplication Cacheを無効 options.add_argument('--disable-application-cache') # SSLセキュリティ証明書のエラーページを非表示 options.add_argument('--ignore-certificate-errors') # ウィンドウ最大化 options.add_argument('--start-maximized') self.driver = Chrome(options=options) # headless end ...
Chromeのoption引数に何も指定しなければ良い (デフォルトの状態)
self.driver = Chrome()
スクレイピングの際に、条件から、要素を取得する関数
複数の要素を見つけるには(これらのメソッドはリストを返す)
クラス名で要素の取得
1件
self.driver.find_element_by_class_name('class-name') # クラス名 'class-name'の要素を取得
全件
self.driver.find_elements_by_class_name('class-name')
HTMLタグで要素の取得
1件
find_element_by_tag_name('span') # spanタグの要素を取得
全件
find_elements_by_tag_name('span')
idで要素の取得
1件
find_element_by_id('element-id') # id属性が'element-id'の要素を取得
全件
find_elements_by_id('element-id')
複数の条件から要素を取得
elementById = self.driver.find_element_by_id('element-id') elementByIdAndClass = facultiesInfoDiv.find_elements_by_class_name('element-class')
テキストを取得
spanTag = find_element_by_tag_name('span') spanText = spanTag.text print(spanText)
send_keysに指定した文字を入力できる
例:
from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import Select, WebDriverWait import sys class QuotesSpider(scrapy.Spider): def login(self, response): form = self.driver.find_element_by_tag_name('form') self.driver.find_element_by_name('login_id').send_keys('mypage_login_id') self.driver.find_element_by_name('login_password').send_keys('mypage_login_password') form.submit()
またはjsの関数を実行する (こっちの方が早いのでおすすめ)
loginId = "mypage_login_id" loginPass = "mypage_login_password" self.driver.execute_script( "document.getElementsById('login_id').value=arguments[0];", loginId ) self.driver.execute_script( "document.getElementsById('login_password').value=arguments[0];", loginPass )
self.driver.execute_script('arguments[0].target="_blank"',element)
self.driver.execute_script('arguments[0].click()', element)
で 新規タブでボタンやリンクを開くことができる。
facultyPage = find_element_by_tag_name('a') actions = ActionChains(self.driver) actions.move_to_element(facultyPage) actions.perform() self.driver.execute_script('arguments[0].target="_blank"',facultyPage) self.driver.execute_script('arguments[0].click()', facultyPage)
一番最初に開いたタブ (一番左に開いているタブ)のみ残し、他は全て閉じる
while len(self.driver.window_handles) > 1: self.driver.switch_to.window(self.driver.window_handles[-1]) self.driver.close() self.driver.implicitly_wait(3) self.driver.switch_to.window(self.driver.window_handles[0])
from twisted.internet.error import DNSLookupError from twisted.internet.error import TimeoutError, TCPTimedOutError ... def start_requests(self): for url in self.start_urls: yield Request( url, callback=self.parse, errback=self._errback ) def _errback(self, failure): """ リクエスト処理で例外が発生したとき(HTTPレスポンスステータスコードが200以外)のエラーバック :param failure: """ # log all failures self.logger.error(repr(failure)) if failure.check(HttpError): # these exceptions come from HttpError spider middleware # you can get the non-200 response response = failure.value.response self.logger.error('HttpError on %s', response.url) elif failure.check(DNSLookupError): # DNS lookup failed request = failure.request self.logger.error('DNSLookupError on %s', request.url) elif failure.check(TimeoutError, TCPTimedOutError): request = failure.request self.logger.error('TimeoutError on %s', request.url)
import scrapy from io import BytesIO from PIL import Image import sys class QuotesSpider(scrapy.Spider): ... def saveScreenShot(self, file_name): """ スクリーンショットをJPGに変換して保存 :param file_name: """ # 画面全体をスクリーンショット driver = self.driver w = driver.execute_script('return document.body.scrollWidth') h = driver.execute_script('return document.body.scrollHeight') driver.set_window_size(w, h) png_image = driver.get_screenshot_as_png() # PNGからJPGに変換 try: with Image.open(BytesIO(png_image)) as image: rgb_image = image.convert('RGB') rgb_image.save('path_to_your_image_directory/' + file_name + '.jpg', quality = 95) except OSError: self.logger.error('Exception: 見出し記事の取得に失敗しました') sys.exit(1) self.saveScreenShot('myPage') # 'path_to_your_image_directory/myPage.jpg' が保存される
例: 'paraKeyWords'というキーワードを持つテキストエリアに対して、改行を含む入力を行いたいとき
おすすめのやり方 (推奨)
keywords = 'キーワード1\r\nキーワード2\r\nキーワード3' self.driver.execute_script( "document.getElementsByName('paraKeywords')[0].value=arguments[0];", keywords )
一応、以下の方法でもできますが、改行文字を2重でエスケープしているように見えてレビュワーが混乱するので、基本的にやめた方が良いです。
terarailでは以下のやり方ばかり紹介しているユーザーを多く見かけしますが、よくないやり方なので、真似せず、上記やり方(jsの関数argmentsを使った手法)を取ってください。(上記の方がわかりやすいし、早いです)
非推奨のやり方 (レビュワーの混乱をうむ)
バックスラッシュをエスケープではなく文字として認識させる為にバックスラッシュをエスケープするというもの
keywords = 'キーワード1\r\nキーワード2\r\nキーワード3' driver.execute_script('document.getElementsByName(\'paraKeywords\')[0].value=\'%s\';' % keywords.replace('\r','\\r').replace('\n','\\n')
よくある間違い
改行文字が認識されず、エラーになる
keywords = 'キーワード1\r\nキーワード2\r\nキーワード3' driver.execute_script('document.getElementsByName(\'paraKeywords\')[0].value=\'%s\';' % keywords