简化文件共享

Simplified file sharing

使用Python处理Google Drive共享文件夹的编码示例

最近,又出现了一个数据共享的问题,我觉得现在是设计一种处理共享文件夹的方法的好时机。我是一名独立的GIScience专业人员,经常同时与各种组织合作。在我的项目中,我发现每个组织在处理数据方面都有其独特的方法,这是由其独特的文化和工作伦理塑造的,导致了各种各样的方法论。幸运的是,他们有一些共同的做法,其中之一就是使用基于云的数据管理系统,通常是Google,但也可以是OneDrive(来自Microsoft)或Dropbox。

在本文中,我将解释如何在Google生态系统中使用Python处理共享文件夹。

Annie Spratt在Unsplash上的照片

用例

如何在本地机器上管理文件是非常个人化的,并且(希望)在组织中工作时有一些标准化,或者至少有一些标准化。在不直接访问生产文件夹的情况下,通过共享文件夹来共享文件是一种选择,组织可以与您共享一个特定指定的工作文件夹以交换文件。在本例中,某个组织已授予对其Google Drive存储库上名为DATA的文件夹的访问权限,并且我们约定可以使用此文件夹来交换文件。

本地文件管理

为了快速解释,对于不熟悉Google Drive文件共享的人来说,该过程始于收到一封邀请您为特定文件夹做出贡献的电子邮件;请参阅下面的邀请(左侧).在邀请中有一个按钮,点击该按钮将在关联了接收邀请的Google邮件的网页浏览器中打开Google Drive界面(右侧).

图1:创建共享文件夹(作者提供的图像)

界面中隐藏了一些重要信息,提前了解这些信息将有助于后续的过程。

  • 在URL(屏幕顶部)中,有一个掩码ID,这是Google用于跟踪对该文件夹的所有操作的ID,我们将在本文后面的Python代码中获取该ID。
  • 然后,它显示:“与我共享”和共享文件夹的名称;同样,这一点很重要,因为当我们将Google Drive挂载到CoLab笔记本上时,我们会发现该类别不可用。
  • 最后,我们看到了Data文件夹下的文件和文件夹;这意味着我们可以访问所需的信息,并向文件夹中添加新文件。不过,文件夹的安全设置可能存在问题,因此在此阶段进行一个好的测试是创建一个小的文本文件,并将其拖放到“ExternalData”文件夹中,以验证您是否具有完全访问权限。

要使“与我共享”文件夹可访问,我们需要将此文件夹链接到本地/个人驱动器。我们可以通过创建快捷方式来实现这一点,但这是一个手动步骤,对于每个人来说可能不同。要在Google Colab中访问与您共享的文件夹或文件,您需要:

  1. 转到Google Drive中的“与我共享”。
  2. 选择要访问的文件夹或文件。
  3. 右键单击它,然后选择“将快捷方式添加到驱动器”,然后会弹出一个窗口,“选择我的驱动器”,然后点击“添加快捷方式”。
  4. 将快捷方式放在您可以轻松找到的驱动器位置;在我使用的设置中,快捷方式的位置是“__Shared”,确保快捷方式文件夹位于“MyDrive”下的文件夹列表的顶部,然后是一个组织的子目录。
  5. 将快捷方式重命名为有意义的名称;在本例中,我使用“DataDevelopement”。文件位置和名称约定非常个人化,对于程序而言,文件存储在何处以及如何命名并不重要,但是有一定的结构可以节省一些麻烦。
图2:创建快捷方式(作者提供的图片)

在组织好本地文件系统并配置个人谷歌云端硬盘后,我们可以尝试在Python笔记本中使用这个共享文件夹,并自动化项目中的文件共享。

安装

这个项目基于谷歌Colab(或“Collaboratory”)笔记本,我会在本文底部分享。使用这个环境的优势是可以在浏览器中编写和执行Python代码,具有以下特点:

  • 无需配置
  • 免费使用GPU
  • 易于共享

在与具有内部流程的组织合作时,这些都是非常重要的因素,因为作为外部合作者,你通常无法直接访问代码库(原因有很多,从安全问题到项目管理限制都有可能)。Colab笔记本是谷歌生态系统的一部分,并且(作为额外的优势)创建了一个运行时环境,可以挂载个人谷歌云端硬盘(用于文件共享)。

