RubyのNokogiriでスクレイピングしてSlackに通知&Qiita記事更新


わたモテ13巻「遠足編」発売記念 もこっちとくっつくのは誰だ!?選手権 | 電子書籍ストア-BOOK☆WALKER
が目に入ってしましい、一時間ごとのリアルタイム更新では目が離せない!
という言い訳を得まして。

今回はRubyでスクレイピングしてSlackで監視するとともに、
なんとなくAPIを使いQiitaの記事の更新を試してみることにしました。

7月29日までの期間限定デス。

動作環境

  • docker ruby:latest
  • ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux]
  • nokogiri (1.8.4)
  • slack-incoming-webhooks (0.2.0)
  • qiita (1.3.5)

成果物

Slack通知

普通にPOSTするとURLが多すぎて展開されませんでした。
attachmentのtextでもURL画像が展開されず、authorとimageで無理やり2つの画像を詰め込むように。
たくさん画像を表示させたいときが課題ですね…

Qiita

RubyのNokogiriでスクレイピングしてSlackに通知&Qiita記事更新 - Qiita
コピーしたprivate記事

コード

require 'nokogiri'
require 'open-uri'
require 'qiita'
require 'slack/incoming/webhooks'


# Qiita投稿前に書き換えるものはグローバル変数にしておこう(悪手)
$incoming_url  = 'secret'
$qiita_token   = 'secret'
$qiita_item_id = '9f34f79aee34f5fc56ed'

def scraping()
    charset = nil
    url = 'https://bookwalker.jp/ex/feature/watamote-vote/'
    html = open(url) do |f|
        charset = f.charset
        f.read
    end

    members = []
    doc = Nokogiri::HTML.parse(html, nil, charset)
    doc.css('.vote-box').each do |vote|
        members.push({
            rank: url + vote.css('.rank-box>img').first.attributes['src'].value,
            name: vote.css('.border').first.attributes['alt'].value,
            icon: icon = url +  vote.css('.border').first.attributes['src'].value,
        })
    end
    members
end

def makeAttachments(members)
    attachments = []
    members.each do |member|
        attachments.push({
            author_name: member[:name],
            image_url: member[:icon],
            author_icon: member[:rank],
        })
    end
    attachments
end

def postSlack(attachments)
    incoming_url = $incoming_url
    channel      = '#hitorigoto'
    username     = 'bot'
    slack = Slack::Incoming::Webhooks.new incoming_url, channel: channel, username: username, attachments: attachments
    slack.attachments = attachments
    slack.post "もこっちとくっつくのは誰だ!?選手権\nhttps://bookwalker.jp/ex/feature/watamote-vote/"
end

def postQiita(body)
    title = 'RubyのNokogiriでスクレイピングしてSlackに通知&Qiita記事更新'
    client = Qiita::Client.new(access_token: $qiita_token)
    client.patch('/api/v2/items/' + $qiita_item_id, title: title, body: body)
end

def makeBody(members)
    ranking = <<~EOT
    | ランク | エントリー |
    |:-:|:-:|
    EOT
    members.each do |member|
        ranking = ranking + "|<img src=\"#{member[:rank]}\">|<img src=\"#{member[:icon]}\">|\n"
    end
    ranking

    client = Qiita::Client.new(access_token: $qiita_token)
    body = client.get_item($qiita_item_id).body['body']
    body.gsub(/(?<=\*{10}\n).*/m, ranking)
end


# rubyはメソッドを先に宣言しておかないといけない
members = scraping()

attachments = makeAttachments(members)
postSlack(attachments)

body = makeBody(members)
#p body
res = postQiita(body)
#p res

rubyは初心者ですが、コピペでなんとかなりました。
ハッシュの文字列とシンボルはショートハンド含めて混乱のもとですね。

当初はこの記事まるごとコードに持たせるつもりでしたが、記事取得の置換で書き換えるようにしました。

参考記事

更にややこしいことに、HashのKeyにSymbolを使う場合、KeyとValueの紐付けに => (ハッシュロケット)を使わず以下のようなシンプルな表記が可能になります。

記載はシンプルになって見やすいのですが、この表記法を知らない人には混乱のタネになります。また、あくまでも紐付けの表記が変わるだけで、値を取り出す際のリテラルは変わらないことにご注意ください。

ツラミ!


mがマルチラインモードじゃないのですねえ。
じゃあ^$がどうなるかというと、普段から各行を表し、ファイル先頭・末尾は\A\z(\Z)を使うようです。
わざわざmつかわないほうが混乱しないのかも。

こういうのもあるらしい。Rubyはなかなか深そうですね…





cronがrequire cannot load such fileエラーを吐いていので、crontabGEM_HOME="/usr/local/bundle"を追加しました。

知見

QiitaのPatchは差分更新

APIでの更新は、毎回タグや限定公開状況も含め、完璧な記事パラメーターにしないとダメだと思っていました。
しかし、渡したパラメーターだけが更新され、渡されなかったものは最初の投稿時に決めればプログラム側で持つ必要がないことがわかりました。
(タグパラメーターの渡し方がわからずずっとbad requestでてたので助かりました)

ただし、タイトルと本文は必須

ここでかなり詰まったのですが、JSONにはtitlebodyを含めなければ、Bad requestになるようです。
更新したいものは本文だけだったので、かなり困りました。
APIを見ても、必須指定の情報を見いだせなかったです。(雑に見ていたので見落としかもしれません(必須|必要で検索してみたけど…))

無変更なら非更新

毎回bodyとtitleを渡す必要がありますが、もともとの内容と差異がなければ更新自体が行われませんでした。
ちゃんと動いているか不安になる場合もあるかもしれませんが、
履歴が無駄に汚れないので良いです。
これも差分更新という言い方であってるかな?

Qiitaは直リン不可

imgタグで画像を表示していますが、投稿時するとQiita側に自動でアップロードされるようです。
もともとのsrc属性値はdata-canonical-srcとして保存されるようです。

多分アップロードがスマート

画像を含む更新を頻繁にしていれば、月のアップロード上限に引っかかるのではないか?
と不安になりましたが、画像以外の部分を多少変えた更新では
実際の画像src = https://camo.qiitausercontent.com/~
が不変でしたので、再度アップロードせずに前回の画像を使いまわしてくれている気がします。
ただし、それだけで枠消費を回避しているのかはわかりませんが…要経過観察。

関係ない疑問

限定共有投稿内のQiitaリンクやメンションは通知されるのかしらん。

private記事なら↓↓にランキングが挿入・更新される予定(これランキングほぼ不動のやつや)