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


はじめに

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

概要

requests-htmlでスクレイピングし、slackにメッセージを飛ばす仕組みを実装します。
スクレイピング対象のページが動的なページのためBeautiful Soupが使えず、requests-htmlを使用しました。
今回、個人利用の目的で
1. yahooニュースの東京都のコロナ関連情報ページをスクレイピング
2. 表示されている3件のニュースのタイトルと記事urlを取得する
3. 取得結果をslackの任意のチャンネルに投稿する
という仕組みを実装します。

環境/使用する技術

  • 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のみサポートしているため

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

├── Dockerfile
├── docker-compose.yml
└── src
    └── checkNews.py // スクレイピングするファイル

Dockerfile

Dockerfileを書きます。

touch 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は以下とします。

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を使うのでこちらに沿って設定を行ってください。

スクレイピングするソースコード

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

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しました。

以下参考先サイトさまから引用させていただきます。

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をクリックするとちゃんと記事のページを開くことができました。

参考サイト

  • pyppeteer.errors.BrowserErrorの対応策

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

  • requests-htmlについて