导入模块和包

在这个示例中,只加载了笔记本运行时所需的必要包,我们需要一些特定的库来处理共享驱动器。

谷歌授权

from oauth2client.client import GoogleCredentialsfrom google.colab import auth as google_authgoogle_auth.authenticate_user()from google.colab import drivedrive.mount('/content/gdrive')

使用oauth2client和谷歌凭据可以更轻松地处理文件。还有一些可替代方案,比如下载带有凭据的JSON文件,在某些情况下,使用JSON文件可能优于使用谷歌凭据,但是由于这是一个没有敏感数据的项目,使用oauth2client库提供了足够的保护。

pydrive

from pydrive.auth import GoogleAuthfrom pydrive.drive import GoogleDrivegauth = GoogleAuth()gauth.credentials = GoogleCredentials.get_application_default()drive = GoogleDrive(gauth)

pydrive是google-api-python-client的包装库,简化了许多常见的谷歌云端硬盘API任务,其中之一就是在查询谷歌云端硬盘文件系统时处理获取响应的功能。谷歌云端硬盘通过ID存储所有对象,并且这些ID通过对象中的关联信息进行链接。可以通过API访问这些信息(参见下一个代码块),但是当我们使用带有Files.list()参数的字典创建GoogleDriveFileList实例时,包装库会为我们完成所有繁重的工作。调用GetList()将获取与查询匹配的所有文件作为GoogleDriveFile列表。

谷歌API客户端

# 谷歌API客户端:from googleapiclient.discovery import build# 初始化谷歌云端硬盘API客户端drive_service = build('drive', 'v3')

谷歌API客户端是一个庞大的库,具有许多功能,但是对于这个项目,我们只需要一个模块:build。build模块用于构建与API交互的资源对象,并返回与服务交互的方法。pydrive库将很好地处理基本功能,如创建、更新和删除文件,但在这个项目中有几个时刻我们需要更高级的功能,并且访问“service”允许我们提取pydrive方法未捕获的信息。

以上是笔记本的配置内容。在这个示例中,我们只需要加载用于文件管理的库,加载完成后,我们可以看一下它们的功能。

笔记本中的文件管理

到目前为止,已经发生了一些事情:

  • 谷歌授权已设置好
  • 我们创建了对驱动器的访问(用于读写访问)
  • Pydrive软件包可用于在驱动器上导航

希望在跟随并运行代码时,您可以在刷新面板后在右侧看到图像。您可以在图像上看到快捷方式,它作为“__Shared”下的一个文件夹,我们没有看到“与我共享”的部分,但是因为我们有了快捷方式,所以不需要看到“与我共享”的文件。

Figure 3: Google Colab 网页界面中运行环境的未挂载与已挂载状态(作者提供的图像)

Google Drive 与本地操作系统的文件管理方式有所不同,文件的物理位置并不重要,因为文件以 ID 的形式在一个非结构化的 DataLake 中进行管理,我们可以通过 ID 来访问文件和文件夹。

不幸的是,虽然 Python 中的 os.path 提供了用于遍历文件系统的函数,但是 Google Drive 并没有类似的方法(或者我不知道这个方法)。然而,我们可以使用 pydrive 库,在目录树中手动遍历文件夹,并且幸运的是,通过文件夹的路径,我们知道我们想要去哪里。所以我们不需要遍历整个结构,而是可以使用数据路径的文件夹名称来深入到文件夹树中。

因此,我们循环遍历这个小列表(在这个示例中是三个项目),以找到 ID,并使用该 ID 前往下一级。请注意,第四级被注释掉了,我们将在本笔记本的文件处理部分的第二部分中讲解到这一级。

# 文件处理测试:# 在这个示例中有三个文件夹级别:# /content/gdrive/MyDrive/__Shared/<your Project>/DataDevelopment# 将这些内容更新为你的结构:folderList1 = ["__Shared", your_Project ,"DataDevelopment"] #, "ExternalData"]

下面的代码块中的循环从根目录开始,当在列表中找到一个项目时,循环将使用对象的 ID 前往列表中的下一级,如果未找到项目,则代码将提示找不到该文件夹,并且不会再深入查找任何文件夹。循环以快捷方式文件夹的 ID 或提示找不到文件夹结束。

