粗大メモ置き場

個人用,たまーに来訪者を意識する雑記メモ

SLAM_HUBさんのTweetをスクレイピングしてmarkdown資料(Marpスライド)形式にまとめる

勢いで作りましたがまだ@slam_hubさんの許可をもらってないので場合によっては後で消します><

許諾をいただきました。どうもありがとうございます!

概要

  • 特定のTwitterアカウントからTweetを抽出
  • YoutubeArxivのURLから画像やタイトルを抽出
  • 整形してMarkdown(Marpスライド形式)にて吐き出す
  • 作業環境:Python3.7 on Windows10,+ Docker環境

こうなる

f:id:ossyaritoori:20200731221841p:plain
Marpを用いたMarkdownスライド形式です。勝手に作ってごめんなさい。

工程

  • Tweet情報を抽出
  • リンクのURLからコンテンツを抽出
  • データ選別など後処理
  • 整形,文章作成




Twitter情報を抽出

TwitterからTweetスクレイピングします。ざっと思いつく手法は以下の通り。

基本的には最近面倒になったTwitter APIの申請が必要ですが,いくつか抜け道もあるようです。

ここでは以下の記事で取得したツイート群を使用します。

ossyaritoori.hatenablog.com

このTwitterScraperで抽出したjsonファイルは辞書型の要素を詰めたリストになります。(長いので詳細を御覧ください)

{'has_media': True,
 'hashtags': [],
 'img_urls': ['https://pbs.twimg.com/media/Ec2t_Y-UcAAYEyM.png'],
 'is_replied': True,
 'is_reply_to': False,
 'likes': 26,
 'links': ['https://arxiv.org/abs/2004.01793'],
 'parent_tweet_id': '',
 'replies': 1,
 'reply_to_users': [],
 'retweets': 9,
 'screen_name': 'slam_hub',
 'text': '画像からの物体方向推定を,self-supervisedに学習する枠組みを提案.画像から3次元方向とスタイル特徴量を抽出し,それらの潜在変数を元に幾何学的変換を行うGenerator(GAN)を用いて学習.損失には一貫性と水平対称性を利用し,教師あり学習に匹敵する性能を達成.\nhttps://arxiv.org/abs/2004.01793\xa0pic.twitter.com/Qr4LM3uTxA',
 'text_html': '<p class="TweetTextSize TweetTextSize--normal js-tweet-text tweet-text" data-aria-label-part="0" lang="ja">画像からの物体方向推定を,self-supervisedに学習する枠組みを提案.画像から3次元方向とスタイル特徴量を抽出し,それらの潜在変数を元に幾何学的変換を行うGenerator(GAN)を用いて学習.損失には一貫性と水平対称性を利用し,教師あり学習に匹敵する性能を達成.\n<a class="twitter-timeline-link" data-expanded-url="https://arxiv.org/abs/2004.01793" dir="ltr" href="https://t.co/rEGIDLI41f" rel="nofollow noopener" target="_blank" title="https://arxiv.org/abs/2004.01793"><span class="tco-ellipsis"></span><span class="invisible">https://</span><span class="js-display-url">arxiv.org/abs/2004.01793</span><span class="invisible"></span><span class="tco-ellipsis"><span class="invisible">\xa0</span></span></a><a class="twitter-timeline-link u-hidden" data-pre-embedded="true" dir="ltr" href="https://t.co/Qr4LM3uTxA">pic.twitter.com/Qr4LM3uTxA</a></p>',
 'timestamp': '2020-07-14T03:02:09',
 'timestamp_epochs': 1594695729,
 'tweet_id': '1282872986467397633',
 'tweet_url': '/slam_hub/status/1282872986467397633',
 'user_id': '1244129482132209664',
 'username': 'SLAM-Hub',
 'video_url': ''}

今回使うのは主に以下の3つになります。

  • text:ツイートの本文
  • link:ツイートに紐付けられたメディアのURL(Youtube, Aixivなど)
  • img_url:ツイートに直接貼り付けられた画像のURL

ここから,スライド形式の資料のタイトル,本文,説明画像を1セットにして資料を集めていくというのが今回の流れです。

コンテンツ作成

先程抽出したデータからサーベイ資料に載せるコンテンツを作成していきます。

  • タイトル≒論文題目
  • テキスト≒ツイッターの説明テキスト
  • サムネ画像≒代表的な画像

の3つが必要と考えます。

arxivの論文情報をスクレイピング

最も多いリンクはarxivのURLです。ここからタイトルを抽出します。

下記に紹介されているarxiv APIを用います。

PythonでarXiv APIを使って論文情報取得、PDFダウンロード | note.nkmk.me

