透過這篇文章【Google API 教學】Google Drive API upload 使用 Python 上傳單一檔案 基本教學 可以上傳檔案到根目錄底下
但是你想要放置到某個Google 雲端資料夾底下,就需要修改一下程式碼
那要如何改成可以上傳檔案到指定資料夾內呢?
其實不難只要給個路徑就行,那要怎麼寫才是重點對吧!
此文章也會分享程式碼提供給需要的朋友參考。
假設你還不知道怎麼申請Google API 憑證可參考這篇:【Google API 教學】如何申請 OAuth 2.0 憑證? 使用Google Drive API 做示範給你看!
內容目錄
使用 Python 上傳檔案到 Google Drive 指定資料夾 完整程式碼
可以直接將下方完整程式碼複製到你的本地端使用
下方會先介紹三種範例說明,主要都是設定上傳檔案名稱為aaa.txt,上傳到Google Drive 資料夾名稱設定為 TestAPI
from __future__ import print_function
import os
import io
import time
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload, MediaIoBaseDownload
from httplib2 import Http
from oauth2client import file, client, tools
# 權限必須
SCOPES = ['https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive']
def delete_drive_service_file(service, file_id):
service.files().delete(fileId=file_id).execute()
def update_file(service, update_drive_service_name, local_file_path, update_drive_service_folder_id):
"""
將本地端的檔案傳到雲端上
:param update_drive_service_folder_id: 判斷是否有 Folder id 沒有的話,會上到雲端的目錄
:param service: 認證用
:param update_drive_service_name: 存到 雲端上的名稱
:param local_file_path: 本地端的位置
:param local_file_name: 本地端的檔案名稱
"""
print("正在上傳檔案...")
if update_drive_service_folder_id is None:
file_metadata = {'name': update_drive_service_name}
else:
print("雲端資料夾id: %s" % update_drive_service_folder_id)
file_metadata = {'name': update_drive_service_name,
'parents': update_drive_service_folder_id}
media = MediaFileUpload(local_file_path, )
file_metadata_size = media.size()
start = time.time()
file_id = service.files().create(body=file_metadata, media_body=media, fields='id').execute()
end = time.time()
print("上傳檔案成功!")
print('雲端檔案名稱為: ' + str(file_metadata['name']))
print('雲端檔案ID為: ' + str(file_id['id']))
print('檔案大小為: ' + str(file_metadata_size) + ' byte')
print("上傳時間為: " + str(end-start))
return file_metadata['name'], file_id['id']
def search_folder(service, update_drive_folder_name=None):
"""
如果雲端資料夾名稱相同,則只會選擇一個資料夾上傳,請勿取名相同名稱
:param service: 認證用
:param update_drive_folder_name: 取得指定資料夾的id,沒有的話回傳None,給錯也會回傳None
"""
get_folder_id_list = []
print(len(get_folder_id_list))
if update_drive_folder_name is not None:
response = service.files().list(fields="nextPageToken, files(id, name)", spaces='drive',
q = "name = '" + update_drive_folder_name + "' and mimeType = 'application/vnd.google-apps.folder' and trashed = false").execute()
for file in response.get('files', []):
# Process change
print('Found file: %s (%s)' % (file.get('name'), file.get('id')))
get_folder_id_list.append(file.get('id'))
if len(get_folder_id_list) == 0:
print("你給的資料夾名稱沒有在你的雲端上!,因此檔案會上傳至雲端根目錄")
return None
else:
return get_folder_id_list
return None
def search_file(service, update_drive_service_name, is_delete_search_file=False):
"""
本地端
取得到雲端名稱,可透過下載時,取得file id 下載
:param service: 認證用
:param update_drive_service_name: 要上傳到雲端的名稱
:param is_delete_search_file: 判斷是否需要刪除這個檔案名稱
"""
# Call the Drive v3 API
results = service.files().list(fields="nextPageToken, files(id, name)", spaces='drive',
q="name = '" + update_drive_service_name + "' and trashed = false",
).execute()
items = results.get('files', [])
if not items:
print('沒有發現你要找尋的 ' + update_drive_service_name + ' 檔案.')
else:
print('搜尋的檔案: ')
for item in items:
times = 1
print(u'{0} ({1})'.format(item['name'], item['id']))
if is_delete_search_file is True:
print("刪除檔案為:" + u'{0} ({1})'.format(item['name'], item['id']))
delete_drive_service_file(service, file_id=item['id'])
if times == len(items):
return item['id']
else:
times += 1
def trashed_file(service, is_delete_trashed_file=False):
"""
抓取到雲端上垃圾桶內的全部檔案,進行刪除
:param service: 認證用
:param is_delete_trashed_file: 是否要刪除垃圾桶資料
"""
results = service.files().list(fields="nextPageToken, files(id, name)", spaces='drive', q="trashed = true",
).execute()
items = results.get('files', [])
if not items:
print('垃圾桶無任何資料.')
else:
print('垃圾桶檔案: ')
for item in items:
print(u'{0} ({1})'.format(item['name'], item['id']))
if is_delete_trashed_file is True:
print("刪除檔案為:" + u'{0} ({1})'.format(item['name'], item['id']))
delete_drive_service_file(service, file_id=item['id'])
def main(is_update_file_function=False, update_drive_service_folder_name=None, update_drive_service_name=None, update_file_path=None):
"""
:param update_drive_service_folder_name: 給要上傳檔案到雲端的資料夾名稱,預設則是上傳至雲端目錄
:param is_update_file_function: 判斷是否執行上傳的功能
:param update_drive_service_name: 要上傳到雲端上的檔案名稱
:param update_file_path: 要上傳檔案的位置以及名稱
"""
print("is_update_file_function: %s" % is_update_file_function)
print("update_drive_service_folder_name: %s" % update_drive_service_folder_name)
store = file.Storage('token.json')
creds = store.get()
if not creds or creds.invalid:
flow = client.flow_from_clientsecrets('credentials.json', SCOPES)
creds = tools.run_flow(flow, store)
service = build('drive', 'v3', http=creds.authorize(Http()))
print('*' * 10)
if is_update_file_function is True:
print(update_file_path + update_drive_service_name)
print("=====執行上傳檔案=====")
# 清空 雲端垃圾桶檔案
# trashed_file(service=service, is_delete_trashed_file=True)
get_folder_id = search_folder(service = service, update_drive_folder_name = update_drive_service_folder_name)
# 搜尋要上傳的檔案名稱是否有在雲端上並且刪除
search_file(service=service, update_drive_service_name=update_drive_service_name,
is_delete_search_file=True)
# 檔案上傳到雲端上
update_file(service=service, update_drive_service_name=update_drive_service_name,
local_file_path=os.getcwd() + '/' + update_drive_service_name, update_drive_service_folder_id=get_folder_id)
print("=====上傳檔案完成=====")
if __name__ == '__main__':
main(is_update_file_function=bool(True), update_drive_service_folder_name='TestAPI', update_drive_service_name='aaa.txt', update_file_path=os.getcwd() + '/')
Example 1: 先建立一個資料夾名稱為TestAPI,檔案會上傳到這個資料夾內

