ラズパイで各種クローリングライブラリを使用すると、エラーが出る等ですんなり実行できません。この記事では、ラズパイでSelenium
・Pyppeteer
・Requsts-HTML
を用いてクローリングできるようにするための設定をまとめます。
実行環境
Raspberry Pi 4 Model B
$ uname -a
Linux raspberrypi 5.15.84-v7l+ #1613 SMP Thu Jan 5 12:01:26 GMT 2023 armv7l GNU/Linux
$ lsb_release -a
No LSB modules are available.
Distributor ID: Raspbian
Description: Raspbian GNU/Linux 11 (bullseye)
Release: 11
Codename: bullseye
Selenium
まずはクローリングで特に有名な、Selenium
についての設定方法です。
Seleniumでは、使用したいブラウザのバージョンに適したドライバーをインストールする必要があります。自動的に各種ブラウザの最新のドライバーをインストールするために、webdriver-manager
を併用することが多いです。今回はChromeでのクローリングを行います。
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
service = Service(ChromeDriverManager().install()) # 最新ドライバーを自動インストールしてパスを返す
driver = webdriver.Chrome(service=service)
driver.get('https://google.com')
ライブラリバージョン
selenium == 4.7.2
webdriver-manager == 3.8.5
エラーと解決法
ラズパイでこのまま実行すると、エラーが発生しました。Chrome用ドライバーの配布元からは、Linuxが64bit版しか配布されていません。このOSはarmv7l(32bit)なので、ダウンロードできないためです。
Traceback (most recent call last):
File "/home/pi/test.py", line 10, in <module>
service = Service(ChromeDriverManager().install()) # 最新ドライバーを自動インストールしてパスを返す
File "/home/pi/.local/lib/python3.9/site-packages/webdriver_manager/chrome.py", line 39, in install
driver_path = self._get_driver_path(self.driver)
File "/home/pi/.local/lib/python3.9/site-packages/webdriver_manager/core/manager.py", line 30, in _get_driver_path
file = self._download_manager.download_file(driver.get_url())
File "/home/pi/.local/lib/python3.9/site-packages/webdriver_manager/core/download_manager.py", line 28, in download_file
response = self._http_client.get(url)
File "/home/pi/.local/lib/python3.9/site-packages/webdriver_manager/core/http.py", line 33, in get
self.validate_response(resp)
File "/home/pi/.local/lib/python3.9/site-packages/webdriver_manager/core/http.py", line 16, in validate_response
raise ValueError(f"There is no such driver by url {resp.url}")
ValueError: There is no such driver by url https://chromedriver.storage.googleapis.com/110.0.5481.77/chromedriver_linux32.zip
調べてみると、sudo apt-get install chromium-chromedriver
を行ってドライバーをダウンロードする解決法が挙げられていました。以下のようにして使用します。
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
options = Options()
options.BinaryLocation = ("/usr/bin/chromium-browser") # バイナリ実行可能ファイルの場所指定 (chromium)
service = Service("/usr/bin/chromedriver") # インストールしたドライバーのパスを指定
driver = webdriver.Chrome(options=options, service=service)
driver.get('https://google.com')
もし、実行できなかった場合はchromiumとドライバーのバージョンが異なっている可能性が高いので、sudo apt-get install chromium-browser
で更新(インストール)してください。
Pyppeteer
Pypetter
は非同期処理を行ってアクセスします。複数のサイトに同期にアクセスなどがやりやすいです(チュートリアル1, 2)。
from pyppeteer import launch
import asyncio
async def crawling():
browser = await launch() # Chroniumをヘッドレスモードで起動
page = await browser.newPage()
await page.goto('https://google.com')
asyncio.get_event_loop().run_until_complete(crawling())
ライブラリバージョン
pyppeteer == 1.0.2
エラーと解決法
ラズパイでこのまま実行すると、エラーが発生しました。
Traceback (most recent call last):
File "/home/pi/test.py", line 9, in <module>
asyncio.get_event_loop().run_until_complete(crawling())
File "/usr/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
return future.result()
File "/home/pi/test.py", line 5, in crawling
browser = await launch() # Chroniumをヘッドレスモードで起動
File "/home/pi/.local/lib/python3.9/site-packages/pyppeteer/launcher.py", line 307, in launch
return await Launcher(options, **kwargs).launch()
File "/home/pi/.local/lib/python3.9/site-packages/pyppeteer/launcher.py", line 148, in launch
self.proc = subprocess.Popen( # type: ignore
File "/usr/lib/python3.9/subprocess.py", line 951, in __init__
self._execute_child(args, executable, preexec_fn, close_fds,
File "/usr/lib/python3.9/subprocess.py", line 1823, in _execute_child
raise child_exception_type(errno_num, err_msg, err_filename)
OSError: [Errno 8] Exec format error: '/home/pi/.local/share/pyppeteer/local-chromium/588429/chrome-linux/chrome'
'/home/pi/.local/share/pyppeteer/local-chromium/588429/chrome-linux/chrome'
をfileコマンドで確認すると、64bit用のchromeを実行しようとしていることがわかります。
$ file '/home/pi/.local/share/pyppeteer/local-chromium/588429/chrome-linux/chrome'
/home/pi/.local/share/pyppeteer/local-chromium/588429/chrome-linux/chrome:
ELF 64-bit LSB shared object, x86-64, version 1 (SYSV),
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, not stripped
任意のChrome実行ファイルを指定できるオプションがあるか調べると、executablePath
キーワードに指定が無いと上記のパスが設定されるようでした。つまり、executablePathにラズパイのchromiumのパスを指定すれば良さそうですね。
from pyppeteer import launch
import asyncio
async def crawling():
options = {
"headless": False, # 非ヘッドレスモード(起動をわかりやすくする)
"executablePath": "/usr/bin/chromium-browser" # 実行ファイル指定
}
browser = await launch(options) # Chroniumをヘッドレスモードで起動
page = await browser.newPage()
await page.goto('https://google.com')
asyncio.get_event_loop().run_until_complete(crawling())
ちなみに、PyppeteerはJavaScriptパッケージPuppeteerを移植したものなので、Puppeteerについて調べるとよく出てくるかもしれません。実際、Puppeteerで実行ファイル指定する方法なら書いてありました...
Requests-HTML
最後に、Requests-HTML
についての設定方法です。
Requests-HTMLは、前述のPyppeteer
やBeautiful Soup
などをラップしているので、クローリングからスクレイピングまでこれ一つで行うことができます。
from requests_html import HTMLSession
session = HTMLSession()
res = session.get("https://google.com")
# URLとtitleを表示
# res.html.findはこの時点ではrequests.Response.content(静的情報のみ)から検索する
print(res.html.url, res.html.find('title')[0].text)
res.html.render() # pyppeteerを使用してサイトにアクセス -> 動的情報取得
print(res.html.find('title')[0].text) # 動的情報から検索
from requests_html import AsyncHTMLSession
asession = AsyncHTMLSession()
async def static_crawling():
return await asession.get('https://google.com')
async def dynamic_crawling():
resp = await asession.get('https://google.com')
await resp.html.arender() # 非同期版render
return resp
resp = asession.run(static_crawling, static_crawling)[0] # 複数サイトを非同期クローリング可
print(resp.html.url, resp.html.find('title')[0].text)
resp = asession.run(dynamic_crawling, dynamic_crawling)[0] # 複数サイトを非同期クローリング可
print(resp.html.url, resp.html.find('title')[0].text)
ライブラリバージョン
requests-html == 0.10.0
エラーと解決法
ラズパイにおいてrender
やarender
を実行するプログラムでは、エラーが発生しました。pyppeteer
を内部に使っているので、同様の原因でエラーが発生しています。
Traceback (most recent call last):
File "/home/pi/test.py", line 8, in <module>
res.html.render() # pyppeteerを使用してサイトにアクセス -> 動的情報取得
File "/home/pi/.local/lib/python3.9/site-packages/requests_html.py", line 586, in render
self.browser = self.session.browser # Automatically create a event loop and browser
File "/home/pi/.local/lib/python3.9/site-packages/requests_html.py", line 730, in browser
self._browser = self.loop.run_until_complete(super().browser)
File "/usr/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
return future.result()
File "/home/pi/.local/lib/python3.9/site-packages/requests_html.py", line 714, in browser
self._browser = await pyppeteer.launch(ignoreHTTPSErrors=not(self.verify), headless=True, args=self.__browser_args)
File "/home/pi/.local/lib/python3.9/site-packages/pyppeteer/launcher.py", line 307, in launch
return await Launcher(options, **kwargs).launch()
File "/home/pi/.local/lib/python3.9/site-packages/pyppeteer/launcher.py", line 148, in launch
self.proc = subprocess.Popen( # type: ignore
File "/usr/lib/python3.9/subprocess.py", line 951, in __init__
self._execute_child(args, executable, preexec_fn, close_fds,
File "/usr/lib/python3.9/subprocess.py", line 1823, in _execute_child
raise child_exception_type(errno_num, err_msg, err_filename)
OSError: [Errno 8] Exec format error: '/home/pi/.local/share/pyppeteer/local-chromium/588429/chrome-linux/chrome'
つまり、Requests-HTML内でpyppeteerを呼び出すときのブラウザ指定があればいいのですが、受け付けるようになっていませんでした。
解決法その1
Requests-HTMLのコードを、ブラウザ実行ファイル設定も受け付けるように少し書き換えてしまいましょう。ライブラリのrequests_html.py
(上記エラー文が出現した場合であれば、/home/pi/.local/lib/python3.9/site-packages/
内)のBaseSession
クラスの定義を以下のように変更すれば良いと思います。
class BaseSession(requests.Session):
""" A consumable session, for cookie persistence and connection pooling,
amongst other things.
"""
def __init__(self, mock_browser : bool = True, verify : bool = True,
- browser_args : list = ['--no-sandbox']):
+ browser_args : list = ['--no-sandbox'], browser_path : str = None):
super().__init__()
# Mock a web browser's user agent.
if mock_browser:
self.headers['User-Agent'] = user_agent()
self.hooks['response'].append(self.response_hook)
self.verify = verify
self.__browser_args = browser_args
+ self.__browser_path = browser_path
def response_hook(self, response, **kwargs) -> HTMLResponse:
""" Change response enconding and replace it by a HTMLResponse. """
if not response.encoding:
response.encoding = DEFAULT_ENCODING
return HTMLResponse._from_response(response, self)
@property
async def browser(self):
if not hasattr(self, "_browser"):
- self._browser = await pyppeteer.launch(ignoreHTTPSErrors=not(self.verify), headless=True, args=self.__browser_args)
+ self._browser = await pyppeteer.launch(ignoreHTTPSErrors=not(self.verify), headless=True, args=self.__browser_args, executablePath=self.__browser_path)
return self._browser
そして、先の例文のHTMLSession()
やAsyncHTMLSession()
に、キーワード引数browser_path
を"/usr/bin/chromium-browser"
として指定するとうまく機能するでしょう。
解決法その2
ライブラリを書きかえることが抵抗がある人に、もう一つの解決法を提示します。
それは、chrome実行ファイルを置き換えるということです。pyppeteer
は、実行時にchromeをダウンロードします。上記エラー文から、/home/pi/.local/share/pyppeteer/local-chromium/588429/chrome-linux/chrome
がそれに該当することがわかります。
この実行ファイルをラズパイのchromiumに置き換えましょう。
以下のコマンドを実行すればいいでしょう(バージョン更新などでパスは変化する可能性があります)。
$ rm /home/pi/.local/share/pyppeteer/local-chromium/588429/chrome-linux/chrome
$ ln -s /usr/bin/chromium-browser /home/pi/.local/share/pyppeteer/local-chromium/588429/chrome-linux/chrome
こうすれば、ライブラリはそのままで機能すると思います。先の例文で言うと、プログラムはそのままでいいです。
まとめ
快適にクローリング・スクレイピング生活を送りましょう!