RedmineからチケットをスクレイピングするPythonスクリプトの作例です。
一覧画面でページを遷移しながら、一つのCSVファイルに出力します。
手順に沿って解説していきます。
スクレイピング用URLの生成
Redmineのチケット一覧画面で、スクレイピングで使用するURLを生成します。
画面上をクリックしていくだけなので、難しい知識は必要ありません。
当記事はRedmineの公式サイトを例に解説しています。
フィルタとオプションの適用
フィルタとオプションを使って、抽出したい条件や項目を決めます。
最後に左下の「適用」をクリックして、画面に反映させます。
表示件数とページの指定
次に、画面下のナビゲーションを使って、1ページ当たりの表示件数を調整します。
URLの送信回数を減らしたいので、今回は最大の100件に変更しました。
反映されたら、他のページに移動します。
最初のページ以外なら、どのページでも構いません。
表示されたURLの退避
表示されているURLを、メモ帳などに退避します。
スクリプトで使用します。
https://www.redmine.org/projects/redmine/issues?c%5B%5D=tracker&c%5B%5D=status&c%5B%5D=subject&c%5B%5D=assigned_to&c%5B%5D=updated_on&c%5B%5D=category&f%5B%5D=status_id&f%5B%5D=tracker_id&f%5B%5D=&group_by=&op%5Bstatus_id%5D=o&op%5Btracker_id%5D=%3D&page=3&per_page=100&set_filter=1&utf8=%E2%9C%93&v%5Btracker_id%5D%5B%5D=1
スクリプトの作成
スクリプトの概要
スクリプトの概要は次の通りです。
- テーブルの取り扱いには「Pandas」ライブラリを使用します。
- 一覧画面のページを遷移しながら、チケットをファイルに出力します。
- サーバーへの負荷を考慮して、遷移の間に1秒のスリーブを入れます。
- ファイルは、1ページ目は上書き、2ページ目以降は追記します。
- ファイルへの出力は、次のCSV形式とします。
- ヘッダ行は1ページ目のみ出力します。
- すべてのフィールドをダブルクォーテーションで囲みます。
スクリプトと解説
作成したスクリプトは次の通りです。
重要なところをかいつまんで解説します。
●チケット一覧画面のURL
# チケット一覧画面のURL
url = ('https://www.redmine.org/projects/redmine/issues'
'?c%5B%5D=tracker&c%5B%5D=status&c%5B%5D=subject'
'&c%5B%5D=assigned_to&c%5B%5D=updated_on&c%5B%5D=category'
'&f%5B%5D=status_id&f%5B%5D=tracker_id'
'&f%5B%5D=&group_by=&op%5Bstatus_id%5D=o&op%5Btracker_id%5D=%3D'
# 「page=x」はループ内で正しい値に置き換えます。
'&page=x&per_page=100'
'&set_filter=1&utf8=%E2%9C%93&v%5Btracker_id%5D%5B%5D=1')
先ほどメモ帳などに退避したURL、ほぼそのままです。
チケット一覧画面で思い通りに生成したものを、使用するだけなので簡単です。
但し、ページ指定の箇所は、次のreplaceコマンドで正しい値に置き換えるため「page=x」に変更しています。
# 「page=x」を正しい値に置き換えます。
page_url = url.replace('page=x', f'page={i}')
●データの抽出
dfs = pd.read_html(page_url, attrs={'class':'list issues'}, index_col=0)
CSSセレクタに、チケット一覧画面で確認したClass属性の「list issues」を指定しています。たったこれだけで、ページのチケットがDataFrameオブジェクトにして返されるなんてホント凄いことです(;゚Д゚)
●ファイル出力
if i == 1: # 1ページ目:すべて囲む、ファイル上書き、ヘッダ行あり、インデックスなし、改行LF dfs[0].to_csv('issues.csv', quoting=csv.QUOTE_ALL, mode='w', header=True, index=False, line_terminator='\n') else: # 2ページ目以降:すべて囲む、ファイル追記、ヘッダ行なし、インデックスなし、改行LF dfs[0].to_csv('issues.csv', quoting=csv.QUOTE_ALL, mode='a', header=False, index=False, line_terminator='\n')
一つのCSVファイルに出力するため、1ページ目と2ページ目以降で、一部引数の値を変えています。下の表にまとめました。
パラメータ | 1ページ目 | 2ページ目以降 | |
---|---|---|---|
囲み文字 | quoting | csv.QUOTE_ALL:すべて | 同左 |
出力方法 | mode | ‘w’:上書き(無ければ作成) | ‘a’:追記 |
ヘッダー行 | header | True:出力する | False:出力しない |
インデックス | index | False:出力しない | 同左 |
改行コード | line_terminator | ‘\n’:LF(ラインフィード) | 同左 |
実行結果
●コンソール
1ページ毎にメッセージを出力しています。
Windows PowerShell 新しいクロスプラットフォームの PowerShell をお試しください https://aka.ms/pscore6 PS D:\Users\owner\Documents\a1style\vscode> Scraping.py 1ページ目を処理中… 2ページ目を処理中… 3ページ目を処理中… 4ページ目を処理中… 5ページ目を処理中… 6ページ目を処理中… 7ページ目を処理中… 8ページ目を処理中… 9ページ目を処理中… 10ページ目を処理中… 11ページ目を処理中… 12ページ目を処理中… 13ページ目を処理中… 14ページ目を処理中… PS D:\Users\owner\Documents\a1style\vscode
●issues.csv
すべては載せれませんが、参考までに先頭の十数行だけ載せておきます。
改行コードは、指定通り「LF」です。
"#","Tracker","Status","Subject","Assignee","Updated","Category" "37473","Defect","Confirmed","Focus IssueId when linking issues","","2022-07-20 11:39","Issues" "37456","Defect","Resolved","undefined local variable or method `twofa_scheme' after a redmine upgrade","","2022-07-18 11:31","Accounts / authentication" "37453","Defect","New","No email sent to me from redmine.org","","2022-07-19 09:18","" "37441","Defect","New","No monospace (fixed length) font in markdown any longer","","2022-07-17 06:10","Text formatting" "37402","Defect","New","ticket topic","","2022-07-12 12:26","Administration" "37394","Defect","New","uninitialized constant Redmine::WikiFormatting::CommonMark::HTML (NameError)","","2022-07-17 14:39","Text formatting" "37390","Defect","New","Extraneous whitespace when selecting and copying issue number on Chrome/Windows","","2022-07-07 12:33","UI" "37388","Defect","Needs feedback","Not Able to Upload Files Using Google Chrome","","2022-07-14 08:13","Attachments" "37379","Defect","Resolved","Thumbnail macro does not work when a file is attached and preview is displayed immediately","Go MAEDA","2022-07-19 15:57","Text formatting" "37369","Defect","New","Mention auto-complete not works in bulk-edit comments","Marius BALTEANU","2022-07-04 07:52","Issues" "37366","Defect","New","Redmine Docker - Easy deploy upgrade, administer","","2022-06-28 13:34","Administration" "37364","Defect","New","Complex ticket search causes 500 Internal Server Error","","2022-06-29 02:20","Issues filter" "37358","Defect","New","Trackers can be assigned to users with role that do not have edit option on that tracker","","2022-06-27 13:29","Permissions and roles" "37282","Defect","Confirmed","subtask isn't displayed correctly since 4.2.7","","2022-07-18 21:43","UI" (以下、省略)
おわりに
Redmineには、標準でチケットのエクスポート機能があります。
しかしながら、多くの場合、サーバーへの負荷を考慮して、エクスポートできるチケット数の上限を低く設定しているのが実情です。Redmine公式では200件までです。
Pythonによるスクレイピングは、制約を受けずに抽出できるので、自分でデータを分析してみたいユーザーには強力なツールになります。
但し、過度のスクレイピングは、サーバーに負荷をかけ、攻撃とみなされる危険性があります。必ず1ページにつき1秒はスリープを入れましょう。
また、Redmine向けのライブラリ「Python-Redmine」もありますが、こちらはAPIとのことなので、また別の記事でチャレンジできたらと思っています。
最後までご覧いただき、ありがとうございます。
では、また。