さくらレンタルサーバーでスクレイピング定期実行 & DB 保存

さくらのレンタルサーバを使ってスクレイピングをする方法をまとめます。

eBay の商品情報を例としてページの要素を取得し、MySQL のデータベースに insert する一連の過程をまとめたいと思います。最後は CRON を使って定期実行させる設定も行います。

なので最低限 MySQL と CRON が使える「さくらのレンタルサーバ スタンダード」以上のプランを想定しています。

一応自分の忘備録としてまとめていますが、過程を追っていただければと思います。

  1. 実行環境の準備
  2. スクレイピングをしてみる
  3. データを保存する DB の作成
  4. Python からデータベースを操作する
  5. CRON で定期実行する

1. 実行環境の準備

まず始めるにあたってサーバー上に Python3 をインストールして仮想環境を作成します。

Python3 のインストール

さくらレンタルサーバのデフォルトの Python は Python2 なので Python3 を別途インストールする必要があります。下記の記事で手順を説明しています。

▶︎Python3 をインストールする方法

仮想環境の作成

Python3 のインストールが完了したら、任意の場所に仮想環境を作成します。

例として仮想環境「scraping_venv」を作成します。

% python3 -m venv scraping_venv
% 

仮想環境のパスに入り起動します。

% cd scraping_venv
% source bin/activate.csh
(scraping_venv) % 

3. スクレイピングをしてみる

では早速、requests モジュールを使ってページの情報を取得してみます。

requests のインストール

Web ページの取得に便利な requests をインストールするためコマンド「pip3 install requests」を実行します。

(scraping_venv) % pip3 install requests

インストールが完了したら Python3 を起動しますが、仮想環境を起動しているのでコマンド「python」で 起動できます。

(scraping_venv) % python
Python 3.9.0 (default, Jan  3 2021, 10:29:59) 
[GCC 4.2.1 20070831 patched [FreeBSD]] on freebsd9
Type "help", "copyright", "credits" or "license" for more information.
>>>

eBay ページの取得

requests をインポートし、requests.get() で URL「https://www.ebay.com/b/Video-Game-Consoles/139971/bn_320033」のソースコードを取得します。

>>> import requests
>>> r = requests.get('https://www.ebay.com/b/Video-Game-Consoles/139971/bn_320033')
>>> 

一旦変数 r に全部入ったので、r.text を実行するとソースコードが確認できます。

>>> r.text
'<!DOCTYPE html><!--[if IE 9]><html class="ie9" lang="ja"><![endif]--><!--[if gt IE 9]><!--><html lang="ja"><!--<![endif]--><head><style>\n    .font-marketsans body {\n        font-family: "Market Sans", Arial, sans-serif;\n    }\n</style><script>\n    (function() {\n        var useCustomFont = (\'fontDisplay\' in document.documentElement.style) ||\n                        (localStorage && localStorage.getItem(\'ebay-font\'));\n        if (useCustomFont) {\n            document.documentElement.classList.add(\'font-marketsans\');\n        }\n    })();\n</script><meta charset="utf-8"><link rel="'preconnect'" href="https://ir.ebaystatic.com" /><meta Property="og:description" Content="Shop eBay for great deals on ビデオゲームコンソール. You'll find new or used products in ビデオゲームコンソール on eBay. Free shipping on selected items." />

ソースコードを取得出来ることを確認できたので「exit()」を実行して一旦 Python のコンソールから出ます。

>>> exit() 
(scraping_venv) % 

Beautiful Soup で HTML 要素抽出

ページのソースを取得することはできたので、今度はそのソースコードから具体的な HTML の要素を抽出していきます。

HTML の取り扱いに便利な BeautifulSoup4 をインストールするためコマンド「pip3 install beautifulsoup4」を実行します。

(scraping_venv) % pip3 install beautifulsoup4

インストールが完了したら再度 Python3 を起動して BeautifulSoup と requestsをインポートします。

(scraping_venv) % python3
>>> from bs4 import BeautifulSoup
>>> import requests
>>> 

とりあえず eBay のページからサムネイル URL、商品名、商品ページ URL を取得したいと思います。

>>> r = requests.get('https://www.ebay.com/b/Video-Game-Consoles/139971/bn_320033')
>>> soup = BeautifulSoup(r.text,'html.parser')
>>> sitem = soup.find_all('li',class_='s-item') # クラス名が「s-item」の li 要素のリストを取得

Chrome のデベロッパーツールでページの構造を確認したところ、どうやらサムネイル URL、商品名、商品ページ URL は li 要素の中に含まれていて、その li 要素のクラス名が s-item だったので上記の様に取得しました。

取得した li 要素はリスト形式になっているので、for ループで各 li 要素からそれぞれの項目を抽出し、print していきます。

>>> for li in sitem: # 各 li 要素に対して処理を行う
...     img = li.find('img') # li 要素内の img 要素を取得
...     img_src = img.get('src') # img 要素内の src 属性を取得
...     name = li.find('h3') # li 要素内の h3 要素を取得
...     name_text = name.text # h3 要素内の文字列を取得
...     link = li.find('a') # li 要素内の a 要素を取得
...     link_href = link.get('href') # a 要素内の href 属性を取得
...     print(img_src)
...     print(name_text)
...     print(link_href)
... 

Enter を2度押下するとプログラムが実行されサムネイル、商品名、商品ページ URL が交互に表出されます。

... 
https://i.ebayimg.com/thumbs/images/g/Tl0AAOSwZpJgOul6/s-l300.jpg
ソニー PS5 ブルーレイエディションコンソール ( ディスク版 ) - クリスマス前に配達 !
https://www.ebay.com/p/19040936896?iid=154349758236
https://i.ebayimg.com/thumbs/images/g/AD4AAOSw3YphYRwA/s-l300.jpg
オリジナルNintendo EntertainmentシステムビデオゲームバンドルセットキットNESコンソールOG
https://www.ebay.com/p/101801921?iid=333818163486
https://i.ebayimg.com/thumbs/images/g/910AAOSwV5FhgtgV/s-l300.jpg
ソニー PS5 PlayStation 5 デジタル版コンソール-クリスマス配信前 !
https://www.ebay.com/p/25040975636?iid=154369068285

〜省略〜

https://ir.ebaystatic.com/cr/v/c1/s_1x2.gif
Microsoft Xbox One Day One Edition - 500 ギガバイトコンソールキネクトボックスコントローラテスト済み
https://www.ebay.com/itm/284613629401?hash=item42444b55d9:g:jBkAAOSwqldh5hbZ
https://ir.ebaystatic.com/cr/v/c1/s_1x2.gif
Nintendo Wii コンソール RVL-001 パワーリード 、 TVリード 、 センサーバー付き
https://www.ebay.com/itm/165287981385?hash=item267bee7149:g:I00AAOSwibph5VVv
https://ir.ebaystatic.com/cr/v/c1/s_1x2.gif
カシオゲームカンフーファイトCG - 310 ハンドヘルドゲーム 1990
https://www.ebay.com/itm/125105279976?hash=item1d20db13e8:g:J-IAAOSwn8dh5cND
>>> 

無事情報の抽出が出来ることを確認できたので、一旦「exit()」で Python のコマンドプロンプトから出ます。

>>> exit()
(scraping_venv) % 

同じ処理を Python ファイルで実行

一括で処理を実行できる様に、これまで実行したコマンドをまとめて下記のように一つのファイル(ebay_scraping.py)にまとめました。

# ebay_scraping.py
from bs4 import BeautifulSoup
import requests

r = requests.get('https://www.ebay.com/b/Video-Game-Consoles/139971/bn_320033')
soup = BeautifulSoup(r.text,'html.parser')
sitem = soup.find_all('li',class_='s-item') # クラス名が「s-item」の li 要素のリストを取得

for li in sitem: # 各 li 要素に対して処理を行う
    img = li.find('img') # li 要素内の img 要素を取得
    img_src = img.get('src') # img 要素内の src 属性を取得
    name = li.find('h3') # li 要素内の h3 要素を取得
    name_text = name.text # h3 要素内の文字列を取得
    link = li.find('a') # li 要素内の a 要素を取得
    link_href = link.get('href') # a 要素内の href 属性を取得
    print(img_src)
    print(name_text)
    print(link_href)

これを仮想環境「scraping_venv」直下にアップロードします。

そしてコマンド「python3 ebay_scraping.py」を実行します。

(scraping_venv) % python3 ebay_scraping.py
https://i.ebayimg.com/thumbs/images/g/Tl0AAOSwZpJgOul6/s-l300.jpg
ソニー PS5 ブルーレイエディションコンソール ( ディスク版 ) - クリスマス前に配達 !
https://www.ebay.com/p/19040936896?iid=154349758236
https://i.ebayimg.com/thumbs/images/g/GygAAOSwehBh5hlm/s-l300.jpg
ソニー PSP-3000 コンソール キングダム ハートゲームコンソール モンスターハンターゲームソフト付き
https://www.ebay.com/itm/313838198557?hash=item491236df1d:g:GygAAOSwehBh5hlm
https://i.ebayimg.com/thumbs/images/g/AD4AAOSw3YphYRwA/s-l300.jpg
オリジナルNintendo EntertainmentシステムビデオゲームバンドルセットキットNESコンソールOG
https://www.ebay.com/p/101801921?iid=333818163486

〜省略〜

https://ir.ebaystatic.com/cr/v/c1/s_1x2.gif
新品 ソニー PS4 Playstation 4 コンソール 1 TBスリム-新品送料無料
https://www.ebay.com/p/13031982632?iid=255337489506
https://ir.ebaystatic.com/cr/v/c1/s_1x2.gif
Nintendo GameCubeモデルNo. DOL -101 コンソールパーツと修理のみゲーム付き
https://www.ebay.com/itm/165288586955?hash=item267bf7aecb:g:TIgAAOSwZf9h5cbT
https://ir.ebaystatic.com/cr/v/c1/s_1x2.gif
Nintendo GameCube リージョンスイッチ付き LED mod カスタムコントローラ付き
https://www.ebay.com/itm/165288874148?hash=item267bfc10a4:g:u1cAAOSw3MJh5fWT
(scraping_venv) % 

無事サムネイル、商品名、商品ページ URL、が表出されました。ページに掲載される商品がタイミングによって違うので前回の実行時と若干内容が変わっています。

4. データベースの作成

ここまではただターミナル上に print してきましたが、これらの情報をデータベースに保存できる様にします。

さくらレンタルのコントロールパネルでデータベース作成

コントロールパネルのデータベースのセクションで「新規追加」をクリックします。

WordPress を使っている場合はそのデータベースが既にあると思いますが、何かを間違えて WordPress 用のテーブルに影響が出ると良くないので新規のデータベースを作ることをお勧めします。

無事新たなデータベースが作成されたので phpMyAdmin へログインします。

phpMyAdmin でテーブル作成

ログインした後、先ほど作成したデータベースを選択するとテーブル作成の画面に遷移すると思います。

テーブル名を入力して「Go」をクリックします。

カラム名、形式、長さを入力して「Save」をクリックします。

これでテーブル作成は完了です。下記のように反映されていると思います。

これでデータベースとその中にテーブルが出来上がりました。

5. Python からデータベースを操作する

では Python プログラムで抽出した情報をそのままデータベースのテーブルに insert 出来るようにしていきます。

MySQLdb モジュールで操作する

まずは Python で MySQL を操作するために必要な MySQLdb モジュールを使える様、ピp3で「mysqlclient」をインストールします。

(scraping_venv) % pip3 install mysqlclient

インストールしたら Python を起動し、MySQLdb をインポートしてデータベースに接続します。

(scraping_venv) % python3
>>> import MySQLdb
>>> conn = MySQLdb.connect(host='データベースサーバ', db='データベース名',user='ユーザー名',passwd='パスワード',charset='utf8mb4')
>>>

そして試しに先ほど作成したテーブル「ebay_products」に select クエリを叩いてみます。

>>> c = conn.cursor()
>>> c.execute('select * from ebay_products')
0
>>> 

まだデータを何も入れていないので 0 と返ってきています。動作は特に問題なさそうです。

Python ファイルに追記する

では先ほど作成した ebay_scraping.py を編集します。

MySQLdb モジュールでデータベースに接続する部分と合わせて、データを print する代わりにテーブルへ insert する様変更します。

