Raspberry Pi 上でRequest-HTMLを用いてスクレイピングを行うdiscord botを作ったときにはまったあれこれ

# はじめに
本記事はRaspberry Pi 上にUbuntu20を入れ、dockerを構築しその中にスクレイピングを行うBOTの実行環境を作って動かすときにぶつかった問題に関してまとめたものである。

本記事に関して書いている本人がまだ初心者なため、間違っている部分やより効率の良い方法があるかもしれません。

解決したエラーは主に以下の３つ。

`OSError: [Errno 8] Exec format error: '/root/.local/share/pyppeteer/local-chromium/588429/chrome-linux/chrome' `

`RuntimeError: Cannot use HTMLSession within an existing event loop. Use AsyncHTMLSession instead.`

`RuntimeWarning: Enable tracemalloc to get the object allocation traceback`
# 目次
はじめに
目次
環境
1. requests-html の r.html.render() をした時のOS Error
2. discordのイベントループ内で実行した場合の RuntimeError
3. event loop 内でr.html.render() をした時に発生した RuntimeWarning:

# 環境
Raspberry Pi 4B+ 4GB
Ubuntu20.04
Docker version 19.03.8

docker内の環境
Python 3.9.4

pythonのパッケージのバージョン
discord.py        1.7.1
requests-html     0.10.0
nest-asyncio      1.5.1

# 1. requests-html の r.html.render() をした時のOS Error

## 問題
最初は実際にURLからデータを取得してウェブエンジンを用いて描画を行おうとした。
以下のようなコードを書き、実行したらエラーが発生した。

実行したコード

```python
from requests_html import HTMLSession

url = 'https://www.google.com/'
session = HTMLSession()
r = session.get(url)
r.html.render()
print(r.html.text)
```

発生したエラー

```
# docker exec -it <コンテナID> python3 opt/test1.py
Traceback (most recent call last):
  File "/root/opt/test1.py", line 6, in <module>
    r.html.render()
  File "/usr/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 "/usr/local/lib/python3.9/site-packages/requests_html.py", line 730, in browser
    self._browser = self.loop.run_until_complete(super().browser)
  File "/usr/local/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
    return future.result()
  File "/usr/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 "/usr/local/lib/python3.9/site-packages/pyppeteer/launcher.py", line 306, in launch
    return await Launcher(options, **kwargs).launch()
  File "/usr/local/lib/python3.9/site-packages/pyppeteer/launcher.py", line 147, in launch
    self.proc = subprocess.Popen(  # type: ignore
  File "/usr/local/lib/python3.9/subprocess.py", line 951, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/usr/local/lib/python3.9/subprocess.py", line 1821, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
OSError: [Errno 8] Exec format error: '/root/.local/share/pyppeteer/local-chromium/588429/chrome-linux/chrome'
```