# 尝试复制创建的虚拟文件:boo_foundFolder = FalsefileID = "root"level = 0# 查看 Google Drive 中的所有文件夹和文件# 首先循环遍历列表:print("文件和文件夹结构 - 使用 ID 进行检查")for folderName in folderList1:  print(f"正在检查:{folderName}")  if boo_foundFolder or fileID == "root": # 第一次运行    boo_foundFolder = False    fileList = drive.ListFile({'q': f"'{fileID}' in parents and trashed=false"}).GetList()    for file in fileList:      # 测试名称:            if(file['title'] == folderName):        fileID = file['id']        boo_foundFolder = True        level += 1       # 结束 if    # 结束 for    if boo_foundFolder == False:      print(f"未找到文件夹")      break    # 结束 if      # 结束 if# 结束 forprint(f"我们是否找到了文件夹:{boo_foundFolder}")if boo_foundFolder:  print(fileID)  ShortCutID = fileIDelse:  ShortCutID = 0

此时,我们已经获得了工作文件夹的本地文件 ID,但在可以查找此位置的文件之前,我们需要将此本地 ID 与共享文件夹的目标 ID 进行匹配。要找到此信息,我们需要更深入地了解 Google 基础架构,为此,我们需要一个帮助程序:drive_service。我们在加载项目时激活了这个帮助程序,并且没有收到警告,这意味着我们可以通过使用 API 访问该服务,并通过 ID 请求信息。

我们最好通过一个简单的函数来收集所需的详细信息,如下一个代码块中的 findTargetID 函数所示。在此函数中,fileID 是我们通过循环遍历文件夹中的名称找到的快捷方式 ID,并且通过调用 drive_service.files().get 并指定字段,我们可以获得文件夹的目标 ID(这与 Google Drive 网页界面的 URL 中的 ID 相同,参见图1)。

def findTargetID(fileID, drive_service):  # 要获取 ShortcutDetails 的共享文件的 ID  file_id = fileID  try:      # 获取文件详情      file = drive_service.files().get(fileId=file_id,                                       fields="id, shortcutDetails").execute()      # 检查文件是否是快捷方式      if 'shortcutDetails' in file:          shortcut_details = file['shortcutDetails']          print("快捷方式详情:")          print(f"目标 ID:{shortcut_details['targetId']}")          print(f"目标 MIME 类型:{shortcut_details['targetMimeType']}")      else:          print("该文件不是快捷方式。")      # 结束 if  except Exception as e:      print(f"发生错误:{e}")  return shortcut_details['targetId']if boo_foundFolder:  targetID = findTargetID(fileID, drive_service)  print(targetID)else:  print("未找到文件夹")# 结束 if

有了这个目标ID,我们可以访问Google数据服务器上的实际共享文件夹,不再使用快捷方式文件夹。

回顾一下,我们创建快捷方式文件夹的原因是为了能够在我们挂载的文件夹列表中看到该文件夹。类别“与我共享”的文件夹没有被挂载,但快捷方式有。因此,有了这个新的ID,我们可以查找文件。

查找文件

现在我们有了我们需要的东西,即在过程开始时与我们共享的文件夹的目标ID,有了该ID,我们可以进行所有正常的文件操作。

我们可以通过在运行时环境中先创建一个小的文本文件来验证我们对共享文件夹具有足够的权限;创建此文件还确认我们可以访问运行时环境,因为当文件被正确创建时,它将出现在CoLab笔记本的Web界面的左侧面板中。

# 创建一个测试文件:with open('example.txt', 'w') as f:  f.write('这是一个测试文件,用于测试CoLab文件共享')# 现在,我们的想法是将此文件移动到“与我共享”的文件夹“Data”中,我们在快捷方式中将其重命名为“DataDevelopment”,但是前一节中的函数提供了<目标ID>,我们现在可以使用该ID检查我们刚刚在运行时环境中创建的文件是否在共享驱动器上可用。
if boo_foundFolder:  print("找到文件夹")  folderID = targetID  file_on_drive = False  file_id = 0    # 检查文件是否在驱动器上:  fileList = drive.ListFile({'q': f"'{folderID}' in parents and trashed=false"}).GetList()  for file in fileList:    if(file['title'] == "example.txt"):      file_on_drive = True      fileID = file['id']    # end if  # end for  if file_on_drive:  #覆盖现有的Google驱动器文件。    file1 = drive.CreateFile({'id': fileID})    strFileHandling = "已更新"  else:    file1 = drive.CreateFile({"mimeType": "text/csv",                             "parents": [{"kind": "drive#fileLink",                                         "id": folderID}]})    strFileHandling = "已创建"  # end if  # 创建与运行时环境中的文件的绑定:  file1.SetContentFile("example.txt")  # 将文件复制到Google驱动器:  file1.Upload()  print(f'{strFileHandling} 文件 %s with mimeType %s' % (file1['title'], file1['mimeType']))else:  print("未找到文件夹")# end if