# ebay_scraping.py
from bs4 import BeautifulSoup
import requests
import MySQLdb # 追記分

# 追記分↓
conn = MySQLdb.connect(host='データベースサーバー', db='データベース名',user='ユーザー名',passwd='パスワード',charset='utf8mb4')
c = conn.cursor()
# 追記分↑

r = requests.get('https://www.ebay.com/b/Video-Game-Consoles/139971/bn_320033')
soup = BeautifulSoup(r.text,'html.parser')
sitem = soup.find_all('li',class_='s-item')

for li in sitem:
    img = li.find('img')
    img_src = img.get('src')
    name = li.find('h3')
    name_text = name.text
    link = li.find('a')
    link_href = link.get('href')
    # print(img_src)
    # print(name_text)
    # print(link_href)
    # 追記分↓
    c.execute('insert into ebay_products values("'+img_src+'","'+name_text+'","'+link_href+'")')
    
conn.commit()
# 追記分↑

ではファイルを実行してみます。

(scraping_venv) % python3 ebay_scraping.py
(scraping_venv) % 

phpMyAdmin で対象のテーブルを開くとちゃんとデータが入っています。

これで eBay からデータを取得してデータベースへ保存するところまで出来ました。

6. CRON で定期実行する

作成したプログラムを定期的に実行する上で便利なのが CRON です。さくらのコントロールパネルから設定できるのでこの際やってみましょう。

コントロールパネル左下の「スクリプト設定」>「CRON設定」を開き「スケジュール追加」をクリックします。

設定画面が開くので実行コマンドと実行日時を設定します。毎日 21:00 に実行する設定にします。

「実行コマンド」欄は今回の場合「cd 実行ファイルのパス; Python3 の絶対パス 実行ファイル名」の形で下記の様に入力します。

cd www/notemite/scraping_venv; /home/ユーザー名/local/python/bin/python3 ebay_scraping.py

Python3 の絶対パスはコマンド「which python3」で確認できます。

% which python3
/home/ユーザー名/local/python/bin/python3

「保存する」をクリックして CRON 設定は完了です。

これで毎日 21:00 に ebay_scraping.py が実行され、取得したデータがデータベースのテーブルに insert されていきます。

複数処理を順に実行したい場合

余談ですが、設定できる CRON の数には限りがあるので、一つの処理につき一つの CRON を設定するとすぐ使い切ってしまいます。

なので例えば process_A.py、process_B.py、process_C.py という順番で実行したいプログラムがあった場合、まず CRON 設定の実行コマンドに下記を記述します。

cd www/notemite/scraping_venv; /bin/sh multiple_process.sh

これで CRON からは multiple_process.sh が実行されるので、個別のファイルと同じディレクトリに multiple_process.sh というファイルを作成し下記のように記述します。

#!/bin/sh
/home/ユーザー名/local/python/bin/python3 process_A.py &&
/home/ユーザー名/local/python/bin/python3 process_B.py &&
/home/ユーザー名/local/python/bin/python3 process_C.py

これによって CRON によって multiple_process.sh 呼び出され、まず process_A.py を実行、処理が成功したら process_B.py、さらにそれが成功したら process_C.py という具合に実行することができます。

Python 開発が可能なロボット Ameca(第一世代)の基本情報

Ameca とは

イギリスのロボット開発企業 Engineered Arts が開発したロボットの一つで、Mesmer、RoboThespian につぐ最新モデル。

人型のデバイスにマイクやカメラ、多数のセンサー、顔認識と音声認識の機能、そして機械学習用のハードウェアを積んだロボットですが、先代の Mesmer の開発で培われた 3D スキャンや表情の再現の技術によって非常にリアルな顔の表情や体の動きを再現する事が可能となっています。

187 センチ 49 キロと、人間で言うとかなりの痩せ型です。

Tinman

本体を Web ブラウザ上でリアルタイムにコントロールする Tinman という機能も統合されており、遠隔地のオペレーターがカメラやマイクを使用してロボットの周囲との会話をする事ができます。

Engineered Arts

平たく言うとビデオ通話の相手方のデバイスを PC からロボットに変えた様なイメージ。

ロボットならではの機能としては、画面上で特定の相手を選択してロボットの目線を合わせたり、オペレーターが話すと音声に合わせてロボットの口も動きます。

また、ロボットの表情を画面上で確認しながらボタンのクリックで感情の表現も可能です。

ユーザーに優しい設計

ロボットの保守やアップデート、変更など一連の作業が Web ブラウザ上で可能。リモートでサポートを受けることも可能。

Ameca に搭載されているたくさんのセンサーやカメラからの情報をリアルタイムで見ることができる。

開発プラットフォームとしての Ameca

主にカスタマーサービスやホスピタリティ、エンタメの分野での使用を想定されて作られていますが、人間型ロボットに関する開発プラットフォームとしての一面もあり、独自 OS の「Tritium」上でコードのテストや開発を行うことができます。

ソフトウェア、ハードウェアともにモジュラー型を採用、そしてクラウドと連携しているため開発やアップグレードがしやすく、頭部のみ、腕のみ、など個々のパーツに分けてテストや開発が可能とのこと。

Tritium OS

Tritium という独自 OS により、クラウド上でロボットの動きや人間とのコミュニケーションのコントロール、メンテナンス、コンテンツ(事前準備したロボットの挙動)の共有などが可能。

2つの相反する行動の命令が同時に来た場合にも、安全な順番で行う様な判断が可能だそうです。

Python IDE

Python の統合開発環境が提供されており、ロボットに搭載されたセンサーで取得した様々な情報を使ったオリジナルの機能や挙動を作成することが可能。ブラウザで開発環境へのアクセスが可能で、コードを書いてその場でロボット上でのテストが可能です。

RESTful API

RESTful API の技術を使って、Web 上で Ameca のコントロールが可能です。

外部の機材との連携やショーとのシンクロ、別の場所にあるセンサーとの連携によって行動を制御したり、遠方のロボットの行動の把握とコントロールを行えます。

CES 2022 で公開予定

Python3 が初めから使えるレンタルサーバーおすすめ 7 選

Python の学習やスクレイピングのプログラム作成など、ある程度は自分のパソコンで対応できるものの実際に Python をサーバーで実行したいことはよくあると思います。

ただ、その際に気をつけないといけないのが Python2 と Python3 は別物だということ。

基本的にはすでに公式のサポートも切れていて推奨されていない Python2 ではなく Python3 をおすすめしますが、さくらのレンタルサーバーなど、中には Python3 がもともと入っていないレンタルサーバーもあります。

ここでは Python3 が初めからインストールされているおすすめレンタルサーバーをご紹介します。

無料お試し期間があるサーバーも多いのでその間に Python3 を試してみるのもありです。

サーバー選択時に注目する点

実際のリストに行く前に、Python3 が入っている事以外にも気にするところがいくつかあるので紹介します。

1. 作成可能なデータベースの個数

Python でスクレイピングした情報など、データベースに保存したりデータベースから取り出したりする必要があります。その際、用途によってデータベースを分けたい場合もあると思いますので、作成可能なデータベースの個数も気にするといいと思います。

2. 設定可能な CRON の個数

CRON は指定した日時によって定期的にプログラムやスクリプトを実行できる機能です。スクレイピングやデータベースの更新など、CRON を使うと便利な場面が多いと思いますが、プランによって同時に設定できる CRON の個数が決まっているのでサーバーを選ぶ際には注目するといいと思います。

3. ウェブ上の関連記事数

サーバーの中で作業をしていく中で、必ず何かしらのエラーが起きて検索する必要があるのが常なので、検索した時にどの程度情報が出てくるかも大事なポイントです。検索結果の数も目安として掲載しますので参考にしてみてください。

Python3 が入っているレンタルサーバー

エックスサーバーロリポップXREAConoha WINGコアサーバースターサーバーバリューサーバー
Python バージョン2.7/3.4/3.62.7/3.72.x/3.x2.7/3,62.x/3.x2.7/3.4/3.62.7/3.6
データベース上限無制限505無制限無制限30無制限
CRON 上限個数無制限10不明不明不明1099
関連記事数かなり多いかなり多い多い多いかなり少ないかなり少ないかなり少ない
おすすめプランスタンダードスタンダードXREA PlusベーシックCORE-Xスタンダードスタンダード

1. エックスサーバー

カテゴリコメント
公式サイトエックスサーバー
OSLinux
Python のバージョンPython2.7, Python3.4, Python3.6
データベース個数全プラン無制限
CRON 個数全プラン無制限
ウェブ上の関連記事数かなり多い
おすすめプランスタンダード
(月額 990 円 + 初期費用 3,300 円)
無料お試し期間10 日間

ウェブ上の関連記事数

「"エックスサーバー" "python3"」での検索結果は 12,500 件程度と、多くの情報がすでに存在しているので心強いです。

おすすめプラン

基本的な違いはディスクスペースの容量と無料独自ドメインの対象くらいなので「スタンダード」プランで充分だと思います。

CRON の個数は無制限です。データベースの個数も無制限です。すごいですね。

公式サイトエックスサーバー

2. ロリポップ

カテゴリコメント
公式サイトロリポップ!
OSLinux (CentOS)
Python のバージョンPython2.7, Python3.7
データベース個数ライトプラン:1
スタンダードプラン:50
CRON 個数ライトプラン:5
スタンダードプラン:10
ウェブ上の関連記事数かなり多い
おすすめプランスタンダード
(月額 440 円〜 + 初期費用 0 円)
無料お試し期間10 日間

ウェブ上の関連記事数

「"ロリポップ" "python3"」での検索結果は 18,200 件と、こちらも多くの情報がすでにネット上に存在しています。

おすすめプラン

ライトだとMySQLデータベース1つしか作れないので、50個まで作れる「スタンダード」プランが良いと思います。

CRON もスタンダードプランであれば 10 個まで設定可能です。

公式サイトロリポップ!

3. XREA

カテゴリコメント
公式サイトXREA
OSLinux (CentOS)
Python のバージョンPython2, Python3
データベース個数XREA Free:1
XREA Plus:5
CRONXREA Free:なし
XREA Plus:あり(個数不明)
ウェブ上の関連記事数多い
おすすめプランXREA Plus
(月額 210 円〜 + 初期費用 0 円)
無料お試し期間7 日間

ウェブ上の関連記事数

「"XREA" "python3"」での検索結果は 5,020 件で、上の2つに比べると少ないですが、これでもよく調べれば普通の使い方には対応できるのではと思います。

おすすめプラン

XREA Free は無料ですが CRON が使えませんし、データベースも 1 つしか持てません。XREA Plus は CRON も使えてデータベースも 5 個まで作れるのでおすすめです。

公式サイトXREA

4. ConoHa WING

カテゴリコメント
公式サイトConoHa WING
OSLinux (CentOS)
Python のバージョンPython2.7, Python3,6
データベース個数全プラン無制限
CRONあり(対象プラン及び個数不明)
ウェブ上の関連記事数多い
おすすめプランベーシック
(月額 740 円〜 + 初期費用 0 円)
無料お試し期間無し

ウェブ上の関連記事数

「"ConoHa WING" "python3"」での検索結果は 1,930 件で少し少ないかと思います。

おすすめプラン

データベースの個数は全プランで無制限です。 CRON は「ジョブスケジューラー」という名前で提供されていますが、個数はわかりませんでした。ベーシックプランがおすすめです。

公式サイトConoHa WING

5. コアサーバー

カテゴリコメント
公式サイトコアサーバー
OSLinux
Python のバージョンPython2, Python3
データベース個数全プラン無制限
CRON全プランあり(個数不明)
ウェブ上の関連記事数かなり少ない
おすすめプランCORE-X
(月額 399 円〜 + 初期費用 1,650 円)
無料お試し期間30 日間

ウェブ上の関連記事数

「"コアサーバー" "python3"」での検索結果は 500 件程度で、かなり少ない状態です。何か問題が起こったとき、まだ解決方法がネット上に載っていない場合も考えられます。

おすすめプラン

全プランで CRON あり、データベースの個数も無制限です。CORE-Xプランで十分足りると思います。

公式サイトコアサーバー

6. スターサーバー

カテゴリコメント
公式サイトスターサーバー
OSLinux
Python のバージョンPython2.7, Python3.4, Python3.6
データベース個数ライト:1
スタンダード:30
CRON 個数ライト:5
スタンダード:10
ウェブ上の関連記事数かなり少ない
おすすめプランスタンダード
(月額 440 円〜 + 初期費用 0 円)
無料お試し期間14 日間

