Google Colab(Jupyter Notebook)では、Pythonだけでなく、Go言語のプログラムも記述・実行することができます。この特徴を活かすと、同じNotebook内で、Pythonのプログラムを、Goにリファクタリング(書き直し)することができます。
これにより、ひとつのファイル内で、Python/Goのプログラムがならび、
- 比較・レビューがしやすくなる
- プログラミング言語ごとの設計/思想のちがいまで、考慮しやすくなる
といったメリットがあります。
本記事では、
- Pythonで書いたWebスクレイピング処理を確認する
- 同じ処理をGo言語で書き直す
- Python版とGo版の実行結果を比較する
- 速度差だけでなく、設計・構造の違いを考える
- Google Colabを、言語比較やリファクタリング検証の場として使う
までの流れを紹介します。
対象読者
- Google Colabを使ったことがある人
- Pythonで簡単なスクレイピングやデータ処理を書いたことがある人
- Go言語に興味はあるが、何を作ればよいか迷っている人
- Pythonで書いた処理を、Goで書き直すとどうなるか知りたい人
- プログラムの速度だけでなく、言語ごとの設計思想の違いに興味がある人
- ローカル環境を汚さずに、PythonとGoを比較しながら試したい人
なお、「Google ColabでGo言語が書けるなんで初耳!」という人は、まずはこれらの記事を参考にしてください。
今回の題材
今回の題材は、noteの記事一覧と本文を取得し、CSVファイルとして保存するWebスクレイピング処理です。
スクレイピング対象のデータは、弊社・合同会社インクルーシブソリューションズのNote記事です。
2026年7月2日時点で、掲載記事数がちょうど100記事でした。
100記事ぶんのNoteをプログラムで全部取得する時間が、今回の調査テーマです。
リファクタリング前:Python版の実装概要
Python版では、
- noteの記事一覧APIを取得する
- 各記事ページにアクセスする
- BeautifulSoupで本文を抽出する
- pandasでCSVに出力する
という流れで実装しました。
リファクタリング後:Go言語に書き直し後
その後、同じ処理をGo言語で書き直しました。
- net/http でHTTPリクエストを行う
- goquery でHTMLを解析する
- encoding/csv でCSVを書き出す
- goroutineを使って本文取得を並列化する
- tickerを使ってアクセス間隔を制御する
という構成にしています。
単にPythonのコードをGoに置き換えるだけでなく並列処理やエラー処理も明示的に書くなど、Goらしい構造・構成にしました。
コードの概要
※長いため、ここでは一部のみ抜粋。コード全文は、記事の後半にて掲載しています。
※コードの記述には、Claudeを用いています。
import requests
import pandas as pd
from bs4 import BeautifulSoup
def get_note_entry_body(key: str, username: str) -> str:
"""
特定の記事URLから本文を取得し、HTMLタグを取り除いたプレーンテキストを返す
"""
time.sleep(0.5) # 個別記事取得のウェイト
url = f"https://note.com/{username}/n/{key}"
try:
res = requests.get(url, timeout=10)
if res.status_code != 200:
return ""
soup = BeautifulSoup(res.content, "html.parser")
body_element = soup.find('div', attrs={'data-name': 'body'})
# --- 中略 ---
## Pythonのスクレイピングプログラム
def create_note_csv_for_notebooklm(username: str, max_page: int = 100, output_filename: str = "note_articles.csv"):
"""
全記事の一覧と本文を取得し、CSVを出力する
"""
all_articles = []
session = requests.Session()
print(f"ユーザー: {username} の記事取得を開始します...")
for page in range(1, max_page + 1):
time.sleep(1.0) # 一覧API取得のウェイト
api_url = f"https://note.com/api/v2/creators/{username}/contents?kind=note&page={page}"
try:
res = session.get(api_url, timeout=10)
if res.status_code != 200:
print(f"page {page}: ステータスコード {res.status_code} のため終了します。")
break
data_json = res.json()
contents = data_json.get('data', {}).get('contents', [])
# --- 中略 ---
except Exception as e:
print(f"page {page} の処理中にエラーが発生しました: {e}")
break
# データが存在すればCSVへ出力
if all_articles:
df = pd.DataFrame(all_articles)
# NotebookLMが認識しやすいようにUTF-8(BOM付き)で出力
df.to_csv(output_filename, index=False, encoding="utf-8-sig")
print(f"正常に完了しました!ファイル名: {output_filename} (総記事数: {len(df)}件)")
else:
print("記事データが取得できませんでした。")
# --- 実行 ---
if __name__ == "__main__":
# 計測開始時間を取得
start_time = time.perf_counter()
# アカウント名、最大取得ページ数、出力ファイル名を指定
create_note_csv_for_notebooklm(username="inclu_solutions", max_page=1000, output_filename="note_all_contents.csv")
# 計測終了時間を取得
end_time = time.perf_counter()
# 差分を計算して表示
elapsed_time = end_time - start_time
print(f"\n[計測結果] 総実行時間: {elapsed_time:.2f} 秒")
一方、goでは、およそ次のような構造になります。
%%writefile main.go
// 20260702: スクレイピングPG: Go移植版(並列化あり)
package main
import (
"context"
"encoding/csv"
"encoding/json"
// 中略
"github.com/PuerkitoBio/goquery"
)
// ===== 設定値 =====
const (
username = "inclu_solutions"
maxPage = 1000
outputFilename = "note_all_contents.go.csv"
listPageDelay = 300 * time.Millisecond // 一覧API取得のウェイト(Pythonは1.0秒)
bodyConcurrency = 8 // 本文取得の同時実行数
bodyMinInterval = 150 * time.Millisecond // 本文取得のレート制限(並列worker全体で共有)
maxRetries = 3
requestTimeout = 10 * time.Second
)
// ===== APIレスポンス構造体 =====
type apiResponse struct {
Data struct {
Contents []contentItem `json:"contents"`
} `json:"data"`
}
type contentItem struct {
Type string `json:"type"`
Key string `json:"key"`
Name string `json:"name"`
Body string `json:"body"`
NoteURL string `json:"noteUrl"`
// 中略
}
// 出力用の記事データ
type article struct {
Key string
Type string
// 中略
}
var tagStripRe = regexp.MustCompile(`<[^>]*?>`)
// ===== HTTPクライアント(コネクションプール共有) =====
func newHTTPClient() *http.Client {
transport := &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: bodyConcurrency + 2,
IdleConnTimeout: 30 * time.Second,
}
return &http.Client{Transport: transport, Timeout: requestTimeout}
}
// ===== 1. 記事一覧の取得(逐次ページング) =====
func fetchArticleList(client *http.Client) []article {
var all []article
// 中略
contents := data.Data.Contents
if len(contents) == 0 {
fmt.Printf("page %d は空のため、全記事の抽出処理を完了しました!\n", page)
break
}
fmt.Printf("page %d を処理中... (%d件の記事を発見)\n", page, len(contents))
for _, item := range contents {
title := item.Name
body := ""
if item.Type == "TalkNote" {
title = "つぶやき"
if item.Body != "" {
body = tagStripRe.ReplaceAllString(item.Body, "")
}
}
// 中略
}
return all
}
// ===== 2. 本文の取得(通常記事1件分) =====
func fetchEntryBody(ctx context.Context, client *http.Client, key string) (string, error) {
url := fmt.Sprintf("https://note.com/%s/n/%s", username, key)
// 中略
doc, err := goquery.NewDocumentFromReader(res.Body)
res.Body.Close()
if err != nil {
lastErr = err
continue
}
text := strings.TrimSpace(doc.Find(`div[data-name="body"]`).Text())
return text, nil
}
return "", lastErr
}
// ===== 3. 本文の並列取得(ワーカープール + レート制限) =====
func fillBodiesConcurrently(articles []article) {
// 中略
go func() {
defer wg.Done()
defer func() { <-sem }()
<-limiter.C // レート制限(全workerで共有のチケット)
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
defer cancel()
// 中略
articles[idx].Body = body // 各goroutineが別indexにしか書かないのでmutex不要
mu.Lock()
done++
if done%20 == 0 {
fmt.Printf(" 本文取得進捗: %d/%d\n", done, len(targets))
}
mu.Unlock()
}()
}
wg.Wait()
}
// ===== 4. CSV出力(UTF-8 BOM付き) =====
func writeCSV(articles []article, filename string) error {
// 中略
}
// ===== メイン =====
func main() {
// 中略
if len(articles) > 0 {
if err := writeCSV(articles, outputFilename); err != nil {
fmt.Printf("CSV書き込みエラー: %v\n", err)
} else {
fmt.Printf("正常に完了しました!ファイル名: %s (総記事数: %d件)\n", outputFilename, len(articles))
}
} else {
fmt.Println("記事データが取得できませんでした。")
}
elapsed := time.Since(start)
fmt.Printf("\n[計測結果] 総実行時間: %.2f 秒\n", elapsed.Seconds())
}
検証結果の概要
試したところ、
-
Python版の計測結果:
115.45秒(およそ二分) -
Go版の計測結果:
23.41秒
また、Goでビルドしたバイナリを実行した場合は、総実行時間: 24.58 秒になりました。
今回のプログラムにおいては、事前にバイナリ化しても、速度・性能はほぼ変わらなかったといえそうです。
今回の結果だけを見ると、Python → Goへのリファクタリングで、速度がおよそ五倍になったといえそうです。
結果から何を読み取るか
ただし、これは単純に「GoはPythonよりも五倍速い」という話ではありません。
今回の高速化には、次の要素が大きく影響しています。
- 本文取得を並列化したこと
- Go言語は並列処理が簡単に実装できるため、本検証においても使用しています
- ただし、Pythonでも並列処理を実装することは可能です
- 待機時間の設計を見直したこと
- pandasを使わず、Go標準のCSV出力にしたこと
つまり、今回の結果は、言語性能そのものというよりも、処理設計の変更による効果が大きいと考えられます。
結局、PythonとGo、あえて比較するなら?
他方、コードを書く負担をみれば、Pythonは、とくにデータを集めてCSVに出すだけなら、圧倒的に短く、シンプルなコードが書けます。
pandas の存在が非常に便利なためです。
一方でGoは、構造をはっきり書く必要があり、コードを書く負担が大きくなりがちです。
まとめると、
Pythonが強みを発揮しやすいのは、
- 機能・仕様がまだ曖昧なまま、「仮決め」で実装をはじめたいとき
- 実データをもとにした、探索・試作を進めたいとき
- プログラムを書きながら、自分の考え・構想を整理したいとき
- コードを多くの人が読んで、多人数でレビュー・議論したいとき
- 長期の構想よりも、迅速なPoCを重視したいとき
などだと思います。
一方、Go言語が強みを発揮しやすいのは、
- 決まった仕様・要件のもとで、プログラムの高速化・最適化を進めていきたいとき
- 長く運用・保守を続ける想定で、システム障害に強い、構造のはっきりした実装にしたいとき
- プロジェクトの初速をあげるよりも、サービスの大規模化・データの肥大化に準備したいとき
- プログラムの実行環境・開発環境に左右されず、「どんなパソコンでも動くツール」が作りたいとき
などだと思います。
まとめ:Python/Goは適材適所 比較結果は「引き分け」ということに
普通に開発していると、PythonのコードはPythonプロジェクトにあり、GoのコードはGoプロジェクトにあります。
そのため、言語をまたいだ比較は意外と面倒です。
しかし、Colab上で検証すると、同じNotebook内に次の要素を並べることができます。
- Python版のコード
- Python版の実行結果
- Goのインストール手順
- Go版のコード
- Go版の実行結果
- ビルドしたバイナリの実行結果
これは、単なる実行環境として便利というだけではありません。
- 「Pythonではこう書いた処理を、Goではどう分解するべきか」
- 「Pythonの便利なライブラリに任せていた部分を、Goではどこまで明示的に書く必要があるか」
- 「並列処理やエラー処理を、どの言語ではどう表現するか」
といった細やかな技術判断について、比較したり、議論したりする際に、Google Colab(Jupyter Notebook)のマルチ言語活用は、非常に便利だと思います。
さいごに:
今回の記事は、ややマニアックなテーマも多かったですが、
- プログラムやプログラミング言語は、いろいろな視点で優劣/メリットデメリットが比較できる
- 表面的な機能/仕様にあらわれない設計/構想をめぐって、人やAIと議論できるテーマは数多くある
という感触が伝われば幸いです。
あわせて、次のようなテーマも興味深いと思います。
- AIコーディングが普及に伴って、プログラミング言語の、"人間にとっての"書きやすさが無意味になる可能性はあるのか?
- AIにとって、「もっともコーディングしやすいプログラミング言語」はなんなのか?
- AI活用の「ガードレール」が大事になるなか、コンパイル型の言語の価値が高まる可能性はあるのか?
弊社について
本記事を書いている 合同会社インクルーシブソリューションズ は、データ基盤構築・分析基盤設計・システム改善支援を中心に活動している小規模IT法人です。
主な領域は、
- データマート設計・データパイプライン構築
- SQL / Python を用いたデータ処理設計
- BI導入支援・分析基盤の整備
- 既存システムの運用改善・可視化支援
といった、「データを使える状態にする」ための活動です。
弊社の企業活動に興味がある方は、ぜひ公式サイトも覗いてみてください。
注記:
- 本記事で「スクレイピング」と表現する処理は、厳密には、APIのリクエストと、HTMLのスクレイピング双方を含んでいます。
- NoteのAPIは、非公式であり、動作に対するサポートはないとのことです。
参考リンク: