dockerでpythonの開発環境を作成し、requests-htmlでのスクレイピング結果をslackに送信する 

# はじめに
スクレイピングを行うにあたりこちらの注意事項まとめを参考にさせていただきました。

https://qiita.com/nezuq/items/c5e827e1827e7cb29011

# 概要
requests-htmlでスクレイピングし、slackにメッセージを飛ばす仕組みを実装します。
スクレイピング対象のページが動的なページのためBeautiful Soupが使えず、requests-htmlを使用しました。
今回、個人利用の目的で
1. yahooニュースの東京都のコロナ関連情報ページをスクレイピング
2. 表示されている3件のニュースのタイトルと記事urlを取得する
3. 取得結果をslackの任意のチャンネルに投稿する 
という仕組みを実装します。
![yahoonews.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267471/10b98c2c-5f54-9609-1ce6-af95d1c27e6f.png)


# 環境/使用する技術
- MacOS Big Sur
- python 3.6
- スクレイピングするためのライブラリ requests-html
- Docker version 19.03.13(開発環境構築)
- docker-compose version 1.27.4(開発環境構築)
- slack webhook



# 開発環境構築
pythonのコードを書くための使い捨て開発環境を作成します。Dockerを使用します。
今回使用するイメージはpythonの3.6バージョンです。
※ requests-htmlがpython3.6のみサポートしているため

https://hub.docker.com/_/python


開発マシン(Mac)側では適当なディレクトリを掘ってソースコードを以下のように配置します。

```
├── Dockerfile
├── docker-compose.yml
└── src
    └── checkNews.py // スクレイピングするファイル
```
## Dockerfile
Dockerfileを書きます。

```
touch Dockerfile
```

Dockerfileは以下とします。

```dockerfile:Dockerfile
FROM python:3.6
WORKDIR /usr/src/pythonwork
RUN apt-get update && \
  pip install requests-html && \
  pip install pyppeteer && \
  pip install slackweb
```

## docker-compose.yml
docker-compose.ymlを作成します。

```
touch docker-compose.yml
```

docker-compose.ymlは以下とします。

```yml:docker-compose.yml
version: "3.8"
services:
  python:
    build: .
    volumes:
      - ./src:/usr/src/pythonwork
    tty: true
    working_dir: /usr/src/pythonwork
```

## コンテナの作成
コンテナを作成します。

```
docker-compose build
```
開発環境構築は以上です。

# webhookの利用設定
slackに投稿する際、Incoming webhookを使うのでこちらに沿って設定を行ってください。

https://slack.com/intl/ja-jp/help/articles/115005265063-Slack-%E3%81%A7%E3%81%AE-Incoming-Webhook-%E3%81%AE%E5%88%A9%E7%94%A8

# スクレイピングするソースコード
yahooの東京都のコロナニュースの件名と記事のurlを3件取得し、slackの#generalチャンネルに投稿します。

```python
from requests_html import HTMLSession
import slackweb
import re

news_url = "https://news.yahoo.co.jp/pages/article/covid19tokyo"
slackweb_url = "https://hooks.slack.com/services/xxxxxxxxxxxxxxxxxxxxx"

# セッション開始
session = HTMLSession()
r = session.get(news_url)

# HTMLを生成
r.html.render()

for num in range(1, 4):
    # スクレイピング
    article_title = r.html.find("#tab_1 > ul.dlpThumbLink > li:nth-child(" + str(num) + ") > a > span.dlpThumbText > span:nth-child(1)", first=True)
    article_title_text = article_title.text
    article_link = r.html.find("#tab_1 > ul.dlpThumbLink > li:nth-child(" + str(num) + ") > a", first=True)
    article_url = article_link.absolute_links

    # slackへ投稿
    slack = slackweb.Slack(url=slackweb_url)
    slack.notify(text=article_title_text + '\n' + re.sub("\{|\}|'", "", str(article_url)), channel="#general", username="covid19-news-bot", icon_emoji=":eyes:", mrkdwn=True)

```

# スクレイピングを実行する

コンテナを立ち上げ、コンテナ内に入ります。

```
docker-compose up -d
docker-compose exec python bash
```

checkNesw.pyを動かします。
初回実行ではヘッドレスブラウザをインストールします。[W:pyppeteer.chromium_downloader] start chromium download.

```
# python checkNews.py
```

## エラー：pyppeteer.errors.BrowserError: Browser closed unexpectedlyが出力される

```
root@353345a20664:/usr/src/pythonwork# python checkNews.py
[W:pyppeteer.chromium_downloader] start chromium download.
Download may take a few minutes.
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 108773488/108773488 [00:03<00:00, 32959625.14it/s]
[W:pyppeteer.chromium_downloader]
chromium download done.
[W:pyppeteer.chromium_downloader] chromium extracted to: /root/.local/share/pyppeteer/local-chromium/588429
Traceback (most recent call last):
  File "checkNews.py", line 15, in <module>
    r.html.render()
  File "/usr/local/lib/python3.6/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.6/site-packages/requests_html.py", line 730, in browser
    self._browser = self.loop.run_until_complete(super().browser)
  File "/usr/local/lib/python3.6/asyncio/base_events.py", line 488, in run_until_complete
    return future.result()
  File "/usr/local/lib/python3.6/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.6/site-packages/pyppeteer/launcher.py", line 306, in launch
    return await Launcher(options, **kwargs).launch()
  File "/usr/local/lib/python3.6/site-packages/pyppeteer/launcher.py", line 167, in launch
    self.browserWSEndpoint = get_ws_endpoint(self.url)
  File "/usr/local/lib/python3.6/site-packages/pyppeteer/launcher.py", line 226, in get_ws_endpoint
    raise BrowserError('Browser closed unexpectedly:\n')
pyppeteer.errors.BrowserError: Browser closed unexpectedly:
```


エラーメッセージで調べてみると、ヘッドレスブラウザを使うにはライブラリが不足しているようです。
こちらの記事を参考にpyppeteerに必要なライブラリをapt-get installしました。

https://medium.com/@ssmak/how-to-fix-puppetteer-error-while-loading-shared-libraries-libx11-xcb-so-1-c1918b75acc3

以下参考先サイトさまから引用させていただきます。
>sudo apt-get install gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget


コンテナにはrootログインしているのでsudoは省き、実行します。(※DockerfileのRUNに↓を書いておけば再発しません。その際は-yを忘れずに)

```
apt-get install gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
```


再度実行。

```
root@353345a20664:/usr/src/pythonwork# python checkNews.py
root@353345a20664:/usr/src/pythonwork#
```

slackに投稿できました。urlをクリックするとちゃんと記事のページを開くことができました。

![slack.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267471/539fe39f-6d7a-3641-8e96-b9d268cc02dd.png)




# 参考サイト
https://qiita.com/nezuq/items/c5e827e1827e7cb29011

- pyppeteer.errors.BrowserErrorの対応策

https://medium.com/@ssmak/how-to-fix-puppetteer-error-while-loading-shared-libraries-libx11-xcb-so-1-c1918b75acc3

https://qiita.com/shtnkgm/items/4f0e4dcbb9eb52fdf316

https://www.webfx.com/tools/emoji-cheat-sheet/

https://qiita.com/simonritchie/items/bb06a7521ae6560738a7


- Dockerのバージョンとdocker-composeのサポート対応表

https://matsuand.github.io/docs.docker.jp.onthefly/compose/compose-file/

- requests-htmlについて

https://pypi.org/project/requests-html/