ウェブ上の関連記事数

「"スターサーバー" "python3"」での検索結果は 250 件程度と、こちらも既存の情報はかなり少ないです。

おすすめプラン

ライトだとMySQLデータベース1つしか作れないので、スタンダードが良いと思います。CRON はスタンダードプランで 10 個まで使用可能です。

公式サイトスターサーバー

7. バリューサーバー

カテゴリコメント
公式サイトバリューサーバー
OSLinux
Python のバージョンPython2.7, Python3.6
データベース個数エコプラン:1
スタンダードプラン:無制限
CRON 個数エコプラン:10
スタンダードプラン:99
ウェブ上の関連記事数かなり少ない
おすすめプランスタンダード
(月額 367 円〜 + 初期費用 0 円)
無料お試し期間10 日間

ウェブ上の関連記事数

「"バリューサーバー" "python3"」での検索結果は 120 件程度で、こちらもかなり少ないです。初学者の場合は何かエラーでつまづいた場合に心が折れてしまう可能性が高いと思います。

おすすめプラン

軽く触るだけならエコでもいいとは思うがデータベースを使う予定がある場合はスタンダードにしておいた方がいいかも。CRON はスタンダードで 99 個まで使えます。データベースもエコプランでは 1 つだけですが、スタンダードでは個数無制限です。

公式サイトバリューサーバー

まとめ

いかがでしたか?

学習も兼ねて Python を少しいじってみたいという場合は、既存の情報が多いエックスサーバーロリポップ!がおすすめです。どうしても予想外のエラーは発生しますので、それをなるべく早く解決出来る方がプログラミングに集中しやすくなります。

また見ての通り、Python2系 と Python3系両方が入っているサーバーがほとんどです。デフォルトの仕様では「python」コマンドは Python2系 、「python3」コマンドで Python3系を呼び出す場合が多いので注意してください。

最後に、レンタルサーバーよりもさらに自由度の高い環境でプログラミングをしたい方は VPS もおすすめです。ぜひ下記の記事もチェックしてみてください。

▶︎Ubuntu を手軽に使える VPS 7 選

▶︎Django のおすすめ VPS(仮想専用サーバー)は OS から選ぶべし

さくらの VPS (Ubuntu 20.04) で Django アプリを作る(3/3)

Ubuntu 環境(さくらのVPS )で Django アプリケーションを作るまでに実際に辿ったステップ。こちらの記事の続編です。

今回はこれまでのステップで作成した Django アプリケーションの HTTPS 化、そして使用データベースを MySQL に変更します。

  1. ドメインの HTTPS 化
    • ポート 443 の開放
    • certbot と Nginx プラグインのインストール
    • certbot の設定
    • 変更内容の確認
  2. 使用データベースを MySQL に変更する
    • MySQL のインストール
    • 非 root ユーザーとデータベースの作成
    • mysqlclient インストールの準備
    • Python 仮想環境と MySQLdb の動作確認

1. サイトの HTTPS 化

サイトに https でアクセスできる様設定します。certbot を使えば Let's Encrypt の TLS/SSL 証明書の取得や設定を簡単に行うことができます。

ポート 443 の開放

さくらインターネットのパケットフィルタを使っている場合、ポート番号 443 が開放されている必要があります。

ファイアウォールで設定する場合は ufw コマンドで必要な設定を行ってください。

certbot と Nginx プラグインのインストール