运行上面的代码将创建一个新文件夹,或者在找到文件时更新(覆盖)该文件。

创建工作空间

使用快捷方式ID查找目标ID的第二个原因是查找共享文件夹下的项目。如前所述,Google Drive通过ID管理所有内容,快捷方式ID没有任何子项,因此使用该ID查找新项目将得到一个空列表。可以通过在第一个文件夹列表中包含“ExternalData”文件夹名称来测试此功能;第一个列表将找不到此文件夹。但是,使用目标ID重新启动循环将找到此文件夹。

在下面的代码片段中,使用“Shared with me”文件夹名称下面的文件夹名称创建了一个新的文件夹列表。 “ExternalData”文件夹可用(参见图1),但“NewDataFolder”尚未创建。

# 将以下内容更新为您的结构:# ... DataDevelopment/ExternalData/__CoLab_Notebook # 设置工作文件夹:folderList2 = ["ExternalData", "NewDataFolder"]

我们可以使用与之前相同的循环结构,但现在不是从ROOT开始,而是从目标ID开始,循环将找到“ExternalData”文件夹,但不会找到新数据文件夹。

由于工作文件夹尚不存在,我们可以使用drive_service.files来创建这个新文件夹,并且使用相同的方法,将需要从运行时环境传输到“与我共享”文件夹的所有文件。

def create_folder_in_folder(folder_name, parent_folder_id, drive_service):
    file_metadata = {
        'name': folder_name,
        'parents': [parent_folder_id],
        'mimeType': 'application/vnd.google-apps.folder'
    }
    file = drive_service.files().create(body=file_metadata, supportsAllDrives=True,
                                         fields='id').execute()
    print('文件夹ID:%s' % file.get('id'))

if WorkingFolderID == 0:   # fileID是前一个搜索的父ID  create_folder_in_folder("新数据文件夹", fileID, drive_service)

主要要点: Google Drive文件系统是ID驱动的,所有对象都有ID。 在Google Colab中无法访问“与我共享”的对象,但通过“快捷方式”可以访问它们,并通过找到相关的目标ID,我们可以直接在最初与我们分享的“与我共享”的文件夹中处理包括文件夹下的对象。

结论

在本文中,我们介绍了与共享文件夹一起工作的一些重要方面,包括:

  1. 设置本地文件管理: 我们从接收到一个贡献到指定Google Drive目录的邀请开始,展示了如何组织本地文件系统以提高协作效率。
  2. 配置Google Colab进行协作: 我们讨论了使用Google Colab进行协作的优势,这是一个协作性的Python环境,并介绍了如何为项目协作进行设置。
  3. 导入必要的模块和包: 我们提供了导入必要模块和包的代码示例,包括Google授权、简化Google Drive API任务的pydrive,以及用于高级功能的Google API客户端。
  4. 笔记本中的文件管理: 您将了解如何在Google Colab环境中管理文件,包括使用Shared ID和Target ID在本地环境和共享文件夹之间创建和移动文件。
  5. 查找文件和创建工作空间: 我们深入探讨了使用目标ID在共享文件夹中查找文件的过程,以及为项目创建新文件夹和工作空间。

希望本文对于在组织间共享文件夹和文件的工作有所帮助,并能对如何在共享文件夹中处理文件和文件夹提供一些见解。

感谢阅读,希望本文帮助您解决问题或为下一个项目提供了思路。

Google CoLab笔记本链接:gist

免责声明:本示例中使用的代码不是优化过的,而是为了说明过程而编写的(对于如何改进代码的建议,请在托管此笔记本的gitHub页面上提出)。