## 原因
[こちら](https://teratail.com/questions/166849) が参考になった。
原因はpyppeteer がarm64版ではなくintel 64bit版をダウンロードしているらしい。
この問題はintelCPU上で環境を構築している人には出ないかもしれない。

## 解決法
別個にchromiumをダウンロードしてpyppeteerの参照先をダウンロードしたchromiumに向けるようにした。
Dockerfile のaptのインストールを行うモジュールにchromium を追加し(apt install chromium)、sedコマンドを用いてrequests-htmlライブラリのpyppeteer.launch()の部分を書き換えた。`docker exec -it <ID> /bin/bash` で入り、ライブラリの場所は` pip show requests-html`で特定した。

書き換えに用いたコマンド

```
sed -i -e "s@pyppeteer.launch(@pyppeteer.launch(executablePath='/usr/bin/chromium', @" /usr/local/lib/python3.9/site-packages/requests_html.py
```

buildの時にこのコマンドを実行するようにするといいかもしれない。

# 2. discordのイベントループ内で実行した場合の RuntimeError
##問題
今度はスクレイピングをdiscordのeventで行うために以下のようなコードを書き、実行したらエラーが発生した。

実行したコード

```python
import discord
from requests_html import HTMLSession

TOKEN = '' #discordのトークン
URL = 'https://www.google.com/'

client = discord.Client()

@client.event
async def on_ready():
    session = HTMLSession()
    r = session.get(URL)
    r.html.render()
    print(r.html.text)

client.run(TOKEN)
```

発生したエラー

```
 % docker exec -it <コンテナID> python3 opt/test2.py
Ignoring exception in on_ready
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/discord/client.py", line 343, in _run_event
    await coro(*args, **kwargs)
  File "/root/opt/test2.py", line 13, in on_ready
    r.html.render()
  File "/usr/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 "/usr/local/lib/python3.9/site-packages/requests_html.py", line 729, in browser
    raise RuntimeError("Cannot use HTMLSession within an existing event loop. Use AsyncHTMLSession instead.")
RuntimeError: Cannot use HTMLSession within an existing event loop. Use AsyncHTMLSession instead.
```

##原因

どうやらHTMLSession はevent loop内では使えないらしい。代わりにAsyncHTMLSession を使えとあるが、`import AsyncHTMLSession`に変えるだけではダメだった(そりゃそうだ)。

##解決法
結論としてはコードを以下のように修正した。ここでは問題解決の手順上r.html.render()をコメントアウトした。

```python
import discord
from requests_html import AsyncHTMLSession

TOKEN = '' #discordのトークン
URL = 'https://www.google.com/'

client = discord.Client()

@client.event
async def on_ready():
    session = AsyncHTMLSession()
    r = await session.get(URL)
    #r.html.render()
    print(r.html.text)

client.run(TOKEN)
```

await を付け足さないといけないらしい。付け足さなかった場合にはrにはコルーチンオブジェクトが帰り、r.html.text等でエラーを吐くらしい。

# 3. event loop 内でr.html.render() をした時に発生した RuntimeWarning:

## 問題

2. でsession.get() まではできるようになったがr.html.render() のコメントアウトを外すと以下のエラーが発生する。

```
% docker exec -it <コンテナID> python3 opt/test3.py
Ignoring exception in on_ready
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/discord/client.py", line 343, in _run_event
    await coro(*args, **kwargs)
  File "/root/opt/test3.py", line 13, in on_ready
    r.html.render()
  File "/usr/local/lib/python3.9/site-packages/requests_html.py", line 598, in render
    content, result, page = self.session.loop.run_until_complete(self._async_render(url=self.url, script=script, sleep=sleep, wait=wait, content=self.html, reload=reload, scrolldown=scrolldown, timeout=timeout, keep_page=keep_page))
  File "/usr/local/lib/python3.9/asyncio/base_events.py", line 618, in run_until_complete
    self._check_running()
  File "/usr/local/lib/python3.9/asyncio/base_events.py", line 578, in _check_running
    raise RuntimeError('This event loop is already running')
RuntimeError: This event loop is already running
/usr/local/lib/python3.9/site-packages/discord/client.py:350: RuntimeWarning: coroutine 'HTML._async_render' was never awaited
  pass
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
/usr/local/lib/python3.9/asyncio/base_events.py:1891: RuntimeWarning: coroutine 'BaseSession.browser' was never awaited
  handle = None  # Needed to break cycles when an exception occurs.
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
```

## 原因
原因はr.html.render() 。それ以上はわからなかった。

##解決法
[こちら](https://github.com/psf/requests-html/issues/330) が参考になった。
r.html.render() を r.html.arender() に変えるだけであった。

#まとめ
今回初めて記事を投稿したのでいろいろ至らない部分等があると思いますが、優しくご指摘いただけると幸いです。
ちゃんとドキュメント読むことって大切です...

#参考にしたサイト等
https://teratail.com/questions/166849
https://github.com/psf/requests-html/issues/330