certbot(Let's Encrypt 使用の HTTPS 自動設定パッケージ)と python3-certbot-nginx(Certbot 用 Nginx プラグイン)をインストールします。

$ sudo apt install certbot python3-certbot-nginx

certbot の設定

Nginx プラグインを使用して自動設定を走らせます。このプロセスによって自動的に sites-available ディレクトリ内の Nginx 設定ファイルに変更が加えられます。

コマンド内「-d」の後に HTTPS 化するドメインを入力します。Nginx の設定ファイル内、server_name に指定されているドメインと一致しているのを確認してください。

$ sudo certbot --nginx -d example.com -d www.example.com

これを実行すると緊急連絡用のメールアドレスの入力を求められ、入力して続けると利用規約への同意、お知らせメール送信の可否、そして最後に HTTP へのリクエストを HTTPS へリダイレクトするか否かを尋ねられるのでそれぞれ回答して進めていきます。

以上で完了です。https でのアクセスが可能になっているはずです。

変更内容の確認

完了後、Nginx 設定ファイルを確認すると「# managed by Certbot」のコメントと共に自動で追記された内容を確認できます。

既存の server ブロック内への追記内容

    listen [::]:443 ssl; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/graffuhs.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/graffuhs.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

新規作成された server ブロックの内容

server {
    if ($host = example.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


        listen 80;
        listen [::]:80;

        server_name example.com;
    return 404; # managed by Certbot


}
server {
    if ($host = www.example.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


        listen 80;
        listen [::]:80;

        server_name www.example.com;
    return 404; # managed by Certbot


}

この設定によって「http://example.com」と「http://www.example.com」両方とも HTTPS へリダイレクトされる様になりました。

2. 使用データベースを MySQL に変更する

MySQL のインストール

コマンド「sudo apt install mysql-server」を実行し、MySQL をインストールします。

adminvps@xx1-234-56789:~$ sudo apt install mysql-server
adminvps@xx1-234-56789:~$ 

コマンド「sudo mysql_secure_installation」を実行。

adminvps@xx1-234-56789:~$ sudo mysql_secure_installation
adminvps@xx1-234-56789:~$ 

コマンド「sudo mysql」で MySQL へアクセスしてみます。

vpsadmin@xx1-234-56789:~$ sudo mysql
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 11
Server version: 8.0.23-0ubuntu0.20.04.1 (Ubuntu)

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 

無事アクセスできました。コマンド「exit」で MySQL から出ます。

mysql> exit
Bye
vpsadmin@xx1-234-56789:~$ 

非 root ユーザーとデータベースの作成

root で接続

sudo mysql -u root
create user ユーザー名 identified by 'パスワード';
GRANT ALL PRIVILEGES ON demodb.* to demouser@'%';
flush privileges;

ここで作成したユーザー名とパスワードを Django の settings.py で使います。

mysqlclient インストールの準備

libmysqlclient-dev と default-libmysqlclient-dev をインストールします。

adminvps@xx1-234-56789:~$ sudo apt install libmysqlclient-dev default-libmysqlclient-dev

Python 仮想環境と MySQLdb の動作確認

(sample) adminvps@xx1-234-56789:~/sample/bin$ pip install wheel
Collecting wheel
  Downloading wheel-0.36.2-py2.py3-none-any.whl (35 kB)
Installing collected packages: wheel
Successfully installed wheel-0.36.2
(sample) adminvps@xx1-234-56789:~/sample/bin$ pip install mysqlclient
Requirement already satisfied: mysqlclient in /home/adminvps/sample/lib/python3.9/site-packages (2.0.3)
(sample) adminvps@xx1-234-56789:~/sample/bin$ 

ここで、Python の仮想環境と MySQLdb の動作確認をしてみます。

Python 仮想環境を作り、起動します。

vpsadmin@ss1-234-56789:~$ python3.9 -m venv testvenv
vpsadmin@ss1-234-56789:~$ source testvenv/bin/activate
(testvenv) vpsadmin@ss1-234-56789:~$

wheel をインストールします。

(testvenv) vpsadmin@ss1-234-56789:~$ pip install wheel
Collecting wheel
  Using cached wheel-0.36.2-py2.py3-none-any.whl (35 kB)
Installing collected packages: wheel
Successfully installed wheel-0.36.2
(testvenv) vpsadmin@ss1-234-56789:~$

mysqlclient をインストールします。

(testvenv) vpsadmin@ss1-234-56789:~$ pip install mysqlclient
Processing ./.cache/pip/wheels/43/55/d9/a2243d4b624c18c5cba30bf88e0521147498368068cb302532/mysqlclient-2.0.3-cp39-cp39-linux_x86_64.whl
Installing collected packages: mysqlclient
Successfully installed mysqlclient-2.0.3
(testvenv) vpsadmin@ss1-234-56789:~$ 

*pip install は「mysqlclient」ですが、python で import するときは「import MySQLdb」になります。(import mysqlclient ではない)誰も間違わないかもしれませんが僕はこれでハマってました。。。

Python を起動し、import MySQLdb ができるか確認します。

(testvenv) vpsadmin@ss1-234-56789:~$ python3.9
Python 3.9.0+ (default, Oct 20 2020, 08:43:38) 
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import MySQLdb
>>> 

無事 import できたので大丈夫そうです。

他の行程へ

▶︎まずは2週間無料でお試し♪さくらのVPS

さくらの VPS (Ubuntu 20.04) で Django アプリを作る(2/3)

Ubuntu 環境(さくらのVPS )で Django アプリケーションを作るまでに実際に辿ったステップ。こちらの記事の続編です。

今回は実際に Django のプロジェクト・アプリケーションを作って公開するところまで。Web サーバーとして Nginx、アプリケーションサーバーとして Gunicorn の設定も行います。

また、今後同じサーバーで複数の Django プロジェクトを作っても大丈夫な様に設定していきます。

Django + Nginx + Gunicorn での設定

全体はとりあえずこんな感じのイメージです。

  1. サイトをホストするディレクトリの作成
    • ルートディレクトリ作成
    • 管理者の変更
  2. Django を動かす
    • Python 仮想環境作成
    • Django インストール
    • Django プロジェクトと Django アプリケーションを作成
    • settings.py の設定
    • Django の動作確認
  3. Web サーバーの設定(Nginx)
    • Web サーバー: Nginx
    • 設定ファイル(nginx.conf)
  4. アプリケーションサーバーの設定(Gunicorn)
    • アプリケーションサーバー: Gunicorn
    • 仮想環境内で pip install gunicorn
    • systemd .socket ファイル
    • systemd .service ファイル
    • Nginx の設定ファイルでソケットを使用する様設定する
  5. 静的ファイルの設定

1. サイトをホストするディレクトリの作成

サーバー上にサイトホスト用ディレクトリを作成しますが、今後を見越し、複数のドメインを同じサーバー上でホスト出来る様に配慮して作業を進めていきます。

ルートディレクトリ作成

サイトをホストするルートディレクトリを作成します。

Nginx はデフォルトで「/var/www/html」というディレクトリを作っていますが、今回は /var/www/ の配下にドメイン名のディレクトリ、そしてその配下に html というディレクトリを作ります。(イメージ:/var/www/example.com/html)

vpsadmin@xx1-234-56789:/var/www$ sudo mkdir -p example.com
[sudo] password for vpsadmin: 
vpsadmin@xx1-234-56789:/var/www$ ls
html  meatthezoo.org
vpsadmin@xx1-234-56789:/var/www$ cd example.com
vpsadmin@xx1-234-56789:/var/www/example.com$ sudo mkdir -p html

管理者の変更

ディレクトリの管理者を変更します。

vpsadmin@xx1-234-56789:/var/www$ sudo chown -R $USER:$USER /var/www/example.com/html

2. Django を動かす

Python 仮想環境作成

ルートディレクトリ(/var/www/example.com/html)直下で「python3 -m venv 仮想環境名」を実行しPython の仮想環境を作成します。

$ python3 -m venv djangovenv

Django インストール

仮想環境を起動し、「pip install django」で Django をインストールします。

% cd djangovenv
% source bin/activate
(djangovenv) % pip install django

Django プロジェクトと Django アプリケーションの作成

Django プロジェクトと Django アプリケーションを作成していきます。

(djangovenv) % django-admin startproject djangoprod
(djangovenv) % cd djangoprod
(djangovenv) % python manage.py startapp djangoapp

settings.py の設定

settings.py の ALLOWED_HOSTS にサイトのドメイン名を登録し、INSTALLED_APS にアプリケーションを追加します。

ALLOWED_HOSTS = ['example.com', 'www.example.com']

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'djangoapp.apps.DjangoAppConfig' # 追記分
]

Django の動作確認

コマンド「python manage.py runserver 0.0.0.0:8000」を実行し、ブラウザの URL バーに「ドメイン名:8000」と打ち込んでみます。

Django デフォルトのロケットの画面が出たら大丈夫です。ちなみにこの画面は settings.py の DEBUG = True の時だけ表示されます。

Djangoのデフォルトエラーページが DEBUG=Falseだと見れないのはなんでか調べた

今現在、Django の runserver を起動した場合に限り、ポート 8000 でなら Django が動くという状態です。

というわけで Web サーバー(Nginx)をアプリケーションサーバー(Gunicorn)を使って下記を解決していきます。

問題解決方法
URL バーでポート 8000 を指定しないとアクセスできないNginx の設定ファイルで、該当するドメイン名にアクセスされたら Django に繋ぐ様変更する
runserver を起動していないとアクセスできないGunicorn を使う

3. Web サーバーの設定(Nginx)

設定ファイル(nginx.conf)

デフォルトの設定ファイルは「/etc/nginx/nginx.conf」

初めから下記の二行が書いてあればこのファイルはノータッチで良さそうです。

  • include /etc/nginx/conf.d/*.conf;
  • include /etc/nginx/sites-enabled/*;

複数ドメイン対応

一つのサーバーで複数のウェブサイトを運営しようとした場合、一つの IP アドレスに複数のドメイン名が紐づく事になります。

そのため、リクエストされた URL に応じて適切なサイトの情報を返せる様設定していきます。

  1. sites-available ディレクトリにドメイン毎の設定ファイルを置く
  2. 各ドメインの設定ファイルでドメインとルートディレクトリを紐付け
  3. それらファイルのシンボリックリンクを sites-enabled ディレクトリに作成
  4. sites-enabled を nginx.conf 本体に include

設定ファイルの複製と編集

「sites-available」ディレクトリの「default」ファイルを個別サイト用にコピーします。

sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/example.com

コピーした方のファイルを確認します。下の方に Virtual Host のための設定というところがあるのでそちらを使います。上半分は Default Server のための設定なので全てコメントアウトします。

server_name と location / の部分を編集しました。

example.com ファイルの Virtual Host 設定部分
# Virtual Host configuration for example.com
#
# You can move that to a different file under sites-available/ and symlink that
# to sites-enabled/ to enable it.
#

server {
        listen 80;
        listen [::]:80;

        server_name www.example.com;
        return 301 http://example.com$request_uri;
}


server {
        listen 80;
        listen [::]:80;

        server_name example.com;

        root /var/www/example.com/html;
        index index.html;

        location / {
                try_files $uri $uri/ =404;
                include proxy_params;
                proxy_pass http://127.0.0.1:8000;
        }
}

初めの部分は「www.example.com」へのアクセスを自動的に「example.com」へリダイレクトする設定です。

シンボリックリンクを作成します。

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/

Nginx を再起動します。

sudo systemctl restart nginx

そして runserver を実行して、反映を確かめます。

python manage.py runserver 0.0.0.0:8000

これで一つ解決です。ポート 8000 を URL に含めなくても Django に飛ぶ様になりました。

次は runserver を起動していないとアクセスできないという部分を、Gunicorn を使って解決します。

問題解決方法
runserver を起動していないとアクセスできないGunicorn を使う

4. アプリケーションサーバーの設定(Gunicorn)

Nginx から飛んできたリクエストを、待ち受けているソケットから Django アプリケーションに伝える様に設定します。

言い方を変えると Nginx からのリクエストを .socket ファイルが受け、.service ファイルを起動し Gunicorn の処理を実行します。

Gunicorn のインストール

仮想環境内で pip install gunicorn を実行します。

pip install gunicorn

.socket と .service の作成

「/etc/systemd/system/」ディレクトリに .socket と .service を作成します。今回はファイル名をそれぞれ「sample_django.socket」、「sample_django.service」としています。

systemd .socket ファイル

Nginx の設定ファイルで指定したポートでリクエストを待ち受け、 リクエストがあったら指定されているサービスに接続を渡します。

# sample_django.socket

[Unit]
Description=gunicorn socket
[Socket]
ListenStream=/run/sample_django.sock
[Install]
WantedBy=sockets.target
項目メモ
Descriptionログ出力の際などに使われる
ListenStreamポートの指定。Nginx 設定ファイルの pass_proxy で指定
WantedBysockets.target

systemd .service ファイル

サービスの依存関係や実際の実行内容を定義します。

# sample_django.service

[Unit]
Description=gunicorn daemon
Requires=sample_django.socket
After=network.target
[Service]
User=root
Group=root
WorkingDirectory=/var/www/example.com/html/djangovenv/djangoprod
ExecStart=/var/www/example.com/html/djangovenv/bin/gunicorn --workers 3 --bind unix:/run/sample_django.sock djangoprod.wsgi:application
[Install]
WantedBy=multi-user.target
項目メモ
Descriptionログ出力の際などに使われる
Requires対応する .socket ファイル
After
User
Group
WorkingDirectorymanage.py があるディレクトリのフルパス?
ExecStartsystemctl start した時に実行するコマンド。
.sock と wsgi を bind。
venv 内の gunicorn のフルパス(?)を指定
WantedBy大抵 multi-user.target で大丈夫

サービスを起動します。

systemctl start sample_django.socket
systemctl start sample_django.service

Nginx 設定ファイルの変更

Nginx 設定ファイルの proxy_pass の部分を下記の様に変更しました。.socket ファイルの ListenStream で設定したポートを指定しています。

location / {
        try_files $uri $uri/ =404;
        include proxy_params;
        # proxy_pass http://127.0.0.1:8000;
        proxy_pass http://unix:/run/sample_django.sock;
}

Nginx の設定を検証し再起動します。

$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
$ sudo systemctl restart nginx

これで runserver を実行しなくても URL を叩けば Django アプリケーションへアクセスできる様になったはずです。

大雑把ですがなんとなく全体像は下記の様なイメージです。

Django + Nginx + Gunicorn での設定

*作業時は何かにつけて「pkill gunicorn」で gunicorn を終わらせた方が良いです。エラーが起きていくらコードを修正しても治らないまま数時間経って、「pkill gunicorn」一発で治ったこともありました。

静的ファイルの設定

実はこの時点では CSS や JavaScript のいわゆる静的ファイルはうまく反映されません。ドメイン名/admin にブラウザでアクセスすると CSS が抜けている状態だと思います。

Django で静的ファイルとうまくやる

本番環境でちゃんと表示される様、settings.py と Nginx の設定ファイルを編集します。

settings.py の編集

settings.py 内では STATICFILES_DIRS と STATIC_ROOT を設定します。

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),
]
STATIC_ROOT = '/var/www/example.com/html/static'
項目メモ
STATICFILES_DIRScollectstatic が静的ファイルを追加検索する対象のディレクトリ
STATIC_ROOTcollectstatic 実行時に静的ファイルの集約先となるディレクトリ

collectstatic を実行します。

python manage.py collectstatic

Nginx の設定ファイルの編集

server ディレクティブの中に STATIC_ROOT 指定したパスを追記しました。

        location /static {
                alias /var/www/example.com/html/static;
        }

これでドメイン名/admin を再度確認すると CSS が反映されていると思います。

他の行程へ

▶︎まずは2週間無料でお試し♪さくらのVPS

さくらの VPS (Ubuntu 20.04) で Django アプリを作る(1/3)

Ubuntu 環境(さくらのVPS )で Django アプリケーションを作るまでに実際に辿ったステップを記録します。

手順がかなり多いので途中あまり詳しく書いていないところもありますがご了承ください。

まずは VPS の契約から Ubuntu OS の設定Python のインストールWeb サーバーのインストールまで。

  1. VPS の契約(さくらの VPS)
  2. 独自ドメインの取得
  3. IP アドレスとドメイン名を紐付け
  4. OS (Ubuntu 20.04) のインストール
  5. Ubuntu の初期設定
    • ssh でサーバーへ接続
    • 非 root ユーザーの作成
    • パッケージリストを最新版へ更新
    • パッケージのアップグレード
    • サーバーの再起動
  6. FTP サーバーのインストール
  7. 開発パッケージのインストール
  8. Python と関連パッケージのインストール
    • 依存関係のインストール
    • Python 3.9 のインストール
    • python3.9-dev のインストール
    • 仮想環境(venv)のインストール
  9. Web サーバー(Nginx)のインストール

ちなみに、僕は初め下記の書籍に沿って Django の全体像を勉強しました。書籍の中では AWS を使ったデプロイにも触れられていますので参考にしてみてください。

動かして学ぶ! Python Django開発入門 (NEXT ONE)

1. VPS の契約(さくらの VPS)

まずは VPS の契約。私は月額 880 円〜の さくらのVPS 1G プランにしました。

公式ページさくらのVPS 1G

さくらの VPS 以外でも大丈夫

とはいうものの、実はさくらの VPS でなくても Ubuntu OS であればこちらの記事の手順で特に問題ないです。

コントロールパネルの見た目やファイアウォールの設定の部分だけは少し違いますが、それは一番初めの部分でそれ以降は OS に依存するので他の VPS でも変わりません。

Django におすすめの OS と VPS を下記の記事で紹介しているのでぜひ参考にしてみてください。

▶︎ Django のおすすめ VPS(仮想専用サーバー)は OS から選ぶべし
▶︎ Ubuntu を手軽に使える VPS おすすめ 7 選

2. 独自ドメインの取得

サイト公開用の独自ドメインを取得します。こちらもさくらのドメインで取得しました。

3. IP アドレスとドメイン名を紐付け

VPS コントロールパネルの「ネームサーバ登録」をクリックします。

遷移先のページで「ドメインリスト」をクリック後、対象のドメインの「ゾーン編集」をクリックし、情報を登録します。

ゾーン情報の設定に関しては下記の記事にもまとめているので参考にしてみてください。

4. OS (Ubuntu 20.04) のインストール

VPS コントロールパネルの「各種設定」そして「OSインストール」をクリックします。

今回は標準 OS の Ubuntu 20.04 をインストールします。

サーバーのファイアウォールを設定する代わりに、さくら VPS のパケットフィルタの機能でポートの開閉を設定しています。

ファイアウォールで設定する場合はパケットフィルタをオフにします。

「インストールを実行する」をクリックし、Ubuntu のインストールを開始します。

先ほどと重複しますが、Ubuntu 以外にも Django におすすめの OS を下記の記事で紹介しているのでもしよければ見てみてください。

▶︎ Django におすすめの OS と VPS の選び方

5. Ubuntu の初期設定

ssh でサーバーへ接続

ターミナルで「ssh 管理ユーザー名@ホスト名」と打ち込み接続します。ホスト名は下記の通りコントロールパネルの「ネットワーク情報」タブで確認できます。

下記の様にコマンド「ssh 管理ユーザー名@ホスト名」を入力します。

% ssh ubuntu@xx1-234-56789.vs.sakura.ne.jp

初めて接続する際に下記の様に「The authenticity of host 'xx1-234-56789.vs.sakura.ne.jp (123.456.78.90)' can't be established.」とメッセージが出ることがありますが特に問題ないので yes と打ち込みます。

The authenticity of host 'xx1-234-56789.vs.sakura.ne.jp (123.456.78.90)' can't be established.
ECDSA key fingerprint is XXX123:ABCDEFGHIJKLMNOPQRSTUVWXYZ.
Are you sure you want to continue connecting (yes/no/[fingerprint])? 
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'xx1-234-56789.vs.sakura.ne.jp.123.456.78.90' (ECDSA) to the list of known hosts.
% ssh ubuntu@xx1-234-56789.vs.sakura.ne.jp
ubuntu@xx1-234-56789.vs.sakura.ne.jp's password: 

パスワードを入力し無事 ssh で サーバーへログインできました。

% ssh ubuntu@xx1-234-56789.vs.sakura.ne.jp
ubuntu@xx1-234-56789.vs.sakura.ne.jp's password: 
Welcome to Ubuntu 20.04.1 LTS (GNU/Linux 5.4.0-52-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Fri Apr  9 08:11:04 JST 2021

  System load:  0.0               Processes:             105
  Usage of /:   2.0% of 94.43GB   Users logged in:       0
  Memory usage: 16%               IPv4 address for ens3: 123.456.78.90
  Swap usage:   0%


66 updates can be installed immediately.
0 of these updates are security updates.
To see these additional updates run: apt list --upgradable


The list of available updates is more than a week old.
To check for new updates run: sudo apt update


The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.


SAKURA internet [Virtual Private Server SERVICE]

To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

ubuntu@xx1-234-56789:~$ 

3行目に「Welcome to Ubuntu 20.04.1 LTS」と書かれてあります。それ以降の部分はサーバーの容量やアップデートなどの案内事項です。

非 root ユーザーの作成

ログインしたユーザーを root ユーザーへ昇格し、新たな sudo ユーザーを作成します。詳しくは下記の記事を参照してください。

パッケージリストを最新版へ更新

コマンド「sudo apt update」でパッケージリストを最新版に更新します。

ubuntu@xx1-234-56789:~$ sudo apt update
ubuntu@xx1-234-56789:~$ 

パッケージのアップグレード

コマンド「sudo apt upgrade」を実行し、サーバーに元々インストールされているパッケージに対してアップグレードを実施します。

ubuntu@xx1-234-56789:~$ sudo apt upgrade
ubuntu@xx1-234-56789:~$ 

サーバーの再起動

コマンド「sudo reboot」を実行し、サーバーを再起動します。

ubuntu@xx1-234-56789:~$ sudo reboot
Connection to xx1-234-56789.vs.sakura.ne.jp closed by remote host.
Connection to xx1-234-56789.vs.sakura.ne.jp closed.
% 

6. FTP サーバーのインストール

FTP 接続ができる様にしたいので vsftpd をインストールします。コマンド「sudo apt install vsftpd」を実行します。

ubuntu@ik1-437-50827:~$ sudo apt install vsftpd
ubuntu@ik1-437-50827:~$ 

Cyberduck で接続できました。

7. 開発パッケージのインストール

adminvps@xx1-234-56789:~$ sudo apt install build-essential
adminvps@xx1-234-56789:~$ 

8. Python と関連パッケージのインストール

コマンド「python3 -V」で Ubuntu 20.04 にもともと入っている Python のバージョンを確認します。

adminvps@xx1-234-56789:~$ python3 -V
Python 3.8.5
adminvps@xx1-234-56789:~$

Python 3.8.5 が入っていました。この後 Python 3.9 をインストールします。

依存関係のインストール

Python 自体をインストールする前に必要な依存関係をインストールします。コマンド「sudo apt install 〜」で様々なパッケージをインストールします。

adminvps@xx1-234-56789:~$ sudo apt install zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev libsqlite3-dev wget libbz2-dev
adminvps@xx1-234-56789:~$ 

Python 3.9 のインストール

依存関係のインストールが完了したら、Python 3.9 をインストールします。

adminvps@xx1-234-56789:~$ sudo apt install python3.9
adminvps@xx1-234-56789:~$ 

python3.9-dev のインストール

adminvps@xx1-234-56789:~$ sudo apt install python3.9-dev
adminvps@xx1-234-56789:~$ 

仮想環境(venv)のインストール

Python の仮想環境である venv をインストールするためコマンド「sudo apt-get install python3.9-venv」を実行します。

adminvps@xx1-234-56789:~$ sudo apt-get install python3.9-venv
adminvps@xx1-234-56789:~$ 

9. Web サーバー(Nginx)のインストール

コマンド「sudo apt install nginx」で Nginx をインストールします。

adminvps@xx1-234-56789:~$ sudo apt install nginx
adminvps@xx1-234-56789:~$ 

ブラウザの URL バーにサーバーの IP アドレスを入れてみて、下記のページが出れば無事インストールされています。

とりあえず VPS に Ubuntu が入りPython と Nginx がインストールされた状態です。

他の行程へ

▶︎まずは2週間無料でお試し♪さくらのVPS

【Ubuntu】Python の CaboCha をインストールして形態素解析を行う

Ubuntu 20.04 で CaboCha を使える様にした時の手順を記録しておきます。

  1. MeCab のインストール
  2. mecab-ipadic-neologd のインストール
  3. CRF++ のインストール
  4. CaboCha のインストール
  5. Pythonへのバインディング

まだインストールしていなければ build-essential をインストールします。

$ sudo apt install build-essential

1. MeCab のインストール

$ sudo apt install mecab
$ sudo apt install libmecab-dev
$ sudo apt install mecab-ipadic

この時点でコマンド「mecab」を実行して何か文章を入力すると MeCab が形態素解析をしてくれます。

$ mecab
今日はいい天気ですね。
今日	名詞,副詞可能,*,*,*,*,今日,キョウ,キョー
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
いい	形容詞,自立,*,*,形容詞・イイ,基本形,いい,イイ,イイ
天気	名詞,一般,*,*,*,*,天気,テンキ,テンキ
です	助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
ね	助詞,終助詞,*,*,*,*,ね,ネ,ネ
。	記号,句点,*,*,*,*,。,。,。

2. mecab-ipadic-neologd のインストール

新語に対応した NEologd 辞書(mecab-ipadic-neologd)をインストールします。

$ cd /var/lib/mecab/dic
$ sudo git clone https://github.com/neologd/mecab-ipadic-neologd.git
$ cd mecab-ipadic-neologd
$ sudo bin/install-mecab-ipadic-neologd

「/usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd」にインストールされたので、MeCab 実行時に「-d /usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd」を渡してあげると mecab-ipadic-neologd を辞書として使うことができます。

$ mecab -d /usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd
霜降り明星が好きです。
霜降り明星	名詞,固有名詞,人名,一般,*,*,霜降り明星,シモフリミョウジョウ,シモフリミョウジョー
が	助詞,格助詞,一般,*,*,*,が,ガ,ガ
好き	名詞,形容動詞語幹,*,*,*,*,好き,スキ,スキ
です	助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
。	記号,句点,*,*,*,*,。,。,。
EOS

上では「霜降り明星」が一つの名詞として認識されていますが、デフォルトの辞書だと下記の様に「霜降り」と「明星」が別々の言葉として認識されています。

$ mecab
霜降り明星が好きです。
霜降り	名詞,一般,*,*,*,*,霜降り,シモフリ,シモフリ
明星	名詞,一般,*,*,*,*,明星,ミョウジョウ,ミョージョー
が	助詞,格助詞,一般,*,*,*,が,ガ,ガ
好き	名詞,形容動詞語幹,*,*,*,*,好き,スキ,スキ
です	助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
。	記号,句点,*,*,*,*,。,。,。
EOS

3. CRF++ のインストール

CRF++-0.58.tar.gz

$ cd
$ wget "https://drive.google.com/uc?export=download&id=0B4y35FiV1wh7QVR6VXJ5dWExSTQ" -O CRF++-0.58.tar.gz
$ tar zxvf CRF++-0.58.tar.gz
$ cd CRF++-0.58
$ ./configure
$ sudo ldconfig
$ rm CRF++-0.58.tar.gz

4. CaboCha のインストール

cabocha-0.68.tar.bz2 をダウンロードします。Google Drive に配置されているので少し複雑です。

$ FILE_ID=0B4y35FiV1wh7SDd1Q1dUQkZQaUU
$ FILE_NAME=cabocha-0.69.tar.bz2
$ curl -sc /tmp/cookie "https://drive.google.com/uc?export=download&id=${FILE_ID}" > /dev/null
$ CODE="$(awk '/_warning_/ {print $NF}' /tmp/cookie)"  
$ curl -Lb /tmp/cookie "https://drive.google.com/uc?export=download&confirm=${CODE}&id=${FILE_ID}" -o ${FILE_NAME}

cabocha-0.68.tar.bz2 を解凍、インストールしていきます。

$ bzip2 -dc cabocha-0.69.tar.bz2 | tar xvf -
$ cd cabocha-0.69
$ ./configure --with-mecab-config=`which mecab-config` --with-charset=UTF8
$ make
$ make check
$ sudo make install
$ sudo ldconfig

先程の MeCab を同じ様に、コマンド「cabocha」を実行して何か文章を入力すると CaboCha が係り受けを出してくれます。

$ cabocha
今日はいい天気ですね。
      今日は---D
          いい-D
    天気ですね。
EOS

5. Pythonへのバインディング

Python の仮想環境を立ち上げた状態で下記を実行します。

$ cd python
$ python setup.py install
$ pip install mecab-python3

下記の通り Python で CaboCha が使える様になりました。

>>> import CaboCha
>>> sentence = '霜降り明星(しもふりみょうじょう)は、2018年『M-1グランプリ』14代目王者。'
>>> c = CaboCha.Parser('-d /usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd')
>>> print(c.parseToString(sentence))
        霜降り明星-----D      
              (しも-D |      
                  ふり-D      
      みょうじょう)は、-----D
                    2018年-D |
           『M-1グランプリ』-D
                  14代目王者。
EOS

【Python】CaboCha のツリーを XML から JSON に変換する

CaboCha のツリーを扱いたいのですがデフォルトでは JSON でのアウトプットがない様なので、xmltodict を利用して XML 形式から JSON 形式に変換します。

XML での表出

まず、XML の表出は下記の様になります。

import CaboCha

c = CaboCha.Parser()

tree = c.parse('今日は天気がとても良いですね。')
xmltree = tree.toString(CaboCha.FORMAT_XML)
print(xmltree)

XML アウトプット

<sentence>
 <chunk id="0" link="3" rel="D" score="-1.359140" head="0" func="1">
  <tok id="0" feature="名詞,副詞可能,*,*,*,*,今日,キョウ,キョー">今日</tok>
  <tok id="1" feature="助詞,係助詞,*,*,*,*,は,ハ,ワ">は</tok>
 </chunk>
 <chunk id="1" link="3" rel="D" score="-1.359140" head="2" func="3">
  <tok id="2" feature="名詞,一般,*,*,*,*,天気,テンキ,テンキ">天気</tok>
  <tok id="3" feature="助詞,格助詞,一般,*,*,*,が,ガ,ガ">が</tok>
 </chunk>
 <chunk id="2" link="3" rel="D" score="-1.359140" head="4" func="4">
  <tok id="4" feature="副詞,助詞類接続,*,*,*,*,とても,トテモ,トテモ">とても</tok>
 </chunk>
 <chunk id="3" link="-1" rel="D" score="0.000000" head="5" func="7">
  <tok id="5" feature="形容詞,自立,*,*,形容詞・アウオ段,基本形,良い,ヨイ,ヨイ">良い</tok>
  <tok id="6" feature="助動詞,*,*,*,特殊・デス,基本形,です,デス,デス">です</tok>
  <tok id="7" feature="助詞,終助詞,*,*,*,*,ね,ネ,ネ">ね</tok>
  <tok id="8" feature="記号,句点,*,*,*,*,。,。,。">。</tok>
 </chunk>
</sentence>

JSON での表出

xmltodict を使うので、インストールしていない場合はコマンド「pip install xmltodict」でインストールしてください。

import CaboCha
import xmltodict
import json

c = CaboCha.Parser()

tree = c.parse('今日は天気がとても良いですね。')
xmltree = tree.toString(CaboCha.FORMAT_XML)
jsonobj = xmltodict.parse(xmltree, attr_prefix='', cdata_key='surface', dict_constructor=dict)
print(json.dumps(jsonobj, indent=2, ensure_ascii=False))

JSON アウトプット

{
  "sentence": {
    "chunk": [
      {
        "id": "0",
        "link": "3",
        "rel": "D",
        "score": "-1.359140",
        "head": "0",
        "func": "1",
        "tok": [
          {
            "id": "0",
            "feature": "名詞,副詞可能,*,*,*,*,今日,キョウ,キョー",
            "surface": "今日"
          },
          {
            "id": "1",
            "feature": "助詞,係助詞,*,*,*,*,は,ハ,ワ",
            "surface": "は"
          }
        ]
      },
      {
        "id": "1",
        "link": "3",
        "rel": "D",
        "score": "-1.359140",
        "head": "2",
        "func": "3",
        "tok": [
          {
            "id": "2",
            "feature": "名詞,一般,*,*,*,*,天気,テンキ,テンキ",
            "surface": "天気"
          },
          {
            "id": "3",
            "feature": "助詞,格助詞,一般,*,*,*,が,ガ,ガ",
            "surface": "が"
          }
        ]
      },
      {
        "id": "2",
        "link": "3",
        "rel": "D",
        "score": "-1.359140",
        "head": "4",
        "func": "4",
        "tok": {
          "id": "4",
          "feature": "副詞,助詞類接続,*,*,*,*,とても,トテモ,トテモ",
          "surface": "とても"
        }
      },
      {
        "id": "3",
        "link": "-1",
        "rel": "D",
        "score": "0.000000",
        "head": "5",
        "func": "7",
        "tok": [
          {
            "id": "5",
            "feature": "形容詞,自立,*,*,形容詞・アウオ段,基本形,良い,ヨイ,ヨイ",
            "surface": "良い"
          },
          {
            "id": "6",
            "feature": "助動詞,*,*,*,特殊・デス,基本形,です,デス,デス",
            "surface": "です"
          },
          {
            "id": "7",
            "feature": "助詞,終助詞,*,*,*,*,ね,ネ,ネ",
            "surface": "ね"
          },
          {
            "id": "8",
            "feature": "記号,句点,*,*,*,*,。,。,。",
            "surface": "。"
          }
        ]
      }
    ]
  }
}

さらに改良

上記でも JSON 形式で返ってきますが、chunk や tok 要素の中身が 1 つしかない時にリスト形式になっていない、feature がカンマ区切りの文字列(リスト形式でない)になっているなど少し不便です。

下記の様に処理を追加するとフォーマットを揃えることができます。

import CaboCha
import xmltodict
import json

c = CaboCha.Parser()

tree = c.parse('今日は天気がとても良いですね。')
xmltree = tree.toString(CaboCha.FORMAT_XML)
jsonobj = xmltodict.parse(xmltree, attr_prefix='', cdata_key='surface', dict_constructor=dict)

# 追記分 ↓
if jsonobj['sentence']: # sentence が存在する際に処理を行う
    if type(jsonobj['sentence']['chunk']) is not list: # chunk を必ずリスト形式にする
        jsonobj['sentence']['chunk'] = [jsonobj['sentence']['chunk']]
    
    for chunk in jsonobj['sentence']['chunk']:
        if type(chunk['tok']) is not list: # tok を必ずリスト形式にする
            chunk['tok'] = [chunk['tok']]
        
        for tok in chunk['tok']:
            feature_list = tok['feature'].split(',') # feature をリスト形式に変換
            tok['feature'] = feature_list
# 追記分 ↑

print(json.dumps(jsonobj, indent=2, ensure_ascii=False))

JSON アウトプット ver 2

{
  "sentence": {
    "chunk": [
      {
        "id": "0",
        "link": "3",
        "rel": "D",
        "score": "-1.359140",
        "head": "0",
        "func": "1",
        "tok": [
          {
            "id": "0",
            "feature": [
              "名詞",
              "副詞可能",
              "*",
              "*",
              "*",
              "*",
              "今日",
              "キョウ",
              "キョー"
            ],
            "surface": "今日"
          },
          {
            "id": "1",
            "feature": [
              "助詞",
              "係助詞",
              "*",
              "*",
              "*",
              "*",
              "は",
              "ハ",
              "ワ"
            ],
            "surface": "は"
          }
        ]
      },
      {
        "id": "1",
        "link": "3",
        "rel": "D",
        "score": "-1.359140",
        "head": "2",
        "func": "3",
        "tok": [
          {
            "id": "2",
            "feature": [
              "名詞",
              "一般",
              "*",
              "*",
              "*",
              "*",
              "天気",
              "テンキ",
              "テンキ"
            ],
            "surface": "天気"
          },
          {
            "id": "3",
            "feature": [
              "助詞",
              "格助詞",
              "一般",
              "*",
              "*",
              "*",
              "が",
              "ガ",
              "ガ"
            ],
            "surface": "が"
          }
        ]
      },
      {
        "id": "2",
        "link": "3",
        "rel": "D",
        "score": "-1.359140",
        "head": "4",
        "func": "4",
        "tok": [
          {
            "id": "4",
            "feature": [
              "副詞",
              "助詞類接続",
              "*",
              "*",
              "*",
              "*",
              "とても",
              "トテモ",
              "トテモ"
            ],
            "surface": "とても"
          }
        ]
      },
      {
        "id": "3",
        "link": "-1",
        "rel": "D",
        "score": "0.000000",
        "head": "5",
        "func": "7",
        "tok": [
          {
            "id": "5",
            "feature": [
              "形容詞",
              "自立",
              "*",
              "*",
              "形容詞・アウオ段",
              "基本形",
              "良い",
              "ヨイ",
              "ヨイ"
            ],
            "surface": "良い"
          },
          {
            "id": "6",
            "feature": [
              "助動詞",
              "*",
              "*",
              "*",
              "特殊・デス",
              "基本形",
              "です",
              "デス",
              "デス"
            ],
            "surface": "です"
          },
          {
            "id": "7",
            "feature": [
              "助詞",
              "終助詞",
              "*",
              "*",
              "*",
              "*",
              "ね",
              "ネ",
              "ネ"
            ],
            "surface": "ね"
          },
          {
            "id": "8",
            "feature": [
              "記号",
              "句点",
              "*",
              "*",
              "*",
              "*",
              "。",
              "。",
              "。"
            ],
            "surface": "。"
          }
        ]
      }
    ]
  }
}

【Mac】Python の CaboCha をインストールして係り受け解析を行う

Mac 環境で Python の CaboCha を使って係り受け解析を行う方法を紹介します。

  1. MeCab、CRF++、CaboCha のインストール
    • MeCab のインストール
    • CRF++ と CaboCha のインストール
  2. CaboCha を使ってみる(Python 経由ではない)
  3. CaboCha の Python バインディング
    • 新たに仮想環境を作った場合
  4. Python で CaboCha を使う
    • 係り受け関係の出力
    • 形態素の出力
  5. NEologd 辞書で新語対応
    • NEologd 辞書のインストール
    • NEologd 辞書を使う

1. MeCab、CRF++、CaboCha のインストール

まず MeCab、CRF++ そして CaboCha をインストールするので、Python 仮想環境を起動した状態で下記を実行します。

MeCab のインストール

% brew install mecab
% brew install mecab-ipadic
% pip install mecab-python3

CRF++ と CaboCha のインストール

% brew install crf++
% brew install cabocha

2. CaboCha を使ってみる(Python 経由ではない)

上記をインストールするとターミナルで直接であれば CaboCha が使える様になります。

コマンド「cabocha」を実行してそのまま「今日は良い天気ですね。」と入力すると下記の様に出力されます。

% cabocha
今日は良い天気ですね。
      今日は---D
          良い-D
    天気ですね。
EOS

ただ、ここまでは Python を立ち上げずに直接 Shellscript で CaboCha を使っただけです。

3. CaboCha の Python バインディング

cabocha-0.69.tar.bz2 のリンクがあるのでここからダウンロードします。

Downloads フォルダに圧縮ファイルがありますね。

% cd Users/ユーザー名/Downloads
% ls
cabocha-0.69.tar.bz2

ファイルを解凍して、configure、make、make install を行います。

% tar xfv cabocha-0.69.tar.bz2
% cd cabocha-0.69
% ./configure --prefix=/usr/local/cabocha/0_69 --with-charset=UTF8 --with-posset=IPA
% make
% make install

Python の仮想環境を立ち上げた状態で「cabocha-0.69」直下の「python」フォルダに移動して「sudo python setup.py install」を実行します。

% cd python
% sudo python setup.py install

こうすると import CaboCha できる様になります。

ただ、Downloads フォルダから cabocha-0.69 ファイルを削除してもできる意味をまだいまいち理解できていません。仮想環境の site-packages に CaboCha.py は作られたんですけどそれで間に合ってるんですかね。後でログをよくみてみます。。。

追加の仮想環境を作った場合

すでに一度上記の行程を経て CaboCha を使っている場合、新たに追加の仮想環境を作る際にはいくつか行程を飛ばすことができます。

解凍した「cabocha-0.69」がある状態で「cabocha-0.69/python」ディレクトリに入り、新たに作った仮想環境を起動し、下記を実行すれば OK です。

% pip install mecab-python3
% cd cabocha-0.69/python
% sudo python setup.py install

4. Python で CaboCha を使う

とりあえず Python を立ち上げて「import CaboCha」もできますし下記の処理も実行できました。

>>> import CaboCha
>>> c = CaboCha.Parser()
>>> sentence = '今日は良い天気ですね。'
>>> print(c.parseToString(sentence))
      今日は---D
          良い-D
    天気ですね。
EOS

係り受け関係の出力

>>> tree =  c.parse(sentence)
>>> print(tree.toString(CaboCha.FORMAT_TREE))
      今日は---D
          良い-D
    天気ですね。
EOS

>>> print(tree.toString(CaboCha.FORMAT_LATTICE))
* 0 2D 0/1 -1.140323
今日	名詞,副詞可能,*,*,*,*,今日,キョウ,キョー
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
* 1 2D 0/0 -1.140323
良い	形容詞,自立,*,*,形容詞・アウオ段,基本形,良い,ヨイ,ヨイ
* 2 -1D 0/2 0.000000
天気	名詞,一般,*,*,*,*,天気,テンキ,テンキ
です	助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
ね	助詞,終助詞,*,*,*,*,ね,ネ,ネ
。	記号,句点,*,*,*,*,。,。,。
EOS

形態素の出力

形態素の文字列

>>> for i in range(tree.size()):
...     print(tree.token(i).surface)
... 
今日
は
良い
天気
です
ね
。
>>> 

形態素の情報

>>> for i in range(tree.size()):
...     print(tree.token(i).feature)
... 
名詞,副詞可能,*,*,*,*,今日,キョウ,キョー
助詞,係助詞,*,*,*,*,は,ハ,ワ
形容詞,自立,*,*,形容詞・アウオ段,基本形,良い,ヨイ,ヨイ
名詞,一般,*,*,*,*,天気,テンキ,テンキ
助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
助詞,終助詞,*,*,*,*,ね,ネ,ネ
記号,句点,*,*,*,*,。,。,。
>>> 

5. NEologd 辞書で新語対応

デフォルトでは「IPA 辞書」という辞書が使用されますが、新語に対応するには「NEologd 辞書」が多く使用されている様です。

NEologd 辞書のインストール

通常の辞書「ipadic」が格納されているディレクトリに移動します。おそらく「/usr/local/lib/mecab/dic」もしくはそれに似た場所にあると思います。

% /usr/local/lib/mecab/dic
% ls
ipadic

git clone で「mecab-ipadic-neologd」を作成します。

% git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
% ls
ipadic			mecab-ipadic-neologd

「mecab-ipadic-neologd」フォルダに移動し、コマンド「./bin/install-mecab-ipadic-neologd -n -a」を実行します。

% cd mecab-ipadic-neologd
% ./bin/install-mecab-ipadic-neologd -n -a

途中「Do you want to install mecab-ipadic-NEologd? Type yes or no.」と聞かれるので「yes」と入力します。

これでインストール完了です。

NEologd 辞書を使う

CaboCha、MeCab を使用する際、デフォルトでは IPA 辞書が使用されるので、明示的に NEologd 辞書を指定する必要があります。

実行時に「-d /usr/local/lib/mecab/dic/mecab-ipadic-neologd」を渡すのですが、下記コードの様に「CaboCha.Parser('-d /usr/local/lib/mecab/dic/mecab-ipadic-neologd')」としてあげれば OK です。

import CaboCha

sentence = '霜降り明星(しもふりみょうじょう)は、2018年『M-1グランプリ』14代目王者。'

# IPA 辞書
c = CaboCha.Parser()
print('IPA 辞書:')
print(c.parseToString(sentence))

# NEologd 辞書
c = CaboCha.Parser('-d /usr/local/lib/mecab/dic/mecab-ipadic-neologd')
print('NEologd 辞書:')
print(c.parseToString(sentence))

上記を実行すると下記のアウトプットが返ってきます。

IPA 辞書:
      霜降り明星---D            
            (しも-D            
              ふりみ-D          
      ょうじょう)は、---------D
                  2018年---D   |
                      『M--D   |
               1グランプリ』-D |
                        14代目-D
                          王者。
EOS

NEologd 辞書:
        霜降り明星-----D      
              (しも-D |      
                  ふり-D      
      みょうじょう)は、-----D
                    2018年-D |
           『M-1グランプリ』-D
                  14代目王者。
EOS

「しもふりみょうじょう」や「M-1グランプリ」の部分が若干違いますね。

若干応用編として、YouTube のコメント欄を取得して形態素解析をしてみましたので下記に貼っておきます。

▶︎【Mac】Python の MeCab で YouTube コメントを 形態素解析にかける

【Mac】Python の MeCab で YouTube コメントを 形態素解析にかける

せっかく YouTube Data API でコメントを抽出できるので、今回は YouTube のコメント抽出の方法の紹介とともに、コメント欄にどんな単語が出てくるのか Python の MeCab で形態素解析をしたいと思います。

とりあえず Mac ローカル環境で触りだけやってみます。

  1. YouTube Data API でコメント情報を抽出
  2. YouTube 動画のコメントを MeCab で処理する

ちなみに API キーの取得やライブラリのインストールがまだの場合は下記記事をどうぞ。

1. YouTube Data API でコメント情報を抽出

まずはコメント情報を抽出するところから。コメント関連では CommentsThreads と Comments の二つがあり、両方とも JSON 形式で取得する事ができます。

CommentThreads

まず、CommentThreads では動画 ID やチャンネル ID をもとに、それらの ID に紐づくコメントを抽出することができます。

例えば動画 ID 「fdsaZ8EMR2U」のコメント 5 件のデータを取る場合はこんな感じ。

# -*- coding: utf-8 -*-

# Sample Python code for youtube.commentThreads.list
# See instructions for running these code samples locally:
# https://developers.google.com/explorer-help/guides/code_samples#python

import os

import googleapiclient.discovery

def main():
    # Disable OAuthlib's HTTPS verification when running locally.
    # *DO NOT* leave this option enabled in production.
    os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"

    api_service_name = "youtube"
    api_version = "v3"
    DEVELOPER_KEY = "YOUR_API_KEY"

    youtube = googleapiclient.discovery.build(
        api_service_name, api_version, developerKey = DEVELOPER_KEY)

    request = youtube.commentThreads().list(
        part="id,replies,snippet",
        maxResults=5,
        videoId="fdsaZ8EMR2U"
    )
    response = request.execute()

    print(response)

if __name__ == "__main__":
    main()

下記の様な JSON が返ってきます。コメントの内容、投稿主のチャンネル名などが含まれています。そして「replies」にはコメントに対する返信も含まれます。

{
  "kind": "youtube#commentThreadListResponse",
  "etag": "tSw5WSiFS4IMMytcgoYXJ9zpu6I",
  "nextPageToken": "QURTSl9pMDFKVnZtTFl0NFZOdnhaZFpXaFBOcWU5aDA0QWM5bDVpYk5oVTd1WDQwSDY1cU11OVBOZHNnWFNOTmNJby1Db1JpWno2Qnd5bw==",
  "pageInfo": {
    "totalResults": 5,
    "resultsPerPage": 5
  },
  "items": [
    {
      "kind": "youtube#commentThread",
      "etag": "An3zz04lgE7jUVO7VXXmqfdwFjk",
      "id": "UgwDY44NUll4uiZXqqx4AaABAg",
      "snippet": {
        "videoId": "fdsaZ8EMR2U",
        "topLevelComment": {
          "kind": "youtube#comment",
          "etag": "Kty1w2F4dTbXmakl-ywdK28vLEg",
          "id": "UgwDY44NUll4uiZXqqx4AaABAg",
          "snippet": {
            "videoId": "fdsaZ8EMR2U",
            "textDisplay": "SO gooood",
            "textOriginal": "SO gooood",
            "authorDisplayName": "fatt musiek",
            "authorProfileImageUrl": "https://yt3.ggpht.com/ytc/AAUvwngpQ-20jVq0c-9aC-wDJ87aTKi2QvPLTRN2GXGRaw=s48-c-k-c0x00ffffff-no-rj",
            "authorChannelUrl": "http://www.youtube.com/channel/UCl3ha3zwY9p6CemIZZXIdXQ",
            "authorChannelId": {
              "value": "UCl3ha3zwY9p6CemIZZXIdXQ"
            },
            "canRate": true,
            "viewerRating": "none",
            "likeCount": 0,
            "publishedAt": "2021-05-22T18:48:34Z",
            "updatedAt": "2021-05-22T18:48:34Z"
          }
        },
        "canReply": true,
        "totalReplyCount": 0,
        "isPublic": true
      }
    },
    {
      "kind": "youtube#commentThread",
      "etag": "QVJH5RHTNij1fN5jRj_mNcDscHA",
      "id": "Ugx8sUuwqKqPVG9eSuJ4AaABAg",
      "snippet": {
        "videoId": "fdsaZ8EMR2U",
        "topLevelComment": {
          "kind": "youtube#comment",
          "etag": "Y_SsBrGGxQoLpcztQqND9wGarUc",
          "id": "Ugx8sUuwqKqPVG9eSuJ4AaABAg",
          "snippet": {
            "videoId": "fdsaZ8EMR2U",
            "textDisplay": "so what if really yuffie have met johnny hehe",
            "textOriginal": "so what if really yuffie have met johnny hehe",
            "authorDisplayName": "GregOrio Barachina",
            "authorProfileImageUrl": "https://yt3.ggpht.com/ytc/AAUvwnjgJE6zBYksYQWt8TmKlMDYOyG0t-BHPNWWmvUUPQ=s48-c-k-c0x00ffffff-no-rj",
            "authorChannelUrl": "http://www.youtube.com/channel/UCUs2OJ4-KqYGS2EPJCDj7tQ",
            "authorChannelId": {
              "value": "UCUs2OJ4-KqYGS2EPJCDj7tQ"
            },
            "canRate": true,
            "viewerRating": "none",
            "likeCount": 0,
            "publishedAt": "2021-05-21T13:42:45Z",
            "updatedAt": "2021-05-21T13:42:45Z"
          }
        },
        "canReply": true,
        "totalReplyCount": 0,
        "isPublic": true
      }
    },
    {
      "kind": "youtube#commentThread",
      "etag": "ggLtp9jtNyrqb3JSvzkvUDon7gg",
      "id": "UgwP-4ucsrWh_iXJQMN4AaABAg",
      "snippet": {
        "videoId": "fdsaZ8EMR2U",
        "topLevelComment": {
          "kind": "youtube#comment",
          "etag": "Raxyf3_Zw3ksZyGeWDt7_HHW8SA",
          "id": "UgwP-4ucsrWh_iXJQMN4AaABAg",
          "snippet": {
            "videoId": "fdsaZ8EMR2U",
            "textDisplay": "The Aerith and Cloud scene is much more meaningful than the one with Tifa. Still a good scene but come on...Aerith just appearing amongst the flowers and getting to see her again...priceless",
            "textOriginal": "The Aerith and Cloud scene is much more meaningful than the one with Tifa. Still a good scene but come on...Aerith just appearing amongst the flowers and getting to see her again...priceless",
            "authorDisplayName": "Maxx Doran",
            "authorProfileImageUrl": "https://yt3.ggpht.com/ytc/AAUvwnixfMDBxLt_TfUEjlpHhU-OvwE1vjCgpFBAVIMxjg=s48-c-k-c0x00ffffff-no-rj",
            "authorChannelUrl": "http://www.youtube.com/channel/UCXcLTX_9fNHLVAMr_plxeqQ",
            "authorChannelId": {
              "value": "UCXcLTX_9fNHLVAMr_plxeqQ"
            },
            "canRate": true,
            "viewerRating": "none",
            "likeCount": 0,
            "publishedAt": "2021-05-18T01:50:04Z",
            "updatedAt": "2021-05-18T01:50:04Z"
          }
        },
        "canReply": true,
        "totalReplyCount": 0,
        "isPublic": true
      }
    },
    {
      "kind": "youtube#commentThread",
      "etag": "ptUVfOGBkZDnUXKFoDaGnQ9Y-gw",
      "id": "UgzTN_4ek7syWNNbCrB4AaABAg",
      "snippet": {
        "videoId": "fdsaZ8EMR2U",
        "topLevelComment": {
          "kind": "youtube#comment",
          "etag": "L3vDroBKOAklgIqKSkcX_JvLn_g",
          "id": "UgzTN_4ek7syWNNbCrB4AaABAg",
          "snippet": {
            "videoId": "fdsaZ8EMR2U",
            "textDisplay": "You caught on to the magnify barrier idea so early. I was part way into hard mode before I thought of that.",
            "textOriginal": "You caught on to the magnify barrier idea so early. I was part way into hard mode before I thought of that.",
            "authorDisplayName": "Justin Edwards",
            "authorProfileImageUrl": "https://yt3.ggpht.com/ytc/AAUvwngz1mU5zD3QHSRVU3jXTEZApnkYsmAzCKFXxUyD1w=s48-c-k-c0x00ffffff-no-rj",
            "authorChannelUrl": "http://www.youtube.com/channel/UCO-oPQJCpNw87M6YbcuuFMw",
            "authorChannelId": {
              "value": "UCO-oPQJCpNw87M6YbcuuFMw"
            },
            "canRate": true,
            "viewerRating": "none",
            "likeCount": 0,
            "publishedAt": "2021-05-10T07:25:36Z",
            "updatedAt": "2021-05-10T07:25:36Z"
          }
        },
        "canReply": true,
        "totalReplyCount": 0,
        "isPublic": true
      }
    },
    {
      "kind": "youtube#commentThread",
      "etag": "vfaqu09YbjpC_akz6riq0_XpSCw",
      "id": "UgygOOysmSAraKnx81h4AaABAg",
      "snippet": {
        "videoId": "fdsaZ8EMR2U",
        "topLevelComment": {
          "kind": "youtube#comment",
          "etag": "k8vIS0anrGkqCcFfyj0gnrUwXQI",
          "id": "UgygOOysmSAraKnx81h4AaABAg",
          "snippet": {
            "videoId": "fdsaZ8EMR2U",
            "textDisplay": "Y'know Max, you COULD have just run 5k steps in Aerith's garden, checked what the Materia did, and moved on. Or, maybe, look up an online guide, since by now I'm sure SOMEONE has posted one.",
            "textOriginal": "Y'know Max, you COULD have just run 5k steps in Aerith's garden, checked what the Materia did, and moved on. Or, maybe, look up an online guide, since by now I'm sure SOMEONE has posted one.",
            "authorDisplayName": "Soma Cruz the Demigod of Balance",
            "authorProfileImageUrl": "https://yt3.ggpht.com/ytc/AAUvwnilg2dkOBvJqeTbW34CBoxURHLWv78fnbCRkArv=s48-c-k-c0x00ffffff-no-rj",
            "authorChannelUrl": "http://www.youtube.com/channel/UCNaiemmWvNbzfaQm5e3hyqA",
            "authorChannelId": {
              "value": "UCNaiemmWvNbzfaQm5e3hyqA"
            },
            "canRate": true,
            "viewerRating": "none",
            "likeCount": 0,
            "publishedAt": "2021-05-07T02:21:29Z",
            "updatedAt": "2021-05-07T02:21:29Z"
          }
        },
        "canReply": true,
        "totalReplyCount": 1,
        "isPublic": true
      },
      "replies": {
        "comments": [
          {
            "kind": "youtube#comment",
            "etag": "oMSJ1drDJreTmguX72NWydzfbcY",
            "id": "UgygOOysmSAraKnx81h4AaABAg.9N10GKEX1209N2h_vQGc9Y",
            "snippet": {
              "videoId": "fdsaZ8EMR2U",
              "textDisplay": "\u003ca href=\"https://www.youtube.com/watch?v=fdsaZ8EMR2U&t=38m03s\"\u003e38:03\u003c/a\u003e These things don't stagger? But each time they clone, they lose health, and the clones are much weaker. Damn, I see why you had trouble with these, Max.",
              "textOriginal": "38:03 These things don't stagger? But each time they clone, they lose health, and the clones are much weaker. Damn, I see why you had trouble with these, Max.",
              "parentId": "UgygOOysmSAraKnx81h4AaABAg",
              "authorDisplayName": "Soma Cruz the Demigod of Balance",
              "authorProfileImageUrl": "https://yt3.ggpht.com/ytc/AAUvwnilg2dkOBvJqeTbW34CBoxURHLWv78fnbCRkArv=s48-c-k-c0x00ffffff-no-rj",
              "authorChannelUrl": "http://www.youtube.com/channel/UCNaiemmWvNbzfaQm5e3hyqA",
              "authorChannelId": {
                "value": "UCNaiemmWvNbzfaQm5e3hyqA"
              },
              "canRate": true,
              "viewerRating": "none",
              "likeCount": 0,
              "publishedAt": "2021-05-07T18:08:01Z",
              "updatedAt": "2021-05-07T18:08:01Z"
            }
          }
        ]
      }
    }
  ]
}

Comments

Comments ではコメント ID を直接指定してデータを取得します。上の例で取得した 5 つのコメント ID を「id」に指定してデータを取得してみます。

# -*- coding: utf-8 -*-

# Sample Python code for youtube.comments.list
# See instructions for running these code samples locally:
# https://developers.google.com/explorer-help/guides/code_samples#python

import os

import googleapiclient.discovery

def main():
    # Disable OAuthlib's HTTPS verification when running locally.
    # *DO NOT* leave this option enabled in production.
    os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"

    api_service_name = "youtube"
    api_version = "v3"
    DEVELOPER_KEY = "YOUR_API_KEY"

    youtube = googleapiclient.discovery.build(
        api_service_name, api_version, developerKey = DEVELOPER_KEY)

    request = youtube.comments().list(
        part="id,snippet",
        id="UgwDY44NUll4uiZXqqx4AaABAg,Ugx8sUuwqKqPVG9eSuJ4AaABAg,UgwP-4ucsrWh_iXJQMN4AaABAg,UgzTN_4ek7syWNNbCrB4AaABAg,UgygOOysmSAraKnx81h4AaABAg"
    )
    response = request.execute()

    print(response)

if __name__ == "__main__":
    main()

下記の様な JSON が返ってきます。CommentThreads の方ではコメントが投稿された動画の ID や、コメントに対する返信も含まれていましたが、Comments の方には含まれません。

{
  "kind": "youtube#commentListResponse",
  "etag": "vwaB3KAa_Snb_GuTkkMYrlL7Jrg",
  "items": [
    {
      "kind": "youtube#comment",
      "etag": "E9ovRZPTGOUQzHb0AEiKA26EJxY",
      "id": "UgwDY44NUll4uiZXqqx4AaABAg",
      "snippet": {
        "textDisplay": "SO gooood",
        "textOriginal": "SO gooood",
        "authorDisplayName": "fatt musiek",
        "authorProfileImageUrl": "https://yt3.ggpht.com/ytc/AAUvwngpQ-20jVq0c-9aC-wDJ87aTKi2QvPLTRN2GXGRaw=s48-c-k-c0x00ffffff-no-rj",
        "authorChannelUrl": "http://www.youtube.com/channel/UCl3ha3zwY9p6CemIZZXIdXQ",
        "authorChannelId": {
          "value": "UCl3ha3zwY9p6CemIZZXIdXQ"
        },
        "canRate": true,
        "viewerRating": "none",
        "likeCount": 0,
        "publishedAt": "2021-05-22T18:48:34Z",
        "updatedAt": "2021-05-22T18:48:34Z"
      }
    },
    {
      "kind": "youtube#comment",
      "etag": "FG5oWvmMF39kDNl_rlnzb0bsSWM",
      "id": "Ugx8sUuwqKqPVG9eSuJ4AaABAg",
      "snippet": {
        "textDisplay": "so what if really yuffie have met johnny hehe",
        "textOriginal": "so what if really yuffie have met johnny hehe",
        "authorDisplayName": "GregOrio Barachina",
        "authorProfileImageUrl": "https://yt3.ggpht.com/ytc/AAUvwnjgJE6zBYksYQWt8TmKlMDYOyG0t-BHPNWWmvUUPQ=s48-c-k-c0x00ffffff-no-rj",
        "authorChannelUrl": "http://www.youtube.com/channel/UCUs2OJ4-KqYGS2EPJCDj7tQ",
        "authorChannelId": {
          "value": "UCUs2OJ4-KqYGS2EPJCDj7tQ"
        },
        "canRate": true,
        "viewerRating": "none",
        "likeCount": 0,
        "publishedAt": "2021-05-21T13:42:45Z",
        "updatedAt": "2021-05-21T13:42:45Z"
      }
    },
    {
      "kind": "youtube#comment",
      "etag": "WQw2UyILXAhkYOAl-AKScZi1pCY",
      "id": "UgwP-4ucsrWh_iXJQMN4AaABAg",
      "snippet": {
        "textDisplay": "The Aerith and Cloud scene is much more meaningful than the one with Tifa. Still a good scene but come on...Aerith just appearing amongst the flowers and getting to see her again...priceless",
        "textOriginal": "The Aerith and Cloud scene is much more meaningful than the one with Tifa. Still a good scene but come on...Aerith just appearing amongst the flowers and getting to see her again...priceless",
        "authorDisplayName": "Maxx Doran",
        "authorProfileImageUrl": "https://yt3.ggpht.com/ytc/AAUvwnixfMDBxLt_TfUEjlpHhU-OvwE1vjCgpFBAVIMxjg=s48-c-k-c0x00ffffff-no-rj",
        "authorChannelUrl": "http://www.youtube.com/channel/UCXcLTX_9fNHLVAMr_plxeqQ",
        "authorChannelId": {
          "value": "UCXcLTX_9fNHLVAMr_plxeqQ"
        },
        "canRate": true,
        "viewerRating": "none",
        "likeCount": 0,
        "publishedAt": "2021-05-18T01:50:04Z",
        "updatedAt": "2021-05-18T01:50:04Z"
      }
    },
    {
      "kind": "youtube#comment",
      "etag": "Vchl7kutnRgZb-uKYjMNMrJQ2qQ",
      "id": "UgzTN_4ek7syWNNbCrB4AaABAg",
      "snippet": {
        "textDisplay": "You caught on to the magnify barrier idea so early. I was part way into hard mode before I thought of that.",
        "textOriginal": "You caught on to the magnify barrier idea so early. I was part way into hard mode before I thought of that.",
        "authorDisplayName": "Justin Edwards",
        "authorProfileImageUrl": "https://yt3.ggpht.com/ytc/AAUvwngz1mU5zD3QHSRVU3jXTEZApnkYsmAzCKFXxUyD1w=s48-c-k-c0x00ffffff-no-rj",
        "authorChannelUrl": "http://www.youtube.com/channel/UCO-oPQJCpNw87M6YbcuuFMw",
        "authorChannelId": {
          "value": "UCO-oPQJCpNw87M6YbcuuFMw"
        },
        "canRate": true,
        "viewerRating": "none",
        "likeCount": 0,
        "publishedAt": "2021-05-10T07:25:36Z",
        "updatedAt": "2021-05-10T07:25:36Z"
      }
    },
    {
      "kind": "youtube#comment",
      "etag": "pbfhdpIgB5QCKnr4Inkm_U2wbjQ",
      "id": "UgygOOysmSAraKnx81h4AaABAg",
      "snippet": {
        "textDisplay": "Y'know Max, you COULD have just run 5k steps in Aerith's garden, checked what the Materia did, and moved on. Or, maybe, look up an online guide, since by now I'm sure SOMEONE has posted one.",
        "textOriginal": "Y'know Max, you COULD have just run 5k steps in Aerith's garden, checked what the Materia did, and moved on. Or, maybe, look up an online guide, since by now I'm sure SOMEONE has posted one.",
        "authorDisplayName": "Soma Cruz the Demigod of Balance",
        "authorProfileImageUrl": "https://yt3.ggpht.com/ytc/AAUvwnilg2dkOBvJqeTbW34CBoxURHLWv78fnbCRkArv=s48-c-k-c0x00ffffff-no-rj",
        "authorChannelUrl": "http://www.youtube.com/channel/UCNaiemmWvNbzfaQm5e3hyqA",
        "authorChannelId": {
          "value": "UCNaiemmWvNbzfaQm5e3hyqA"
        },
        "canRate": true,
        "viewerRating": "none",
        "likeCount": 0,
        "publishedAt": "2021-05-07T02:21:29Z",
        "updatedAt": "2021-05-07T02:21:29Z"
      }
    }
  ]
}

2. YouTube 動画のコメントを MeCab で処理する

次に、抽出したコメントに対して形態素解析をかけたいと思います。

まず仮想環境で下記を実行し、MeCab を使える様準備します。

$ brew install mecab
$ brew install mecab-ipadic
$ pip install mecab-python3

基本編:単一コメント取得〜形態素解析

MeCab の準備が済んだら下記の様に

import os
import googleapiclient.discovery
import MeCab

def main():
    # Disable OAuthlib's HTTPS verification when running locally.
    # *DO NOT* leave this option enabled in production.
    os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"

    api_service_name = "youtube"
    api_version = "v3"
    DEVELOPER_KEY = "YOUR_API_KEY"

    youtube = googleapiclient.discovery.build(
        api_service_name, api_version, developerKey = DEVELOPER_KEY)

    # コメント ID を指定してコメントを取得
    request = youtube.comments().list(
        part="id,snippet",
        id="UgzKZhtc3fX6JgX6p5p4AaABAg" # コメント ID 
    )
    response = request.execute()

    # 返された JSON からコメントの文章を取得し text に保存
    text = response['items'][0]['snippet']['textOriginal']
    
    m = MeCab.Tagger()
 
    node = m.parseToNode(text)
    text_after = []
    while node:
        words.append(node.surface)
        node = node.next

    print('処理前: '+str(text)) # MeCab 処理前の text
    print('処理後: '+str(text_after)) # MeCab 処理を行なった後の text
    
if __name__ == "__main__":
    main()

狩野英孝さんの動画のコメントで、上記のコードを実行すると下記の結果を返してくれます。

処理前: ここ最近英孝ちゃんの動画みてたらいつの間にか日付け変わってるんだけど笑
処理前: ['', 'ここ', '最近', '英孝', 'ちゃん', 'の', '動画', 'み', 'て', 'たら', 'いつの間にか', '日', '付け', '変わっ', 'てる', 'ん', 'だ', 'けど', '笑', '']

応用編:複数コメント取得〜頻出単語の表出

YouTube Data API でコメントスレッドを取得し、それを MeCab の処理に欠けて出現数の多い単語を抽出してみます。

import os
import googleapiclient.discovery
import MeCab
import collections

def main():
    # Disable OAuthlib's HTTPS verification when running locally.
    # *DO NOT* leave this option enabled in production.
    os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"

    api_service_name = "youtube"
    api_version = "v3"
    DEVELOPER_KEY = "YOUR_API_KEY"

    youtube = googleapiclient.discovery.build(
        api_service_name, api_version, developerKey = DEVELOPER_KEY)

    # コメントスレッドの取得
    request = youtube.commentThreads().list(
        part="id,replies,snippet",
        maxResults=100, # 最大取得コメントスレッド数
        videoId="jsRR_ZimvAo", # 動画 ID
        order="relevance" # 関連性の高い順にコメントスレッドを取得
    )
    response = request.execute()

    comment_list = []
    for item in response['items']:

        comment_list.append(item['snippet']['topLevelComment']['snippet']['textOriginal'])
        if 'replies' in item.keys():
            for reply in item['replies']['comments']:

                comment_list.append(reply['snippet']['textOriginal'])
    
    # MeCab の処理
    comment_particles = []
    for comment in comment_list:
        m = MeCab.Tagger()
    
        node = m.parseToNode(comment)
        while node:
            if len(node.surface) > 0: # ''は処理から除外
                hinshi = node.feature.split(',')[0]
                if hinshi in ['名詞','形容詞']: # 名詞か形容詞に絞る
                    comment_particles.append(node.surface)
            
            node = node.next

    c = collections.Counter(comment_particles)
    
    # 出現数順に print
    for i in c.most_common(30):
        print(i)


if __name__ == "__main__":
    main()

お笑い芸人のさまぁ〜ずとぺこぱの動画なので下記の様な感じでタプルが返ってきます。

('シュウ', 23)
('ペイ', 23)
('さん', 21)
('笑', 17)
('好き', 11)
('ちゃん', 10)
('シュウペイ', 9)
('の', 9)
('ん', 9)
('ぺこぱ', 9)
('企画', 9)
('お前', 9)
('ツッコミ', 8)
('~', 8)
('寺', 7)
('かわいい', 7)
('最高', 7)
('松陰', 6)
('ショック', 6)
('回', 6)
('純粋', 6)
('人', 6)
('俺', 6)
('いい', 5)
('www', 5)
('w', 5)
('面白い', 5)
('ww', 5)
('❤', 5)
('ずさん', 4)

不自然なところで単語が切れてしまったりするのが気になりますが、とりあえずこんな風なことができるということで!