Pythonでやってみよ。noteダッシュボードのコメント数推移
先日、noteの記事一覧を取得する記事を書きました。
記事の一覧と記事をローカルに保存できたわけですが、人間ですね、欲が出てきてしまいました。さらに、noteのダッシュボードのテーブルを取得してみたくなったわけです。そこで、やってみました。以下、自分に役に立つメモシステム、とは言っても単純なテキストファイルですが、にやってみた記録を「使い方」としてメモしたそのままのものです。プロトタイプで成功したのに飽き足らず、次から次へとやってみていることがわかりますよ。
Webページのテーブル表示は、pandasでかんたんに取得できます。RequestsとBeautifulSoup4を使う必要もありません。最近わたしは、pandasからpolarsへ乗り換えたので、いまさらpandasを使いたくはありませんでしたが、polarsには、pandasのread_htmlがないので、仕方ないです。これも柔軟な対応、臨機応変、固執しないことのいい例です。pandasのread_htmlでwebページからテーブル部分を取得したら、すぐにpolarsへ読み込みます。
やはりpolarsの方が、書きやすいですね。考えたとおり文を続けていけばいい。文を紡ぐ感じがとてもここちよい。
ダッシュページには、記事へのアクセス状況の合計がありますが、わたしはあえて、明細から合計の行を計算して作って、それを明細の下に追加する形にしています。結果を「vim」に渡して表示しています。それから結果をテキストファイルで保存しています。
「#」につづく文は、コメントです。
「>>> 」につづく文は、pythonプログラムのスケルトンライナーです。
「$ 」につづく文は、スケルトンライナーのコマンドライン版です。
「pc 」は、python3のエイリアスです。zshの設定ファイルに書いておきます。「$imt」は、python3のimport文のエイリアスです。同じくzshの設定ファイルに書いてあります。中身は、from importファイル import *です。
「cb.paste()」の「cb」はimportファイルに設定を書いています。中身は、import pyperclip as cbです。
#使い方vv
#pythonでnoteのダッシュボードのテーブルのhtmlをコピーしてpolarsで表形式でvimで表示する方法vv
>>> ht_=io.StringIO(cb.paste());df=pd.read_html(ht_);ht_.close();df=pl.from_pandas(df[0]);exec("with pl.Config.load(plcfg):df=pl.concat([df,pl.DataFrame([('合計',)+x[1:] for x in df.sum().iter_rows()],schema=df.sum().columns,orient='row')]);to_vim(str(df))")
$ pn -c "$imt;ht_=io.StringIO(cb.paste());df=pd.read_html(ht_);ht_.close();df=pl.from_pandas(df[0]);exec(\"with pl.Config.load(plcfg):df=pl.concat([df,pl.DataFrame([('合計',)+x[1:] for x in df.sum().iter_rows()],schema=df.sum().columns,orient='row')]);to_vim(str(df));z_=pathlib.Path('note_dashboard_'+datetime.datetime.now().strftime('%Y%m%d%H%M%S')+'.txt').expanduser().write_text(str(df))\")" #note_dashboard__
--- 2025-07-23 16:31:38 +0900 ---
vimで表示された結果をテキストファイルに保存していましたが、その後使いたい場合、parquet形式の方が使いやすいので、そちらの形式で保存する文を追加しました。でも、せっかくparquet形式で保存してあるのに、テキストファイルから中身をコピーして再利用して試しています。くわえて複数の記事のファイル名をコピーして、比較できるようにしてみました。記事の個数によって動的に変数名を作成するという小技も入っています。
テキスト表示でも、「textwrap.wrap」を使って、「tabulate」経由にしたりと、試行錯誤している過程もそのまま残して記録しています。
#使い方vv
#pythonでnoteのダッシュボードのテーブルのhtmlをコピーしてpolarsで表形式でvimで表示する方法vv
#pythonでpandasでnoteのダッシュボードのテーブル値を取得してpolarsで加工して表テキストにしてvimで表示、表テキストとparquet形式で日付を付けて保存する方法vv
>>> lt_=cb.paste().split('\n');s1_='[│┌└┬┴┐╞╪├┼┤╌─═┘╡]';s2_='┆';exec("for i,f in enumerate(lt_):\n fm_='~/'+f;rr_=locals()[f'r{i}']=''+datetime.datetime.strptime(fm[:-4].split('')[-1],'%Y%m%d%H%M%S').strftime('%Y-%m-%d%H%M%S')\n a_=[[x.strip(' ') for x in x.split(s2_)] for x in [x for x in re.sub(s1_,'',pathlib.Path(fm_).expanduser().read_text()).split('\\n') if x!='']]\n col_=[x+rr_ if not x=='記事' else x for x in a_[1]];a1_=a_[4:]\n b_=a1_[:-1];c_=a1_[-1]\n locals()[f'df_{i}']=pl.DataFrame(b_,schema=col_,orient='row').with_columns(pl.col('^(ビュー|コメント|スキ)$').str.replace(',','').cast(pl.Int64))\n locals()[f'df0_{i}']=pl.DataFrame([c_],schema=col_,orient='row').with_columns(pl.col('^(ビュー|コメント|スキ)$').str.replace(',','').cast(pl.Int64))");e_='';exec("for i,f in zip(range(len(lt_))[::-1],lt_[::-1]):\n if i+1==len(lt_):\n e_='df_z=df_'+str(i)\n elif 0<=i+1<len(lt_):\n e_+='.join(df_'+str(i)+',on=\\'記事\\',how=\\'full\\',suffix=\\''+str(i)+'\\')'\n else:pass");exec(e_);df_z=df_z.select(pl.exclude('^記事.+?$')).with_columns(pl.col('^[ビコス].*?$').cast(pl.Int64));df_ans_=df_z.select([pl.col('記事'),pl.col('^ビュー.*?$'),pl.col('^コメント.*?$'),pl.col('^スキ.*?$'),pl.col('^日付.*?$')]);df_ans_=pl.concat([df_ans_,pl.DataFrame([('合計',)+x[1:] for x in df_ans_.sum().iter_rows()],schema=df_ans_.columns,orient='row')]);z_=df_ans_.write_excel('note_dashboard_'+fm_[:-4].split('')[-1]+'.xlsx');z=df_ans_.write_parquet('note_dashboard_'+fm_[:-4].split('')[-1]+'.parquet');z=pl.Config.load(plcfg);to_vim(str(df_ans_));to_vim(tabulate([('\n'.join(textwrap.wrap(x[0],width=15)),)+x[1:] for x in df_ans_.iter_rows()],headers=[re.sub('','\n',x,2) for x in df_ans.columns],tablefmt='fancy_grid',intfmt=',')) #note_dashboard_vs__
$ pn -c "$imt;lt_=cb.paste().split('\\n');s1_='[│┌└┬┴┐╞╪├┼┤╌─═┘╡]';s2_='┆';exec(\"for i,f in enumerate(lt_):\\n fm_='~/'+f;rr_=locals()[f'r{i}']=''+datetime.datetime.strptime(fm[:-4].split('')[-1],'%Y%m%d%H%M%S').strftime('%Y-%m-%d%H%M%S')\\n a_=[[x.strip(' ') for x in x.split(s2_)] for x in [x for x in re.sub(s1_,'',pathlib.Path(fm_).expanduser().read_text()).split('\\\\n') if x!='']]\\n col_=[x+rr_ if not x=='記事' else x for x in a_[1]];a1_=a_[4:]\\n b_=a1_[:-1];c_=a1_[-1]\\n locals()[f'df_{i}']=pl.DataFrame(b_,schema=col_,orient='row').with_columns(pl.col('^(ビュー|コメント|スキ)$').str.replace(',','').cast(pl.Int64))\\n locals()[f'df0_{i}']=pl.DataFrame([c_],schema=col_,orient='row').with_columns(pl.col('^(ビュー|コメント|スキ)$').str.replace(',','').cast(pl.Int64))\");e_='';exec(\"for i,f in zip(range(len(lt_))[::-1],lt_[::-1]):\\n if i+1==len(lt_):\\n e_='df_z=df_'+str(i)\\n elif 0<=i+1<len(lt_):\\n e_+='.join(df_'+str(i)+',on=\\\\'記事\\\\',how=\\\\'full\\\\',suffix=\\\\''+str(i)+'\\\\')'\\n else:pass\");exec(e_);df_z=df_z.select(pl.exclude('^記事.+?$')).with_columns(pl.col('^[ビコス].*?$').cast(pl.Int64));df_ans_=df_z.select([pl.col('記事'),pl.col('^ビュー.*?$'),pl.col('^コメント.*?$'),pl.col('^スキ.*?$'),pl.col('^日付.*?$')]);df_ans_=pl.concat([df_ans_,pl.DataFrame([('合計',)+x[1:] for x in df_ans_.sum().iter_rows()],schema=df_ans_.columns,orient='row')]);z_=df_ans_.write_excel('note_dashboard_'+fm_[:-4].split('')[-1]+'.xlsx');z=df_ans_.write_parquet('note_dashboard_'+fm_[:-4].split('')[-1]+'.parquet');z=pl.Config.load(plcfg);to_vim(str(df_ans_));to_vim(tabulate([('\n'.join(textwrap.wrap(x[0],width=15)),)+x[1:] for x in df_ans_.iter_rows()],headers=[re.sub('','\n',x,2) for x in df_ans.columns],tablefmt='fancy_grid',intfmt=','))" #note_dashboard_vs__
#@parquetファイルから読み込む例@#
>>> df=pl.read_parquet('note_dashboard_20250724092323.parquet');pl.Config.load(plcfg);to_vim(str(df))
>>> pl.Config(set_tbl_width_chars=400,set_fmt_str_lengths=100)
>>> to_vim(str(df_))
#@textwrapとtabulateを試す@#
>>> to_vim(tabulate([('\n'.join(textwrap.wrap(x[0],width=15)),)+x[1:] for x in df_ans_.iter_rows()],headers=[re.sub('','\n',x,2) for x in df_ans.columns],tablefmt='fancy_grid',intfmt=','))
#--- 2025-07-23 16:31:38 +0900 ---
追加(日付ごとのファイルから記事で比較できるようにした)
#--- 2025-07-24 15:28:29 +0900 ---
追加(ファイルが増えたとき修正が必要になるので、変数名を動的に定義する動的変数名で対応した)
--- 2025-07-24 18:07:35 +0900 ---
ダッシュボードのページを開いてhtmlのテーブル部分のソースをコピペするのもなんだかなぁ、と思ってしまい、とうとう「selenium」を使って、自動化をやってみました。「get_note_dashboard」がその自作関数です。
そして、複数ファイル名をコピーして、というのもなんだかなぁ、なので、こちらも「pathlib」を使って比較するファイルを取得するようにしてみました。
#使い方vv
#pythonでnoteのダッシュボードのテーブルのhtmlをコピーしてpolarsで表形式でvimで表示する方法vv
#pythonでpandasでnoteのダッシュボードのテーブル値を取得してpolarsで加工して表テキストにしてvimで表示、表テキストとparquet形式で日付を付けて保存する方法vv
$ pn -c "$imt;ans_=get_note_dashboard('https://note.com/sitesettings/stats')" #note_dashboard_by_selenium__
$ pn -c "$imt;lt_=sorted([x.name for x in pathlib.Path('~/').expanduser().glob('**/*.txt') if re.search('^note_dashboard_\d*?.txt',x.name) and datetime.datetime.strptime(x.name[:-4].split('')[-1],'%Y%m%d%H%M%S').astimezone()>datetime.datetime(2025,7,22).astimezone()],key=lambda x:pathlib.Path(x).name[:-4].split('')[-1]);s1_='[│┌└┬┴┐╞╪├┼┤╌─═┘╡]';s2_='┆';exec(\"for i,f in enumerate(lt_):\\n fm_='~/pn/'+f;rr_=locals()[f'r{i}']=''+datetime.datetime.strptime(fm[:-4].split('')[-1],'%Y%m%d%H%M%S').strftime('%Y-%m-%d%H%M%S')\\n a_=[[x.strip(' ') for x in x.split(s2_)] for x in [x for x in re.sub(s1_,'',pathlib.Path(fm_).expanduser().read_text()).split('\\\\n') if x!='']]\\n col_=[x+rr_ if not x=='記事' else x for x in a_[1]];a1_=a_[4:]\\n b_=a1_[:-1];c_=a1_[-1]\\n locals()[f'df_{i}']=pl.DataFrame(b_,schema=col_,orient='row').with_columns(pl.col('^(ビュー|コメント|スキ)$').str.replace(',','').cast(pl.Int64))\\n locals()[f'df0_{i}']=pl.DataFrame([c_],schema=col_,orient='row').with_columns(pl.col('^(ビュー|コメント|スキ)$').str.replace(',','').cast(pl.Int64))\");e_='';exec(\"for i,f in zip(range(len(lt_))[::-1],lt_[::-1]):\\n if i+1==len(lt_):\\n e_='df_z=df_'+str(i)\\n elif 0<=i+1<len(lt_):\\n e_+='.join(df_'+str(i)+',on=\\\\'記事\\\\',how=\\\\'full\\\\',suffix=\\\\''+str(i)+'\\\\')'\\n else:pass\");exec(e_);df_z=df_z.select(pl.exclude('^記事.+?$')).with_columns(pl.col('^[ビコス].*?$').cast(pl.Int64));df_ans_=df_z.select([pl.col('記事'),pl.col('^ビュー.*?$'),pl.col('^スキ.*?$'),pl.col('^コメント.*?$'),pl.col('^日付.*?$')]);df_ans_=pl.concat([df_ans_,pl.DataFrame([('合計',)+x[1:] for x in df_ans_.sum().iter_rows()],schema=df_ans_.columns,orient='row')]);z_=df_ans_.write_excel('note_dashboard_'+fm_[:-4].split('')[-1]+'.xlsx');z=df_ans_.write_parquet('note_dashboard_'+fm_[:-4].split('')[-1]+'.parquet');z=pl.Config.load(plcfg);to_vim(str(df_ans_));to_vim(tabulate([('\n'.join(textwrap.wrap(x[0],width=15)),)+x[1:] for x in df_ans_.iter_rows()],headers=[re.sub('','\n',x,2) for x in df_ans.columns],tablefmt='fancy_grid',intfmt=','))" #note_dashboard_vs__
#--- 2025-07-23 16:31:38 +0900 ---
追加(日付ごとのファイルから記事で比較できるようにした)
#--- 2025-07-24 15:28:29 +0900 ---
追加(ファイル名のコピーをpathlibに、webを開いてhtmlをコピーしていたのをseleniumで自動取得にした)
--- 2025-07-26 13:15:51 +0900 ---