Example 2: 建立一個TestAPI資料夾裡面再建立一個TestAPI資料夾,兩個資料夾都會有上傳的檔案 Error
看似2020年9月30日起,本文章提供的程式碼沒辦法上傳到相同資料夾名稱,需要用別的方式,否則會出現下面問題,可以參考Google Drive API文檔
googleapiclient.errors.HttpError:
<HttpError 403 when requesting https://www.googleapis.com/upload/drive/v3/files?fields=id&alt=json&uploadType=multipart returned "Increasing the number of parents is not allowed". Details: "[{'domain': 'global', 'reason': 'cannotAddParent', 'message': 'Increasing the number of parents is not allowed'}]">

所以執行後,會將檔案上傳至相同資料夾內 (此文章程式碼再2020年9月30日開始無法上傳至同一個資料夾名稱)
Example 3: 建立一個TestAPI123資料夾,裡面又建立一個TestAPI資料夾,這時檔案只會上傳到TestAPI,不會上傳到TestAPI123


上傳檔案到Google Drive 指定資料夾程式碼 說明
執行程式碼時,我們需要有四個參數值,分別為
- is_update_file_function
- update_drive_service_folder_name
- update_drive_service_name
- update_file_path
is_update_file_function
是否要上傳檔案?預設為True
當初設計主要是針對Jenkins做判斷,上傳還是下載使用。
update_drive_service_folder_name
放置到Google Drive 某個資料夾內,預設為None(放置雲端目錄)
假設你放置到你雲端資料夾請給一個雲端上的資料夾名稱
例如 update_drive_service_folder_name = “TestAPI”,這時程式碼會將你給的檔案名稱丟入到TestAPI內
可能會有以下幾種可能性
資料夾名稱相同
資料夾有多個相同名稱的話,如”TestAPI”,則是都會丟到你的全部等於TestAPI的資料夾
放置檔案到資料夾底下的資料夾
資料夾下方又放一個資料夾,我只想丟到最後一個資料夾裡面又該怎麼做呢?
假設你想放在A資料夾裡面的B資料夾,你只需要給B資料夾名稱就可以!
例如:update_drive_service_folder_name = “B”。
因為在Google Drive上,他會針對每一個資料夾給一個ID。
如果你雲端資料夾名稱給錯的話,則是上傳到雲端目錄喔!此程式碼不會自動建立雲端資料夾。
update_drive_service_name
要上傳本地端檔案到雲端的名稱,需要包含副檔名
update_file_path
如果是放在同一個目錄要上傳,就不需要修改程式碼,如果不同需要修改
不然會找不到你要上傳的檔案。
if __name__ == '__main__':
main(is_update_file_function=bool(True), update_drive_service_folder_name = None, update_drive_service_name='aaa.txt', update_file_path=os.getcwd() + '/')
上傳檔案到Google Drive 指定資料夾程式碼 main 程式碼區塊 說明
我們給他一個資料夾名稱了!那他會怎麼做呢?
請看第25行,這段會去抓你給的雲端資料夾的名稱,抓到有就會回傳給你id,沒有就會回傳給你None
之後在第31行部分,我們將取得到的id或是None給他,因為30~31行主要是執行上傳檔案的功能。
def main(is_update_file_function=False, update_drive_service_folder_name=None, update_drive_service_name=None, update_file_path=None):
"""
:param update_drive_service_folder_name: 給要上傳檔案到雲端的資料夾名稱,預設則是上傳至雲端目錄
:param is_update_file_function: 判斷是否執行上傳的功能
:param update_drive_service_name: 要上傳到雲端上的檔案名稱
:param update_file_path: 要上傳檔案的位置以及名稱
"""
print("is_update_file_function: %s" % is_update_file_function)
print("update_drive_service_folder_name: %s" % update_drive_service_folder_name)
store = file.Storage('token.json')
creds = store.get()
if not creds or creds.invalid:
flow = client.flow_from_clientsecrets('credentials.json', SCOPES)
creds = tools.run_flow(flow, store)
service = build('drive', 'v3', http=creds.authorize(Http()))
print('*' * 10)
if is_update_file_function is True:
print(update_file_path + update_drive_service_name)
print("=====執行上傳檔案=====")
# 清空 雲端垃圾桶檔案
# trashed_file(service=service, is_delete_trashed_file=True)
get_folder_id = search_folder(service = service, update_drive_folder_name = update_drive_service_folder_name)
# 搜尋要上傳的檔案名稱是否有在雲端上並且刪除
search_file(service=service, update_drive_service_name=update_drive_service_name,
is_delete_search_file=True)
# 檔案上傳到雲端上
update_file(service=service, update_drive_service_name=update_drive_service_name,
local_file_path=os.getcwd() + '/' + update_drive_service_name, update_drive_service_folder_id=get_folder_id)
print("=====上傳檔案完成=====")
上傳檔案到Google Drive 指定資料夾程式碼 search_folder 程式碼區塊 說明
什麼情況下才會跑這區塊呢?
當你有給雲端資料夾名稱時,才會執行這塊程式碼,如果沒有就直接回傳None
我們直接看第11~12行,這段的意思是我們現在要開始找雲端上的所有檔案名稱以及資料夾
那我們要開始做篩選的動作 q= 後面這串都是篩選的條件
def search_folder(service, update_drive_folder_name=None):
"""
如果雲端資料夾名稱相同,則只會選擇一個資料夾上傳,請勿取名相同名稱
:param service: 認證用
:param update_drive_folder_name: 取得指定資料夾的id,沒有的話回傳None,給錯也會回傳None
:return:
"""
get_folder_id_list = []
print(len(get_folder_id_list))
if update_drive_folder_name is not None:
response = service.files().list(fields="nextPageToken, files(id, name)", spaces='drive',
q = "name = '" + update_drive_folder_name + "' and mimeType = 'application/vnd.google-apps.folder' and trashed = false").execute()
for file in response.get('files', []):
# Process change
print('Found file: %s (%s)' % (file.get('name'), file.get('id'))) # 找到的符合的檔案印出名稱以及ID
get_folder_id_list.append(file.get('id')) # 將取得的id放置在一個list內,為何是list 因為可能資料夾有多個相同名稱,但id都不同,因此最後會將你要上傳的檔案全部都放置到相同名稱的資料夾內
if len(get_folder_id_list) == 0:
print("你給的資料夾名稱沒有在你的雲端上!,因此檔案會上傳至雲端根目錄")
return None
else:
return get_folder_id_list
return None
篩選條件程式碼 介紹
- name = update_drive_folder_name # 指定的名稱是什麼
- and mimeType = ‘application/vnd.google-apps.folder’ # 和 必須為資料夾
- and trashed = false # 和 我不要垃圾桶的任何資料
上傳檔案到Google Drive 指定資料夾程式碼 update_file 程式碼區塊 說明
這邊是上傳檔案到雲端的程式碼,指定上傳到某個資料夾的參數也就是 update_drive_service_folder_id
如果有就會給一個id,沒有則是給None那該怎麼做呢?
第11~16行部份,如果為None的話,我們則直接將檔案給 file_metadata 也就是說 我只要上傳這個檔案,請幫我放在目錄就好!
那如果你有給資料夾的id呢?
就可以看到有多加一個key(parents),代表的是說 我要上傳檔案,請將把檔案放置到這幾個資料夾內 (資料夾id,list的格式)
def update_file(service, update_drive_service_name, local_file_path, update_drive_service_folder_id):
"""
將本地端的檔案傳到雲端上
:param update_drive_service_folder_id: 判斷是否有 Folder id 沒有的話,會上到雲端的目錄
:param service: 認證用
:param update_drive_service_name: 存到 雲端上的名稱
:param local_file_path: 本地端的位置
:param local_file_name: 本地端的檔案名稱
"""
print("正在上傳檔案...")
if update_drive_service_folder_id is None:
file_metadata = {'name': update_drive_service_name}
else:
print(update_drive_service_folder_id)
file_metadata = {'name': update_drive_service_name,
'parents': update_drive_service_folder_id}
media = MediaFileUpload(local_file_path, )
file_metadata_size = media.size()
start = time.time()
file_id = service.files().create(body=file_metadata, media_body=media, fields='id').execute()
end = time.time()
print("上傳檔案成功!")
print('雲端檔案名稱為: ' + str(file_metadata['name']))
print('雲端檔案ID為: ' + str(file_id['id']))
print('檔案大小為: ' + str(file_metadata_size) + ' byte')
print("上傳時間為: " + str(end-start))
return file_metadata['name'], file_id['id']
注意:你要上傳的檔案名稱,會在執行程式碼的時候,會自動刪除雲端上相同的檔案名稱,在上傳你本地端的檔案
以上是 Python 上傳到 Google Drive 指定資料夾的方式
如果有寫不好的地方請見諒,主要是參考Google Drive文檔以及自己思考架構。
文章內的程式碼都可以直接複製下來練習操作,或是直接套用到自己的專案都行!
多練習,對自已會有很大的幫助!
一回生二回熟,將所學習到內容變成自己的知識。
人就是要學習自己不會的事情,這樣才會不斷的成長、進步!
更多技術文章
透過下方按鈕找尋到相關的技術文章,希望可以幫助到正在學習的你
加入到我的粉絲專頁,不定期發布最新文章資訊!
有任何文章問題都可以詢問喔!