SharePoint Online(SPO)のドキュメントライブラリをPythonから操作できると、定期バックアップ、ETL、ファイル連携の自動化が一気に進みます。本記事では、SPOで定番の「office365系クライアントライブラリ」を使って、ファイル一覧取得・ダウンロード・アップロードを実装する手順と、MFA環境で安定するMicrosoft Graph(OAuth)構成まで実務目線でまとめます。
SharePoint OnlineをPythonから触るときに押さえる全体像
「SPOのファイル/フォルダーをPythonで一覧取得・ダウンロード・アップロードしたい」という要望はよくありますが、つまずきやすいポイントはだいたい次の2つです。
- 認証方式:ユーザー名+パスワードが通る環境もありますが、組織によってはMFA必須やレガシー認証無効で通りません。
- URLの形式:ブラウザーでコピーしたURL(共有リンク等)をそのまま使うと失敗しやすく、Python実装ではサーバー相対URLやGraphのパス指定に合わせて整形が必要です。
最短で動かすなら、SharePoint RESTを叩く定番実装であるoffice365-rest-python-client系を使い、ClientContextで接続→フォルダー配下の取得→ファイル操作、という流れにすると安定します。さらに、ユーザー名+パスワードが使えない環境では、Entra ID(Azure AD)アプリ登録+OAuthでMicrosoft Graphに寄せるのが長期的に堅い選択です。
事前に準備しておく情報チェックリスト
実装に入る前に、最低限これだけ整理しておくと無駄なハマりが減ります。
| 項目 | 例 | どこで確認するか |
|---|---|---|
| サイトURL | https://tenant.sharepoint.com/sites/YourSite | ブラウザーでサイトを開いたURL |
| サーバー相対URL(サイト) | /sites/YourSite | サイトURLからドメイン部分を除いたパス |
| ドキュメントライブラリ名(内部名) | Shared Documents / Documents / 任意のライブラリ名 | ライブラリを開いたときのURLパス、またはライブラリ設定 |
| 対象フォルダーのサーバー相対URL | /sites/YourSite/Shared%20Documents/Reports | ブラウザーのパスから組み立て(スペースは%20) |
| 認証方式 | ユーザー認証(MFAなし)/ デバイスコード / クライアント資格情報 | 組織のセキュリティ設定・運用方針 |
特に重要なのが「ドキュメントライブラリ名(内部名)」です。表示名が日本語(例:ドキュメント)でも、URL上は Shared Documents のまま、ということがよくあります。ここがズレると「フォルダーが見つからない」「ファイルが取れない」に直結します。
実装の定番:office365-rest-python-clientでSPOに接続する
ここからは、SharePoint RESTを利用するoffice365系ライブラリでの実装例を紹介します。やりたいこと(一覧取得・ダウンロード・アップロード)に最短距離で到達できます。
インストール
まずはライブラリを入れます。
pip install Office365-REST-Python-Client
接続確認:サイトタイトルを取得する
接続できているかの確認として、サイト(Web)のタイトルを取得します。ここが通れば、だいたいの操作が同じ認証のまま進められます。
ユーザー名+パスワードで認証(組織設定で許可されている場合)
from office365.sharepoint.client_context import ClientContext
from office365.runtime.auth.user_credential import UserCredential
SITE_URL = "[https://tenant.sharepoint.com/sites/YourSite](https://tenant.sharepoint.com/sites/YourSite)"
USERNAME = "[[email protected]](mailto:[email protected])"
PASSWORD = "********"
ctx = ClientContext(SITE_URL).with_credentials(UserCredential(USERNAME, PASSWORD))
web = ctx.web
ctx.load(web)
ctx.execute_query()
print(web.properties.get("Title"))
この方式は手軽ですが、MFA必須やレガシー認証無効の環境では失敗することが多いです。その場合は後半で紹介するGraph(OAuth)構成へ寄せるのが現実的です。
Client ID / Client Secretでアプリ認証(アプリ登録+権限付与が前提)
from office365.sharepoint.client_context import ClientContext
from office365.runtime.auth.client_credential import ClientCredential
SITE_URL = "[https://tenant.sharepoint.com/sites/YourSite](https://tenant.sharepoint.com/sites/YourSite)"
CLIENT_ID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
CLIENT_SECRET = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
ctx = ClientContext(SITE_URL).with_credentials(ClientCredential(CLIENT_ID, CLIENT_SECRET))
web = ctx.web
ctx.load(web)
ctx.execute_query()
print(web.properties.get("Title"))
アプリ認証にすると、サービスアカウントのパスワード変更やMFA影響を避けやすくなります。ただし、どのAPIにどの権限を付けるか(GraphかSharePointか)と、最小権限設計が肝になります。組織のセキュリティ方針によってはGraphに一本化する方が通しやすいです。
フォルダー内のファイル一覧を取得する
SPOの「ドキュメントライブラリ」は、感覚的には「フォルダーの中にファイルがある」構造ですが、SharePoint RESTの操作ではサーバー相対URLでフォルダーを指定し、そこからファイルコレクションをロードします。
ファイル一覧(ファイル名の列挙)
from office365.sharepoint.client_context import ClientContext
from office365.runtime.auth.user_credential import UserCredential
SITE_URL = "[https://tenant.sharepoint.com/sites/YourSite](https://tenant.sharepoint.com/sites/YourSite)"
USERNAME = "[[email protected]](mailto:[email protected])"
PASSWORD = "********"
# 例:/sites/YourSite/Shared%20Documents/Reports
FOLDER_SERVER_RELATIVE_URL = "/sites/YourSite/Shared%20Documents/Reports"
ctx = ClientContext(SITE_URL).with_credentials(UserCredential(USERNAME, PASSWORD))
folder = ctx.web.get_folder_by_server_relative_url(FOLDER_SERVER_RELATIVE_URL)
files = folder.files
ctx.load(files)
ctx.execute_query()
for f in files:
# propertiesにNameが入ることが多い
print(f.properties.get("Name"))
フォルダー一覧(サブフォルダーの列挙)
フォルダー(ディレクトリ)一覧が欲しい場合は、folder.files を folder.folders に変えます。
subfolders = folder.folders
ctx.load(subfolders)
ctx.execute_query()
for sf in subfolders:
print(sf.properties.get("Name"))
| 取得したいもの | 使うコレクション | 用途 |
|---|---|---|
| ファイル一覧 | folder.files | 拡張子で絞り込み、最新ファイル探し、バッチ処理など |
| フォルダー一覧 | folder.folders | 階層巡回、配下の一括取得、年/月フォルダー運用に対応 |
「一覧を取れたのにダウンロードで失敗する」という場合は、次の章のファイルURLの組み立て(特にURLエンコード)を見直すのが近道です。
特定ファイルをダウンロードしてローカル保存する
ダウンロードは、サーバー相対URLでファイルを指定し、open_binaryでバイナリを取得して保存します。ポイントは次の通りです。
- ファイルURLはサーバー相対URL(
/sites/...から始まる)で指定する - スペースなどはURLエンコード(例:
Shared%20Documents) - ローカル保存は必ずwb(バイナリ書き込み)
from office365.sharepoint.client_context import ClientContext
from office365.runtime.auth.user_credential import UserCredential
from office365.sharepoint.files.file import File
SITE_URL = "https://tenant.sharepoint.com/sites/YourSite"
USERNAME = "[email protected]"
PASSWORD = "********"
# 例:/sites/YourSite/Shared%20Documents/Reports/monthly.xlsx
FILE_SERVER_RELATIVE_URL = "/sites/YourSite/Shared%20Documents/Reports/monthly.xlsx"
LOCAL_PATH = r"C:\temp\monthly.xlsx"
ctx = ClientContext(SITE_URL).with_credentials(UserCredential(USERNAME, PASSWORD))
response = File.open_binary(ctx, FILE_SERVER_RELATIVE_URL)
with open(LOCAL_PATH, "wb") as f:
f.write(response.content)
print("downloaded:", LOCAL_PATH)
日本語ファイル名やスペースを含むファイル名を扱う場合、URLエンコード不足で「見つからない」扱いになりがちです。次の章で、URLを事故らせない作り方をまとめます。
ファイルをアップロードする
アップロードは、対象フォルダーを取得して upload_file する形がシンプルです。まずは小〜中容量ファイルの例です(大容量は後述)。
from office365.sharepoint.client_context import ClientContext
from office365.runtime.auth.user_credential import UserCredential
SITE_URL = "https://tenant.sharepoint.com/sites/YourSite"
USERNAME = "[email protected]"
PASSWORD = "********"
TARGET_FOLDER_URL = "/sites/YourSite/Shared%20Documents/Reports"
LOCAL_FILE_PATH = r"C:\temp\report.csv"
UPLOAD_NAME = "report.csv"
ctx = ClientContext(SITE_URL).with_credentials(UserCredential(USERNAME, PASSWORD))
target_folder = ctx.web.get_folder_by_server_relative_url(TARGET_FOLDER_URL)
with open(LOCAL_FILE_PATH, "rb") as f:
content = f.read()
uploaded_file = target_folder.upload_file(UPLOAD_NAME, content).execute_query()
print("uploaded:", uploaded_file.serverRelativeUrl)
実運用でよくあるのが「毎日生成したファイルを同名で上書きしたい」ケースです。上書き挙動は環境・API・設定で差が出ることがあるため、確実にするなら次のような運用が堅いです。
- 日付入りファイル名(例:
report_2026-01-09.csv)で衝突を避ける - 「同名があれば削除してからアップロード」よりも「バージョン管理を有効化して上書き」が安全(監査にも強い)
大容量ファイル(数十MB〜GB級)を扱う場合は、1発アップロードでは失敗しやすくなります。Graphのupload session(分割アップロード)を使う構成にするか、利用しているライブラリが提供するチャンクアップロード機能を検討してください。
最重要:URLは「サーバー相対URL」に直して使う
SPOでPython実装が不安定になる最大の原因は、URLの扱いです。ブラウザーからコピーできるURLには複数の種類があり、APIが期待する形とズレると簡単に失敗します。
| URLの種類 | 例 | Python実装での扱い |
|---|---|---|
| フルURL(通常のパス) | https://tenant.sharepoint.com/sites/YourSite/Shared%20Documents/Reports | ドメインを落としてサーバー相対URLに変換すると安定 |
| サーバー相対URL | /sites/YourSite/Shared%20Documents/Reports | SharePoint REST系(office365ライブラリ)でそのまま使いやすい |
| 共有リンク(短縮/トークン付き) | https://tenant.sharepoint.com/:x:/r/sites/…/?d=…&csf=1 | そのままでは失敗しやすい。元のパスを特定して組み立て直す |
フルURLからサーバー相対URLを取り出す簡易関数
「ブラウザーのアドレスバーに出ているURL」からなら、ドメイン部分を落として path を抜けばOKです(共有リンクのように特殊なURLは別扱いにするのが安全です)。
from urllib.parse import urlparse
def full_url_to_server_relative(full_url: str) -> str:
"""
https://tenant.sharepoint.com/sites/YourSite/Shared%20Documents/Reports
-> /sites/YourSite/Shared%20Documents/Reports
"""
p = urlparse(full_url)
return p.path
URLエンコードを事故らせないコツ
スペース、#、% などが混ざるパスを手で直すのは危険です。フォルダー名やファイル名をコードから組み立てる場合は、パスの各要素ごとにURLエンコードするのが安全です(スラッシュは区切りとして残す)。
from urllib.parse import quote
def build_server_relative_url(site_path: str, library_and_path: str) -> str:
"""
site_path: /sites/YourSite
library_and_path: Shared Documents/Reports/2026 01/monthly report.xlsx
-> /sites/YourSite/Shared%20Documents/Reports/2026%2001/monthly%20report.xlsx
"""
site_path = site_path.rstrip("/")
parts = [quote(p, safe="") for p in library_and_path.split("/")]
return site_path + "/" + "/".join(parts)
この組み立て方にしておくと、フォルダー名に日本語が入っても余計な事故が減ります(ただし、運用上の制約で「URLに日本語を使わない」方針があるなら、それに従うのがベストです)。
よくあるエラーと原因・対処法
現場で頻出する詰まりどころを、症状ベースで整理します。
| 症状 | よくある原因 | 対処の方向性 |
|---|---|---|
| 401 Unauthorized | MFA必須/レガシー認証無効/資格情報が誤り | ユーザー名+パスワードを諦め、Graph(OAuth)またはアプリ認証へ |
| 403 Forbidden | 権限不足(サイト/ライブラリ/フォルダー単位で拒否) | 対象パスに対する権限を確認。アプリの場合は権限スコープと同意も確認 |
| 404 Not Found | サーバー相対URLが誤り/URLエンコード不足/ライブラリ内部名のズレ | ブラウザーのURLからserver relativeを作り直す。スペースは%20 |
| 一覧は取れるがダウンロードできない | フォルダーURLは正しいがファイルURL生成が誤り | ファイル名のエンコード、拡張子、階層を見直す。取得したNameをそのまま使う |
| タイムアウト/たまに失敗する | ネットワーク揺らぎ/スロットリング/大容量ファイル | リトライ・待機、Graphのupload session、処理の分割 |
「パスワードで通らない」は、スキル不足ではなく組織のセキュリティが正しく強いことが原因なケースが多いです。その場合は設計を切り替えた方が早く、結果として保守もしやすくなります。
MFA環境で安定させるなら:Entra IDアプリ登録+OAuth(Microsoft Graph)
ユーザー名+パスワード方式は、今後さらに厳しくなる方向です。長く運用する自動化(バッチ、ETL、RPA連携)なら、次のいずれかを最初から選んでおくのが安全です。
| 方式 | 向いている用途 | メリット | 注意点 |
|---|---|---|---|
| デバイスコード(Delegated) | 手元PCでの運用、初期検証、管理者が時々実行 | パスワードを持たなくてよい。MFAと相性が良い | 無人実行には不向き(人がコード入力する) |
| クライアント資格情報(Application) | サーバー常駐バッチ、CI/CD、無人実行 | 完全自動化が可能。サービスとして運用しやすい | 権限が強くなりがち。最小権限(Sites.Selected等)検討が重要 |
Graphに寄せると「ファイル操作」も「アクセス制御」もMicrosoft 365全体の作法に沿いやすく、監査や将来の拡張(Teams/OneDrive/Plannerなど)にもつなげやすくなります。
実装例:Microsoft Graphでファイル一覧・ダウンロード・アップロード
ここでは、msal + requests でGraphを叩く最小構成の例を示します。実運用では、秘密情報は環境変数やシークレット管理に置き、ログに出さないようにしてください。
前提:アプリ登録と権限
- Entra ID(Azure AD)でアプリ登録(Client ID / Tenant ID / Secretなどを取得)
- Graphの権限(例:Sites.Read.All や Sites.ReadWrite.All)を付与
- Application権限の場合は管理者同意が必要なことが多い
- 最小権限を狙うなら Sites.Selected を検討(運用設計が必要)
トークン取得(クライアント資格情報フロー)
import os
import msal
import requests
TENANT_ID = os.environ["TENANT_ID"]
CLIENT_ID = os.environ["CLIENT_ID"]
CLIENT_SECRET = os.environ["CLIENT_SECRET"]
AUTHORITY = f"[https://login.microsoftonline.com/{TENANT_ID}](https://login.microsoftonline.com/{TENANT_ID})"
SCOPES = ["[https://graph.microsoft.com/.default](https://graph.microsoft.com/.default)"]
app = msal.ConfidentialClientApplication(
client_id=CLIENT_ID,
authority=AUTHORITY,
client_credential=CLIENT_SECRET,
)
token_result = app.acquire_token_for_client(scopes=SCOPES)
if "access_token" not in token_result:
raise RuntimeError(token_result)
headers = {"Authorization": "Bearer " + token_result["access_token"]}
サイトIDを取得する
Graphでは、まず対象サイトのID(site-id)を得るのが定石です。
import requests
HOSTNAME = "tenant.sharepoint.com"
SITE_PATH = "/sites/YourSite" # ブラウザーで見えるサイトパス
url = f"[https://graph.microsoft.com/v1.0/sites/{HOSTNAME}:{SITE_PATH}](https://graph.microsoft.com/v1.0/sites/{HOSTNAME}:{SITE_PATH})"
site = requests.get(url, headers=headers).json()
site_id = site["id"]
print("site_id:", site_id)
ドキュメントライブラリ(Drive)一覧を取得する
SharePointのドキュメントライブラリはGraphではDriveとして扱われます。まずDrive一覧を取り、対象のライブラリ(例:Documents)を選びます。
url = f"https://graph.microsoft.com/v1.0/sites/{site_id}/drives"
drives = requests.get(url, headers=headers).json()["value"]
for d in drives:
print(d["name"], d["id"])
# 例:名前で選ぶ(運用ではIDを設定に固定する方が安全)
drive_id = next(d["id"] for d in drives if d["name"] in ("Documents", "ドキュメント"))
フォルダー配下の一覧を取得する
Graphは「パス指定」でフォルダーのchildrenを取れるため、パスが分かっている運用では扱いやすいです。
from urllib.parse import quote
folder_path = "Reports/2026" # ドライブ直下からの相対パス(例)
encoded_path = "/".join(quote(p, safe="") for p in folder_path.split("/"))
url = f"[https://graph.microsoft.com/v1.0/drives/{drive_id}/root:/{encoded_path}:/children](https://graph.microsoft.com/v1.0/drives/{drive_id}/root:/{encoded_path}:/children)"
items = requests.get(url, headers=headers).json()["value"]
for it in items:
print(it["name"], it.get("file", {}).get("mimeType", "folder"))
ダウンロード(contentエンドポイント)
ファイルは ...:/content を叩くと中身が取れます。
file_path = "Reports/2026/monthly.xlsx"
encoded_file_path = "/".join(quote(p, safe="") for p in file_path.split("/"))
download_url = f"[https://graph.microsoft.com/v1.0/drives/{drive_id}/root:/{encoded_file_path}:/content](https://graph.microsoft.com/v1.0/drives/{drive_id}/root:/{encoded_file_path}:/content)"
r = requests.get(download_url, headers=headers)
r.raise_for_status()
with open(r"C:\temp\monthly.xlsx", "wb") as f:
f.write(r.content)
アップロード(小容量:PUTで一発)
小さめのファイルはPUTでそのままアップできます。大容量はupload sessionを使います。
upload_path = "Reports/2026/report.csv"
encoded_upload_path = "/".join(quote(p, safe="") for p in upload_path.split("/"))
put_url = f"[https://graph.microsoft.com/v1.0/drives/{drive_id}/root:/{encoded_upload_path}:/content](https://graph.microsoft.com/v1.0/drives/{drive_id}/root:/{encoded_upload_path}:/content)"
with open(r"C:\temp\report.csv", "rb") as f:
data = f.read()
r = requests.put(put_url, headers=headers, data=data)
r.raise_for_status()
print("uploaded item id:", r.json()["id"])
Graph構成にしておくと、将来的に「特定チームサイトだけ許可」「特定ライブラリだけ許可」「監査ログで追える形にする」といった、運用・セキュリティの要求にも乗せやすくなります。
セキュアに運用するための実務ベストプラクティス
「動く」だけで終わらせず、公開運用しても事故りにくい設計に寄せるポイントです。
資格情報をコードに埋め込まない
- 環境変数、Key Vault、Secret Manager、CIのシークレット機能などに保存
- ログにトークンやパスワードが出ないようにする
- リポジトリに誤コミットしない(過去履歴も含めて漏洩リスク)
import os
USERNAME = os.environ.get("SPO_USERNAME")
PASSWORD = os.environ.get("SPO_PASSWORD")
# Graphの場合
CLIENT_SECRET = os.environ.get("CLIENT_SECRET")
リトライとスロットリング対策
- 失敗したら即終了ではなく、指数バックオフ(例:1秒→2秒→4秒)
- 大量ファイル処理は「1件ずつ」より「ページング」「間隔を空ける」
- 夜間バッチでもテナント全体の負荷状況で失敗は起こり得る前提にする
パス運用を固定化する
- 「人が作るフォルダー名」に依存すると、表記ゆれ・全角半角・スペースで壊れやすい
- 処理対象フォルダーは命名規則を決めて固定化(例:YYYY/MM)
- 可能なら、対象Drive IDやサイトIDを設定に固定して、名前検索は初回だけにする
まとめ:最短で動かし、長期運用ではGraphへ寄せる
- SPOのドキュメントライブラリをPythonから操作する定番は、office365系(SharePoint REST)での一覧取得・ダウンロード・アップロード
- 成功の鍵は、サーバー相対URLとURLエンコードを正しく扱うこと
- ユーザー名+パスワードが通らない(MFA等)なら、設計を切り替えてEntra IDアプリ+OAuth(Microsoft Graph)に寄せるのが安定
- 運用で重要なのは、秘密情報の管理・権限設計(最小権限)・リトライや大容量対応まで含めた堅牢化

コメント