str形式で与えられるarxivid(例:"110.680”)を抽出すれば以下のようにタイトルを得られます。

import arxiv
# get arxiv title
arxiv.query(id_list=[arxivid])[0]["title"]

linkのURLからIDを抽出します。

  • スラッシュでsplitして後ろを抽出
  • .pdfが混じっている場合があるので非数字を除去
    • 番号の最後に英字が入る場合があるので先頭だけを見て判断
# .pdfが混じっている時がある。数字だけを抽出するようにする。
arxivid_ = 'https://arxiv.org/abs/1910.14139.pdf'.split('/')[-1]
ids = [s for s in arxivid_.split('.') if s[0].isdigit()]

arxivid__=""
for i in range(len(ids)):
    arxivid__ += ids[i]+"."
arxivid = arxivid__[:-1]

途中の数値部分のみを抜き出すロジックは以下のサイトを参考にしました。 regex - How to extract numbers from a string in Python? - Stack Overflow

Youtubeスクレイピング

同様に多いlinkはyoutubeのリンクです。ここからはサムネイル画像とタイトルを抽出します。

Youtubeにアクセスして情報を抜き出すのは以前APIを取得してやっていましたが,APIの管理は正直面倒です。

今回は以下に示すyoutube-dlというAPIなしで使えるコマンドラインツールを用います。

github.com

導入はpipで問題なく入れられます。

pip install youtube-dl

コマンドラインツールなので,subprocess.check_outputを用いてコマンドライン出力を取得します。

import subprocess
imgurl = subprocess.check_output("youtube-dl --get-thumbnail \""+URL +"\"", shell=True)
title = subprocess.check_output("youtube-dl --get-title \""+ URL +"\"", shell=True)

帰ってくるURLの文字列には時々特殊文字が入っているのでエスケープが必要です。

まとめコード

  • 辞書型の要素を格納するリストを作る。
  • タイトル,本文,画像URLをそれぞれ加えて行く。

例外で画像だけがある場合もあったのでその時はタイトルは適当にして処理しました。

長いので詳細をクリックして見てください。

import arxiv
import subprocess

docdics = []
for dics in sdic:
    ddic = {}
    title = ""
    imgurl = ""    
    if dics['links']:# if has link
        # linkの最後尾をタイトルにする
        title = dics['links'][0].split('/')[-1]
        # Youtubeだった場合
        if 'youtu' in dics['links'][0]: # if it is youtube link
            imgurl = subprocess.check_output("youtube-dl --get-thumbnail \""+dics['links'][0] +"\"", shell=True)
            title = subprocess.check_output("youtube-dl --get-title \""+ dics['links'][0] +"\"", shell=True)
        # Arxivだった場合
        if 'arxiv' in dics['links'][0]:
            arxivid_ = dics['links'][0].split('/')[-1]
            ids = [s for s in arxivid_.split('.') if s.isdigit()]

            arxivid__=""
            for i in range(len(ids)):
                arxivid__ += ids[i]+"."
            arxivid = arxivid__[:-1]
            title = arxiv.query(id_list=[arxivid])[0]['title']
            if dics['img_urls']:
                imgurl = dics['img_urls'][0]
    # linkはないけど画像はある時
    elif dics['img_urls']:
        imgurl = dics['img_urls'][0]
        title = "image only"
    
    ddic['id'] = dics['tweet_id']
    ddic['title'] = title
    ddic['imgurl'] = imgurl
    ddic['text'] = dics['text']
    docdics.append(ddic)

データ後処理

ここからはヒューリスティックな処理が続きます。

  • 広報ツイートなどをフィルタリングする
  • youtube-dlが返す文字がbyte文字列なので補正する(最初からやってればよかった)

広報ツイートをフィルタリングする

LinkのURLがあったとしても必ずしもサーベイ紹介Tweetとは限りません。(そらそうだ) 広報ツイートなどと区別するために,特定のキーワードを含んでいるかどうかでフィルタリングします。

# 広報ツイートが紛れ込んでいる。URLか本文のキーワードで落とすか。"可能","提案","実現"などのワードのどれかがはいっていないなら弾く。
filterwords = ["可能","提案","実現","推定","定式","最適化","生成","評価"]
for docs in docdics:
    if not any(wd in docs["text"] for wd in filterwords):
        print(docs["text"])# テキスト閲覧

byte文字列をstrに変換

これは完全に私個人のミスですが,youtube-dlで得られる文字列がbyte文字列なのでこれをstrにデコードします。

# Encode to utf8
# string byteの混じった文字をstringに統一する
# type(b'abcd') == bytes を用いる

for docs in docdics:
    for dd in docs:
        if type(docs[dd]) == bytes:
            # .decode()でデコード

まとめコード

他にも, - タイトルの文字列に改行コードがあったのでスペースに変換

などの処理を行いました。

## filtering
filterwords = ["可能","提案","実現","推定","定式","最適化","生成","評価"]
filtereddics=[]
for docs in docdics:
    if any(wd in docs["text"] for wd in filterwords):
        fdocs = docs
        # decode bytes
        for dd in fdocs:
            if type(fdocs[dd]) == bytes:
                fdocs[dd]=fdocs[dd].decode()
        # Remove \n from title
        fdocs["title"] = fdocs["title"].replace("\n"," ",10)
        
        filtereddics.append(fdocs)

.mdファイルに書き込み

markdown形式で最終的にまとめますがせっかくなのでMarp for vscodeで表示できるスライド形式に変換します。

ossyaritoori.hatenablog.com

  • 改行してスライド区切り---を入力
  • タイトル,本文を入力
  • 画像を右端40%のfit形式で挿入

これを実行するコマンドは,

mkdocfile = open("survey.md","w+",encoding='utf-8')

for mkdics in filtereddics:
    imgurl=mkdics["imgurl"]
    if imgurl:
        if imgurl[-1] == "\n":
            imgurl = imgurl[:-1]
    onepagestring = "\n---\n"+"## " + mkdics["title"]\
    + "\n" + mkdics["text"]\
    + "\n ![bg right:40% fit]("+imgurl+")\n"
    mkdocfile.write(onepagestring)
    
mkdocfile.close()

以上!

Marpに落とし込む際は以下の記述を文頭に置きます。

---
marp: true
header: 'from @slam_hub'

size: 16:9
paginate: true
---

f:id:ossyaritoori:20200731221953p:plain
画像がないツイートはこのようにちょっとレイアウトが変わります。

TODO・改善点

- 権限とかの問題(なぜ記事を先に書いた)

ご意見・感想お待ちしております。