0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Google ColabでPythonをGoにリファクタリングして、速度比較をしてみる

0
Last updated at Posted at 2026-07-02

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は、非公式であり、動作に対するサポートはないとのことです。

参考リンク:

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?