LoginSignup
3
2

More than 3 years have passed since last update.

僕「ドラ◯もん〜!Qiitaのトレンド記事を自動で取得したいよぉ〜〜!」

Last updated at Posted at 2020-02-17

しょうがないなぁ僕くんは〜。

タリラタッタラ~!
ドラ◯もん「スクレイピングぅ〜!」
僕「スクレイピング?」

ドラ◯もん「この技術があれば、Webページの欲しい情報を取得できるんだぁ。」
僕「えっ!?すごいや!早くつかわs」
ドラ◯もん「今回は僕くんが作るんだよ。」

僕「...えっ?」
僕「な、なんで僕が作らないといけないんだ...?だって、そこに既製品があるじゃn」
ドラ◯もん「嫌ならこの話はなかったことにします。」
僕「よ〜し!!やる気がみなぎってきた!!!!」

・・・

僕「とりあえず、Atom入れて、Python3も入れたよ!」
ドラ◯もん「よし、そうしたら早速ソースを書いてみよう」

目的

SlackBotを用いて、Qiitaのトレンド記事を動的取得するよ。

事前準備

Slackのアカウント, およびSlackBotを作っておくこと。
Herokuのアカウントを作っておくこと。
ChromeDriverをインストールしておくこと。
Chromeブラウザがない人は、インストールしておくこと。

上記4点は別サイトさんを参考にしてね。

環境

  • Mac
  • Atom
  • Python3.7
  • selenium
  • ChromeDriver
  • Heroku
  • Slack
  • Chrome
  • Git

・・・

ドラ◯もん「と、その前に。」
ドラ◯もん「スクレイピングする際の注意点〜!」
僕「注意点?」
ドラ◯もん「そう、そもそもにスクレイピングは、どういう原理で行うか知っているかい?」
僕「えっと、プログラムが動いて、対象のWebページにアクセスして、」
ドラ◯もん「そう!Webページにアクセスしないといけない。アクセスすると、当然サーバに(大なり小なり)負荷がかかるよね?」
僕「ウンウン。」
ドラ◯もん「つまり、やたらめったらスクレイピングをしてしまうと、以下のようなことが起こる可能性があるんだ。」

  1. スクレイピングでめちゃくちゃアクセスする。
  2. サーバの負荷がかかる。(閾値を超える)
  3. サービスが停止する。
  4. スクレイピングをした人に対して損害賠償が発生する(可能性がある)

ドラ◯もん(君はここで、「またまたぁ、大袈裟なんだからぁ。」という)
僕「またまたぁ、大袈裟なんだからぁ。」

ドラ◯もん「上にリンク貼ったから、ちゃんと目を通しておくんだよ。」
僕「わ、わかったよぉ。」
・・・

プロジェクトツリー

./slack-bot-scraping
├── Procfile
├── __pycache__
├── plugins
│   ├── __init__.py
│   ├── __pycache__
│   └── my_slackbot.py
├── requirements.txt
├── run.py
└── slackbot_settings.py

作っていこう!

・・・

僕「ええと、まずは、run.pyに、こう記述して、よし、これでどう?」

run.py
from slackbot.bot import Bot

def main():
    bot = Bot()
    bot.run()

if __name__ == "__main__":
    print("slack bot start")
    main()

ドラ◯もん「うん、いいと思うよ。」
ドラ◯もん「今回はHerokuにソースをデプロイして、動かすからそっちの準備もしよう。」

僕「どんなファイルが必要になってくるの?」
ドラ◯もん「主に3つのファイルが必要だよ。」

  1. Procfile
  2. slackbot_settings.py
  3. requirements.txt

ドラ◯もん「1つずつ軽く紹介しようか。」

ProcFile

worker: python3 run.py # run.pyをpython3で動かすよって記述

slackbot_settings.py

slackbot_settings.py
API_TOKEN = "XXXX"      # 使用したいAPIのトークン値
DEFAULT_REPLY = "Title" # タイトル(適当で良いと思う)
PLUGINS = ["plugins"]   # プラグインとして読込たいディレクトリに指定

requirements.txt

requirements.txt
pythonで使用したいパッケージが記述されてるよ
選定するのが面倒なら、インストールしているパッケージを全部書いちゃうのをオススメするよ
package_name==version

absl-py==0.7.1
astor==0.8.0
beautifulsoup4==4.7.1
certifi==2019.6.16
chardet==3.0.4
・
・
・

ドラ◯もん「上記3点のファイルを作成できたら、次に処理のソースを書いてみよう。」
僕「おー!」

plugins/my_slackbot.py

plugins/my_slackbot.py
from slackbot.bot import respond_to
from slackbot.bot import listen_to
from slackbot.bot import default_reply
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

import random
import sys
import os

class Scrapy(object):
    def __init__(self):
        options = webdriver.ChromeOptions()
        options.binary_location = 'Chromeアプリのパスを指定してね'
        options.add_argument('--headless')

        self.driver = webdriver.Chrome(options=options)

        self.result = []

    def login(self):
        login = "https://qiita.com/login?redirect_to=%2F"
        user = os.environ['USER']
        password = os.environ['PASSWORD']

        self.driver.get(login)
        attribute = self.driver.find_element_by_name("identity")
        attribute.clear()
        attribute.send_keys(user)

        attribute = self.driver.find_element_by_name("password")
        attribute.clear()
        attribute.send_keys(password)

        self.driver.find_element_by_name("commit").click()

        return True

    def get_trend_data(self, url):
        self.driver.get(url)
        WebDriverWait(self.driver, 5).until(EC.presence_of_element_located)
        trends = self.driver.find_elements_by_class_name("tr-Item_body")
        for trend in trends:
            data = []
            items = trend.find_elements_by_tag_name("a")
            href = items[0].get_attribute("href")
            title = items[0].text
            data.append(title)
            data.append(href)

            self.result.append(data)

        self.driver.quit()

        return self.result

def common(url, message):
    item_list = []
    sc = Scrapy()
    sc.login()
    results = sc.get_trend_data(url)
    for v in results:
        for item in v:
            item_list.append(item)

    for item in item_list:
        message.reply(item)

@default_reply()
def default(message):
    message.reply("Qiitaのトレンド情報を取得するよ。")
    message.reply("「1日」のトレンドは、「1日」「一日」「いちにち」で取得できるよ。")
    message.reply("「週間」のトレンドは、「週間」「しゅうかん」で取得できるよ。")
    message.reply("「月間」のトレンドは、「月間」「げっかん」で取得できるよ。")

@respond_to('1日*')
@respond_to('一日*')
@respond_to('いちにち*')
def day(message):
    url = "https://qiita.com"
    common(url, message)

@respond_to('週間*')
@respond_to('しゅうかん*')
def week(message):
    url = "https://qiita.com/?scope=weekly"
    common(url, message)

@respond_to('月間*')
@respond_to('げっかん*')
def mon(message):
    url = "https://qiita.com/?scope=monthly"
    common(url, message)

僕「できた〜〜〜!」
ドラ◯もん「めげないでよく作ったね!えらい!」

ドラ◯もん「長くなっちゃったから小分けして説明していこうか。」

class Scrapy(object):

plugins/my_slackbot.py
class Scrapy(object):
    # ここでは使用するChromeDriverの設定を行なっているよ
    def __init__(self):
        options = webdriver.ChromeOptions()
        options.binary_location = 'Chromeアプリのパスを指定してね'
        # ここではハンドレスの設定をしているよ
        # この設定をしないで実行すると、Chromeブラウザが立ち上がって、処理を実行してしまうから気をつけてね
        options.add_argument('--headless')

        self.driver = webdriver.Chrome(options=options)

        self.result = []

    # Qiitaにログインする処理だよ
    def login(self):
        login = "https://qiita.com/login?redirect_to=%2F"
        # user, passwordはHeroku内でexportして使用可能にしているよ
        # 面倒ならここに直書きでもいいんじゃまいかな〜
        user = os.environ['USER']
        password = os.environ['PASSWORD']

        # ログイン画面にアクセスするよ
        self.driver.get(login)
        # "id=identity"の値を取得するよ
        attribute = self.driver.find_element_by_name("identity")
        # 取得した値を一度初期化するよ
        attribute.clear()
        # 初期化した値に`user`を埋め込むよ
        attribute.send_keys(user)

        # "id=password"の値を取得するよ
        attribute = self.driver.find_element_by_name("password")
        # 取得した値を一度初期化するよ
        attribute.clear()
        # 初期化した値に`password`を埋め込むよ
        attribute.send_keys(password)

        # name="commit"の値を取得して、クリックするよ
        self.driver.find_element_by_name("commit").click()

        # ログイン完了
        return True

    def get_trend_data(self, url):
        self.driver.get(url)
        # 5秒間処理を停止するよ
        WebDriverWait(self.driver, 5).until(EC.presence_of_element_located)
        # tr-Item_bodyという要素を取得するよ
        trends = self.driver.find_elements_by_class_name("tr-Item_body")
        for trend in trends:
            data = []
            # aタグを取得するよ
            items = trend.find_elements_by_tag_name("a")
            # この"href"がQiitaのトレンド記事のURLを示しているよ
            href = items[0].get_attribute("href")
            # 0番目に格納されている文字列を取得するよ(タイトル)
            title = items[0].text
            # 取得した各種情報をリストに格納してね
            data.append(title)
            data.append(href)
            # 記事のタイトル, トレンド記事のURLを格納した配列の出来上がり
            self.result.append(data)

        # Driverを閉じるよ
        self.driver.quit()
        # 取得結果を返すよ
        return self.result

def common(url, message):

plugins/my_slackbot.py
def common(url, message):
    # 結果を格納するリストを宣言するよ
    item_list = []
    # Scrapyオブジェクトを呼び出すよ
    sc = Scrapy()
    # ログイン処理の実行をするよ
    sc.login()
    results = sc.get_trend_data(url)
    # 2次元配列になっているので、展開してリスト(1次元配列)にするよ
    for v in results:
        for item in v:
            item_list.append(item)

    # listから情報を一つずつ取り出して、送信するよ
    for item in item_list:
        message.reply(item)

def default(message):

plugins/my_slackbot.py
# respond_toに該当しないメッセージが来たら、以下の内容をBotが送信してくれるよ
@default_reply()
def default(message):
    message.reply("Qiitaのトレンド情報を取得するよ。")
    message.reply("「1日」のトレンドは、「1日」「一日」「いちにち」で取得できるよ。")
    message.reply("「週間」のトレンドは、「週間」「しゅうかん」で取得できるよ。")
    message.reply("「月間」のトレンドは、「月間」「げっかん」で取得できるよ。")

# 1日, 一日, いちにち, から始まるメッセージを送信したら、Botは以下の処理を行うよ
@respond_to('1日*')
@respond_to('一日*')
@respond_to('いちにち*')
def day(message):
    url = "https://qiita.com"
    common(url, message)

# 週間, しゅうかん, から始まるメッセージを送信したら、Botは以下の処理を行うよ
@respond_to('週間*')
@respond_to('しゅうかん*')
def week(message):
    url = "https://qiita.com/?scope=weekly"
    common(url, message)

# 月間, げっかん, から始まるメッセージを送信したら、Botは以下の処理を行うよ
@respond_to('月間*')
@respond_to('げっかん*')
def mon(message):
    url = "https://qiita.com/?scope=monthly"
    common(url, message)

フロー

UserメッセージをBotに送信。
|
Userメッセージに該当キーワードはある?。
| |
y n
| |
| def default(message); 処理を行う。
|
|
ChromeDriverの設定をする。
|
Qiitaにログインする。
|
Qiitaのトレンド情報を取得する。
|
取得したトレンド情報をBotが送信する。

ドラ◯もん「あとはこのソースをHerokuにデプロイして、SlackBotがメッセージを送信してくれたら完成だ!」

Heroku下準備

ドラ◯もん「以下のURLにアクセスして、パッケージをダウンロードしよう!」
https://devcenter.heroku.com/articles/getting-started-with-python#set-up

ドラ◯もん「パッケージを展開したら、CLI上で、heroku login としよう。」

ドラ◯もん「そうすると、
heroku: Press any key to open up the browser to login or q to exit:
と表示されるので、q以外のキーを叩こう。」

ドラ◯もん「叩いたら、ブラウザに遷移して、Herokuへのログインを求められるよ。メールアドレスと、パスワードを入力して、ログインしよう。」

ドラ◯もん「CLI上では、
Logged in as メールアドレス
と表示されたら成功。」

ドラ◯もん「これで、HerokuとローカルPCとの連携ができたね。」

Gitリポジトリの初期化

ドラ◯もん(カレントデュレクトリはデプロイしたいディレクトリに指定しておこうね。)

git init
git add .
git commit -m "new files"

Herokuにアプリを作成しよう

ドラ◯もん「heroku create アプリの名前 コマンドを実行して、アプリを作成しよう!」
ドラ◯もん「heroku list コマンドを実行して、アプリの名前 があるのを確認してね。」

ドラ◯もん「git push heroku master コマンドを実行すると、PythonのソースコードがHerokuにプッシュされたよ。」

メッセージを投げてみよう

スクリーンショット 2020-01-16 15.18.26.png
僕「お〜〜〜!」
僕「一日」

F4697DCA-4958-4EC1-B029-ECDBA1EEB6F0_1_102_o.jpeg

僕「これで現在の1日のトレンドが取得できるのか〜!」
ドラ◯もん「週間、月間も同様に取得できるけど、今回は尺の都合でカットするよ。」

まとめ

Python3.7を用いて、特定のページから欲しい情報を取得することができました。
各サイトの規約や、サーバに対しての負荷も考慮してソースを組みましょう。
あまりにも負荷をかけたらDos攻撃とみなされ、処罰される可能性があります。
ぜひ、気をつけましょう。

頭の中の処理をそのまま記述したソースですが、
改善案や、指摘事項ありましたらコメントください。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2