connpassのイベント参加者リストをcsv形式で取得し、重複する参加者を調べる

##はじめに
connpassの複数のイベントに参加していると、同一の内容を異なるグループのイベントでLTする機会も増えてきます。

**エンジニアをつなぐIT勉強会支援プラットフォーム
https://connpass.com/**

別のイベントではあるものの、同じ参加者がたくさん来られている場合は、同じ内容でLTするのは気が引けますよね。

connpassのイベント主催者は、参加者リストとcsv形式を取得することができます。しかし、一般の参加者は参加者一覧ページを閲覧することはできますが、csv形式では入手することができません。

そこで、指定したイベントの参加者リストをpython3でスクレイピングし、csv形式で出力する処理を作ってみました。

csvファイルの出力後は、イベント(２つのcsvファイル)間で、重複する参加者を出力する処理も用意しました。

GitHubにてソースコードを公開しています。
https://github.com/NobuyukiInoue/get_connpass_participants

参考１：スクレイピング処理に関しての留意事項
・**[Webスクレイピングの注意事項一覧](https://qiita.com/nezuq/items/c5e827e1827e7cb29011)**
・**[connpass利用規約](https://connpass.com/term/)**

参考２：動作確認環境
* Windows10 & Python 3.6.0
* Windows7 & Python 3.6.1
* macOS 10.13.6 & Python 3.6.5
* Ubuntu 16.04.5 LTS & Python 3.7.0

####追記
２つのイベントを(CSVファイルに出力せずに）比較し、重複ユーザーを表示する処理も追加で作成しました。列挙したいだけなら、こちらの方が使い勝手が良いかもしれません。

[connpassの２つイベント間で重複する参加者を調べる]
(https://qiita.com/gx3n-inue/items/ec7bac2fd73466478637)

ソースコード）[get_participant_duplicates.py](https://github.com/NobuyukiInoue/get_connpass_participants/blob/master/get_participant_duplicates.py)

##1. 必要モジュールのインストール
HTMLのparse処理に`beautifulsoup4`を使用しています。
管理者権限で`pip install beautifulsoup4`コマンドを実行し、事前にインストールしておいてください。

##2. 参加者一覧の取得とcsvファイル出力

コンパスの各イベントのURLは下記のような書式になっています。

```
https://グループ名.connpass.com/event/イベントID/
```

「イベント参加者・申込者一覧」ページのURLには、`participation`というサブディレクトリ名が付加されています。

```
https://グループ名.connpass.com/event/イベントID/participation/
```

###2-1. 処理内容
参加者一覧を取得するスクレイピング処理では、この「イベント参加者・申込者一覧」ページにアクセスしてHTMLの応答を取得したあと、
`<A HREF=...>`タグを抽出することにより、ユーザー名およびプロフィールへのリンクを取得しています。

```Python3:get_participant.py
import sys
from datetime import datetime

import urllib.request, urllib.error
from bs4 import BeautifulSoup

def print_arg_error(commandName):
    """引数エラー時のメッセージ出力"""
    print("Usage: python {cmd} connpassURL\n"
          "\n"
          "example)\n"
          "python {cmd} https://GROUP_NAME.connpass.com/event/xxxxx/"
          .format(cmd=commandName))
    exit(0)


def is_url(url):
    """URL書式チェック"""
    return url.startswith("https://") or url.startswith("http://")


def participation_url(url):
    """参加者URLに変換"""
    if url.endswith("/participation/"):
        return url
    if url.endswith("/participation"):
        return url + "/"
    if url.endswith("/"):
        return url + "participation/"
    else:
        return url + "/participation/"


def getgrpName(url, extention):
    """グループ名とイベント番号を取り出してファイル名とする"""

    # url format
    # "https://<groupName>.connpass.com/event/<eventId>/participation/"

    # <groupName>を取り出す
    pos_l1 = url.find("/")
    pos_l2 = url.find("/", pos_l1 + 1)
    pos_l3 = url.find(".")

    if ((pos_l1 < 0) or (pos_l2 < 0) or (pos_l3 < 0)):
        print("groupName error...")
        return ""

    groupName = url[pos_l2 + 1:pos_l3]

    # <eventId>を取り出す
    pos_r1 = url.rfind("/")
    pos_r2 = url.rfind("/", 0, pos_r1 - 1)
    pos_r3 = url.rfind("/", 0, pos_r2 - 1)

    if ((pos_r1 < 0) or (pos_r2 < 0) or (pos_r3 < 0)):
        print("eventId error...")
        return ""

    eventId = url[pos_r3 + 1:pos_r2]

    # 現在の時刻を年、月、日、時、分、秒で取得
    dateStr = datetime.now().strftime("%Y%m%d_%H%M%S")

    return(groupName + "_" + eventId + "_" + dateStr + extention)


def users(tags):
    """ユーザー情報の取得(ユーザ名とユーザURLのCSV形式)"""
    for tag in tags:
        try:
            if tag.text and tag.text != "\n\n":
                href = tag['href']
                if "/open/" in href or "/user/" in href:
                    yield '"{name}","{url}"\n'.format(name=tag.text, url=href)
        except:
            pass


def main():
    args = sys.argv

    if len(args) <= 1:
        print_arg_error(args[0])

    # URL Format
    # "https://xxxxxx.connpass.com/event/xxxxx/"

    # URL書式チェック
    if not is_url(args[1]):
        print("URL Error...%s\n" %args[1])
        print_arg_error(args[0])

    # 参加者URLに変換
    url = participation_url(args[1])

    save_fname = getgrpName(url, ".csv")
    if (save_fname == ""):
        print_arg_error(args[0])

    # 指定したURLの出力htmlを取得する 
    html = urllib.request.urlopen(url)

    # htmlをBeautifulSoupに取り込む
    soup = BeautifulSoup(html, "html.parser")

    # すべての<A>タグを抽出する
    tags = soup.find_all("a")

    # ユーザー情報保存用リストに格納
    ulist = list(users(tags))

    # 抽出したリストを標準出力に表示する
    for user in ulist:
        print(user, end="")

    # ファイルに出力する
    with open(save_fname, mode='w') as f:
        f.writelines(ulist)

    print("\nsave as ... [%s]" %(save_fname))

if __name__ == "__main__":
    main()
```

###2-2. 取得処理の実行

実行時には、`get_participant.py`への引数として、参加者リストを取得したいイベントのURLを指定します。

参加者リストは、実行結果として標準出力に表示されたあと、
`グループ名_イベントID_YYYYmmdd_HHMMSS.csv`というファイル名で自動保存されます。


```PowerShell
PS D:\> python .\get_participant.py https://グループ名.connpass.com/event/イベントID/

"(主催者01)","https://connpass.com/user/(主催者01)/open/"
"(発表者01)","https://connpass.com/user/(発表者01)/presentation/"
"(発表者02)","https://connpass.com/user/(発表者02)/presentation/"
"(参加者01)","https://connpass.com/user/(参加者01)/"
"(参加者02)","https://connpass.com/user/(参加者02)/"
"(参加者03)","https://connpass.com/user/(参加者03)/"
...
...
...
"(参加者29)","https://connpass.com/user/(参加者29)/"
"(参加者30)","https://connpass.com/user/(参加者30)/"

save as ... [グループ名_イベントID_YYYYmmdd_HHMMSS.csv]
PS D:\> 
```

##3. csvファイルの比較
次に、２つのイベント間で重複する参加者を抽出する処理です。

`get_duplicate_user.py`で２つのイベントの参加者リストを読み込ませます。処理の中では、（ユーザー名ではなく）各ユーザーのプロフィールのURLが一致していないかを調べています。一致していた場合、そのレコードが表示されます。
（10行目の`encoding="sjis"`は、必要に応じて書き換えてください。`macOS`や`Linux`では不要かも。）

###3.1. 重複する参加者を抽出する

```Python:get_duplicate_user.py
# coding: cp932

import sys
import os

def getDuplicateUsers(users1, users2):
    """２つのcsv配列を比較し、profile URLが一致するレコードを出力する"""
    urls2 = {user2.split('","')[1] for user2 in users2}
    for user1 in users1:
        name1, url1 = user1.split('","')
        if url1 in urls2:
            yield user1

def main():
    args = sys.argv

    if len(args) <= 2:
        print("Usage: python %s file1 file2" %(args[0]))
        exit()

    if ( os.path.isfile(args[1]) != True ):
        print("%s not found." %(args[1]))
        exit(0)
        
    if ( os.path.isfile(args[2]) != True ):
        print("%s not found." %(args[2]))
        exit(0)

    with open(args[1]) as f:
        users1 = f.readlines()

    with open(args[2]) as f:
        users2 = f.readlines()

    # ２つのcsv配列を比較し、profile URLが一致するレコードを出力する
    print("\n=== Dupulicate users [%s] in [%s] ===" %(args[1], args[2]))
    for user in getDuplicateUsers(users1, users2):
        print(user, end="")

if __name__ == "__main__":
    main()
```

###3.2. 実行例

```PowerShell
PS D:> python .\get_duplicate_user.py .\groupA_xxxxx_20180818_154841.csv .\groupB_yyyyy_20180819_185951.csv

=== Dupulicate users [.\data\groupA_xxxxxx_20180818_154841.csv] in [.\groupB_yyyyy_20180819_185951.csv]
===
"user01","https://connpass.com/user/user01/"
"user02","https://connpass.com/user/user02/"
"user03","https://connpass.com/user/user03/"
"user04","https://connpass.com/user/user04/"

PS D:\>
```