如何利用GA4 Admin API导出包含用户组、服务账号等所有类型权限主体的账号及媒体资源权限用户信息(Python脚本实现)
嗨,我来帮你搞定这个问题!你之前用accounts.accessBindings和properties.accessBindings接口只拿到了user类型的主体,其实是因为你可能没正确解析返回结果——这两个接口本身是会返回所有类型权限主体的,包括用户组、服务账号,只是它们的格式和USER类型不一样而已。下面我一步步给你讲怎么用Python脚本导出所有类型的权限信息,还能实现类似网页里的展开用户组成员的功能。
一、先搞清楚核心问题:为什么你之前只看到USER类型?
你提到在网页端能看到用户组、服务账号,但API返回里没注意到——其实GA4 Admin API返回的accessBinding对象里,member字段是类型:标识符的格式(比如group:marketing-team@yourdomain.com、serviceAccount:my-sa@project.iam.gserviceaccount.com),同时还有memberType字段直接标记主体类型(GROUP、SERVICE_ACCOUNT、USER)。你之前可能只过滤了USER类型,或者没拆分member字段,所以才只看到USER类型的结果。
二、准备工作
- 确保你的服务账号(或认证账号)有GA4 Admin API的访问权限(比如
Analytics Readonly或Analytics Admin Editor权限) - 安装必要的Python依赖:
pip install google-analytics-admin google-auth google-api-python-client
三、基础版脚本:导出所有权限主体(含用户组、服务账号)
这个脚本会遍历你的所有GA4账号和媒体资源,导出所有类型的权限主体信息,包括用户、用户组、服务账号,最后保存为CSV文件。
from google.analytics.admin import AnalyticsAdminServiceClient from google.oauth2 import service_account import csv # 替换成你的服务账号密钥文件路径 SERVICE_ACCOUNT_KEY_PATH = "path/to/your-service-account-key.json" def authenticate(): # 用服务账号认证 credentials = service_account.Credentials.from_service_account_file( SERVICE_ACCOUNT_KEY_PATH, scopes=["https://www.googleapis.com/auth/analytics.readonly"] ) return AnalyticsAdminServiceClient(credentials=credentials) def fetch_account_permissions(client, account_id): """获取指定账号的所有权限绑定""" permissions = [] # 调用账号权限接口 request = client.list_account_access_bindings(parent=f"accounts/{account_id}") for binding in request: # 拆分member字段提取类型和主体标识 member_parts = binding.member.split(":", 1) member_type = member_parts[0].upper() member_id = member_parts[1] permissions.append({ "层级": "账号", "层级ID": account_id, "主体类型": member_type, "主体标识": member_id, "权限角色": binding.role, "显示名称": getattr(binding, "display_name", "无") }) return permissions def fetch_property_permissions(client, property_id): """获取指定媒体资源的所有权限绑定""" permissions = [] # 调用媒体资源权限接口 request = client.list_property_access_bindings(parent=f"properties/{property_id}") for binding in request: member_parts = binding.member.split(":", 1) member_type = member_parts[0].upper() member_id = member_parts[1] permissions.append({ "层级": "媒体资源", "层级ID": property_id, "主体类型": member_type, "主体标识": member_id, "权限角色": binding.role, "显示名称": getattr(binding, "display_name", "无") }) return permissions def main(): client = authenticate() all_permissions = [] # 遍历所有GA4账号 print("开始遍历所有账号...") for account in client.list_accounts(): account_id = account.name.split("/")[-1] print(f"正在处理账号: {account.display_name} (ID: {account_id})") # 获取账号层级权限 account_perms = fetch_account_permissions(client, account_id) all_permissions.extend(account_perms) # 遍历该账号下的所有媒体资源 for prop in client.list_account_properties(parent=f"accounts/{account_id}"): prop_id = prop.name.split("/")[-1] print(f" 正在处理媒体资源: {prop.display_name} (ID: {prop_id})") # 获取媒体资源层级权限 prop_perms = fetch_property_permissions(client, prop_id) all_permissions.extend(prop_perms) # 导出到CSV文件 output_file = "ga4_all_permissions.csv" with open(output_file, "w", newline="", encoding="utf-8-sig") as f: fieldnames = ["层级", "层级ID", "主体类型", "主体标识", "权限角色", "显示名称"] writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() writer.writerows(all_permissions) print(f"所有权限信息已成功导出到 {output_file}!") if __name__ == "__main__": main()
脚本说明
- 认证部分用服务账号密钥,确保密钥文件路径正确
- 脚本会自动遍历所有账号和对应的媒体资源,不需要手动输入ID
- 导出的CSV里会清晰显示每个权限主体的类型(USER/GROUP/SERVICE_ACCOUNT)、标识(邮箱)、所属层级、权限角色等信息
四、进阶版:展开用户组成员到具体用户
如果你需要像网页端那样“展开所有用户组到成员”,也就是把用户组替换成组内的具体用户,需要额外调用Google Workspace Admin SDK Directory API(需要Workspace管理员授权服务账号的相关权限)。
新增功能代码
在基础版脚本中添加以下函数:
from googleapiclient.discovery import build def get_group_members(group_email, credentials): """获取指定Google Workspace用户组的所有成员""" members = [] # 初始化Directory API服务 service = build('admin', 'directory_v1', credentials=credentials) # 批量获取组成员 results = service.members().list(groupKey=group_email).execute() members.extend(results.get('members', [])) # 处理分页 while 'nextPageToken' in results: page_token = results['nextPageToken'] results = service.members().list(groupKey=group_email, pageToken=page_token).execute() members.extend(results.get('members', [])) # 提取成员邮箱(过滤无效条目) return [member['email'] for member in members if member.get('email')]
然后修改fetch_account_permissions和fetch_property_permissions函数,在处理GROUP类型时展开成员:
def fetch_account_permissions(client, account_id, credentials=None): """获取指定账号的所有权限绑定,支持展开用户组成员""" permissions = [] request = client.list_account_access_bindings(parent=f"accounts/{account_id}") for binding in request: member_parts = binding.member.split(":", 1) member_type = member_parts[0].upper() member_id = member_parts[1] # 如果是用户组且需要展开成员 if member_type == "GROUP" and credentials: group_members = get_group_members(member_id, credentials) for member_email in group_members: permissions.append({ "层级": "账号", "层级ID": account_id, "主体类型": "USER", "主体标识": member_email, "权限角色": binding.role, "显示名称": f"来自用户组: {binding.display_name}" }) # 可选:保留用户组本身的权限条目 permissions.append({ "层级": "账号", "层级ID": account_id, "主体类型": "GROUP", "主体标识": member_id, "权限角色": binding.role, "显示名称": binding.display_name }) else: permissions.append({ "层级": "账号", "层级ID": account_id, "主体类型": member_type, "主体标识": member_id, "权限角色": binding.role, "显示名称": getattr(binding, "display_name", "无") }) return permissions # 同理修改fetch_property_permissions函数,逻辑和上面一致 def fetch_property_permissions(client, property_id, credentials=None): """获取指定媒体资源的所有权限绑定,支持展开用户组成员""" permissions = [] request = client.list_property_access_bindings(parent=f"properties/{property_id}") for binding in request: member_parts = binding.member.split(":", 1) member_type = member_parts[0].upper() member_id = member_parts[1] # 如果是用户组且需要展开成员 if member_type == "GROUP" and credentials: group_members = get_group_members(member_id, credentials) for member_email in group_members: permissions.append({ "层级": "媒体资源", "层级ID": property_id, "主体类型": "USER", "主体标识": member_email, "权限角色": binding.role, "显示名称": f"来自用户组: {binding.display_name}" }) # 可选:保留用户组本身的权限条目 permissions.append({ "层级": "媒体资源", "层级ID": property_id, "主体类型": "GROUP", "主体标识": member_id, "权限角色": binding.role, "显示名称": binding.display_name }) else: permissions.append({ "层级": "媒体资源", "层级ID": property_id, "主体类型": member_type, "主体标识": member_id, "权限角色": binding.role, "显示名称": getattr(binding, "display_name", "无") }) return permissions
最后在main函数中,把认证后的credentials传递给权限获取函数:
def main(): credentials = service_account.Credentials.from_service_account_file( SERVICE_ACCOUNT_KEY_PATH, scopes=["https://www.googleapis.com/auth/analytics.readonly", "https://www.googleapis.com/auth/admin.directory.group.member.readonly"] ) client = AnalyticsAdminServiceClient(credentials=credentials) all_permissions = [] print("开始遍历所有账号...") for account in client.list_accounts(): account_id = account.name.split("/")[-1] print(f"正在处理账号: {account.display_name} (ID: {account_id})") # 传递credentials用于展开用户组 account_perms = fetch_account_permissions(client, account_id, credentials) all_permissions.extend(account_perms) for prop in client.list_account_properties(parent=f"accounts/{account_id}"): prop_id = prop.name.split("/")[-1] print(f" 正在处理媒体资源: {prop.display_name} (ID: {prop_id})") prop_perms = fetch_property_permissions(client, prop_id, credentials) all_permissions.extend(prop_perms) # 导出逻辑不变 output_file = "ga4_all_permissions.csv" with open(output_file, "w", newline="", encoding="utf-8-sig") as f: fieldnames = ["层级", "层级ID", "主体类型", "主体标识", "权限角色", "显示名称"] writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() writer.writerows(all_permissions) print(f"所有权限信息已成功导出到 {output_file}!") if __name__ == "__main__": main()
进阶版说明
- 需要Workspace管理员给服务账号授权
Admin Directory Group Member Readonly权限 - 新增的
get_group_members函数会批量获取用户组的所有成员,处理分页避免遗漏 - 你可以选择是否保留用户组本身的权限条目,或者只保留展开后的用户条目
备注:内容来源于stack exchange,提问作者THITINUN CHOTIGAVANICH

