
Teníamos dos formularios de Google, 75 preguntas en cada uno, 5 usuarios comerciales que editaron activamente estos formularios, y también un script de Google que exportaba el formulario a JSON. No es que sea difícil ejecutarlo cada vez con las manos, pero una vez que comience a automatizar su trabajo, continúe en este pasatiempo hasta el final.
En la documentación oficial, el diablo se romperá la pierna, así que debajo del gato veremos más de cerca la descarga remota y el lanzamiento de Google Apps Script a través de la API REST usando Python.
Introduccion
En Doctor Near, estamos desarrollando una plataforma para bots de chat, en la que los formularios de Google se utilizan para describir escenarios. En consecuencia, quiero obtener un JSON de los formularios con solo hacer clic en un botón que contiene nodos (puntos de formulario) y metadatos (transiciones entre nodos, tipos de nodos, su nombre). Parece que el deseo es simple, pero Google no es compatible con esta funcionalidad y tiene que armar este "exportador" con sus propias manos. Considere los pasos para crearlo.
PASO 1. Script de Google Apps
Google ha proporcionado la capacidad de interactuar con sus servicios (Hojas de cálculo, Documentos, Formularios) a través de Google Apps Script: scripts escritos en google script (.gs). Este artículo no proporciona un análisis del lenguaje de script de google, por lo que daré un ejemplo de un script listo que crea JSON a partir de un formulario de Google existente. El
código de usuario
Steven Schmatz fue tomado del github como base, por lo que le expreso mi gratitud.
Lo que sucede en el código:
- Función getFormMetadata : devuelve JSON con metadatos de formulario
- Función itemToObject : convierte el objeto form.item a JSON con los campos obligatorios
- Función sendEmail : envía un archivo JSON al correo especificado en texto
- función principal : devuelve el JSON resultante
- la variable form_url en la función principal es la dirección de nuestro formulario de google
PASO 2. Probar el guión
Por el momento, el rendimiento del script se puede verificar de la siguiente manera:
- crea tu propio proyecto de script de aplicación
- copia el código en él
- en lugar de <YOUR_FORM_URL>, sustituimos la dirección de nuestro formulario del formulario docs.google.com/forms/d/FORM_IDENTIFICATOR/edit
- descomentar la llamada a sendEmail en la función principal
- en lugar de <YOUR_EMAIL> sustituimos la dirección de correo electrónico a la que queremos recibir JSON
- guardar el proyecto
- ejecuta la función principal
- Si esta es la primera ejecución de la secuencia de comandos, el sistema le informará sobre la necesidad de dar permiso a la secuencia de comandos para enviar correos electrónicos desde su dirección. No tengas miedo. Este es el procedimiento estándar requerido para probar el script. Vaya a “Revisar permisos” -> seleccione su cuenta -> “Avanzado” -> “Ir al proyecto PROJECT_NAME (inseguro)” -> “Permitir”
- esperando que el script funcione
- mire en el buzón y vea el archivo JSON en forma de texto
Todo estaría bien, pero el uso adicional de los datos obtenidos implica la copia manual del correo, el procesamiento de este texto (en Python, por ejemplo) y el almacenamiento del archivo resultante. No suena demasiado listo para la producción. Automatizamos el lanzamiento de este script y obtenemos su resultado a través de la API de Google Apps Script, pero primero configuramos nuestro proyecto de Google en consecuencia.
Atención: para la conveniencia de comprender lo que está sucediendo, a continuación me referiré solo a dos páginas, por lo tanto, se recomienda abrirlas en pestañas adyacentes:
- Guión / Página de edición de guiones - “Página 1”
- Página de Google Cloud Platform - "Página 2"
PASO 3. Configure Google Cloud Platform
Vamos a Google Cloud Platform (página 2), creamos un nuevo proyecto. Es necesario crear un nuevo proyecto, porque el estado predeterminado del proyecto es Predeterminado, y Standart es necesario para nuestros propósitos. Más detalles se pueden encontrar
aquí (punto 3).
Regresamos a la página 2, vaya a la pestaña "API y servicios", luego a "Ventana de solicitud de acceso de OAuth". Establezca el Tipo de usuario en "Externo".
En la ventana que aparece, complete el "Nombre de la aplicación".
Abra la página de inicio en Google Cloud Platform. Desde el bloque "Información del proyecto" copie el número del proyecto.
Vaya a la página 1. Abra el script creado anteriormente. En la ventana de edición de script abierta, vaya a "Recursos" -> "Proyecto de plataforma en la nube". En el campo "Cambiar proyecto", ingrese el número de proyecto copiado previamente. Ahora este script está asociado con el proyecto creado.
PASO 4. API REST de Python
Es hora de automatizar el script usando la
API REST . Python fue usado como el lenguaje.
Inicio de sesión de API de Apps Script
El código debe tener acceso al proyecto, por lo que el primer y muy importante procedimiento es el inicio de sesión en la API de Apps Script. Abra la página 2 -> “API y servicios” -> “Credenciales” -> “Crear credenciales” -> “Identificador de cliente OAuth” -> “Otros tipos”. Llamamos a nuestro identificador, ve a él. En la pestaña "Credenciales", seleccione "Descargar archivo JSON". Esto cargará el archivo de clave para acceder desde el código al proyecto en Google. Colocamos este archivo en la carpeta de credenciales.
Ahora debe dar permiso para usar la API (en nuestro caso, la API de script de aplicaciones) como parte de este proyecto. Para hacer esto, vaya a "API y servicios" -> "Biblioteca" -> escriba "API de script de aplicaciones" en la búsqueda y haga clic en "Habilitar".
Las aplicaciones que interactúan con Google tienen muchos permisos que el usuario debe otorgar al iniciarlo. Esta copia depende de las funciones utilizadas por un script en particular y puede encontrarla yendo a la página 1 en la ventana de edición del script en "Archivo" -> "Propiedades del proyecto" -> "Ámbitos". Estos permisos deben conservarse para futuras referencias en el código.
En este caso, la función de inicio de sesión se verá así:
import pickle import os.path from googleapiclient.discovery import build from google_auth_oauthlib.flow import InstalledAppFlow from google.auth.transport.requests import Request def login(config): try: creds = None
Este bloque de código es el procedimiento estándar para comenzar con Google App Script.
Usamos un token de autenticación y, al iniciar sesión, creamos un token nuevo o usamos uno existente.
Por conveniencia, se creó un archivo de configuración JSON, que tiene la siguiente forma:
{ "SCOPES": ["https://www.googleapis.com/auth/forms", "https://www.googleapis.com/auth/script.send_mail"], "credentials_path": "credentials/", "credentials_file": "google_test_project.json", "token_file": "token.pickle" }
Importante: el token se crea para la autenticación con un alcance específico de permisos. En otras palabras, al cambiar el alcance de los permisos, debe eliminar el token y crear uno nuevo al iniciar sesión.
Código de script de actualización remota
Ahora aprenderemos cómo actualizar remotamente el código del script, luego ejecutar este código y obtener el resultado. De hecho, además del código que ejecutamos en el editor de Google, también hay un
archivo de manifiesto que contiene los derechos de inicio, la configuración de implementación, etc. Más información sobre su estructura se puede encontrar
aquí .
Para ver el archivo de manifiesto predeterminado creado por Google para su script, vaya al editor de script en "Ver" -> "Mostrar archivo de manifiesto". El manifiesto aparecerá en la lista de archivos relacionados con este script.
El discurso sobre el manifiesto no fue sin razón: la actualización remota de scripts requiere la descarga del código de ambos archivos (* .gs) y manifiesto (appscript.json).
Primero, lea el código del archivo .gs que queremos implementar:
with open('export-google-form.gs', 'r') as f: sample_code = f.read()
Ahora copie el manifiesto generado automáticamente y modifíquelo un poco para nuestros propósitos. La documentación describe completamente la estructura del archivo de manifiesto, por lo que no me detendré en este punto. Para que el script funcione, debe agregar la sección "executeApi" al manifiesto predeterminado, que se requiere para ejecutar el script de forma remota a través de la API. En esta sección indicamos el círculo de personas que tienen la capacidad de ejecutarlo. Permití el lanzamiento para todos los que han aprobado la autorización, que corresponde al identificador "CUALQUIERA":
MANIFEST = ''' { "timeZone": "America/New_York", "exceptionLogging": "STACKDRIVER", "executionApi": { "access": "ANYONE" } } '''.strip()
El cuerpo de la solicitud de actualización debe contener una matriz de archivos con la siguiente estructura:
- nombre : nombre del archivo que se creará en el servidor, sin extensión
- tipo : tipo de archivo (JSON para manifiesto, SERVER_JS para .gs)
- fuente : código de archivo
request = { 'files': [{ 'name': 'hello', 'type': 'SERVER_JS', 'source': sample_code }, { 'name': 'appsscript', 'type': 'JSON', 'source': MANIFEST } ] }
Finalmente, la solicitud de actualización en sí misma debe contener el cuerpo (solicitud descrita anteriormente) y el ID del script. Este último puede obtenerse yendo al "Archivo" -> "Propiedades del proyecto" en el editor de script y copiando el "ID del script":
script_id = 'qwertyuiopQWERTYUIOPasdfghjkl123456789zxcvbnmASDFGHJKL54'
Para el objeto de servicio obtenido como resultado del inicio de sesión, obtenemos el campo projects () y llamamos al método updateContent (), después de lo cual llamamos al método execute () para el objeto HttpRequest recibido:
service.projects().updateContent( body=request, scriptId=script_id ).execute()
Sin embargo, por ahora, ejecutar el código dará como resultado un error:
"error": { "code": 403, "message": "Request had insufficient authentication scopes.", "status": "PERMISSION_DENIED" }
Como puede ver, no hay suficientes permisos en el águila pescadora de autenticación, que indicamos anteriormente. Pasamos a la documentación oficial en la API, a saber, el método
updateContent , que utilizamos para actualizar el script de forma remota. La documentación dice que usar este método requiere habilitar el acceso a script.projects:
https://www.googleapis.com/auth/script.projects
Agréguelo a nuestro archivo de configuración en la sección ALCANCE. Como escribí anteriormente, al cambiar el águila pescadora, es necesario eliminar el token generado automáticamente.
Genial Por el momento, hemos aprendido a actualizar remotamente el script de Google. Queda por ejecutarlo y obtener el resultado de la ejecución.
Script ejecutado
La solicitud de
inicio del script contiene scriptID y cuerpo con la siguiente estructura:
- función : nombre de la función que queremos ejecutar
- parámetros : (opcional) un conjunto de parámetros de un tipo primitivo (cadena, matriz ...) pasados a la función
- sessionState : (opcional) se requiere solo para aplicaciones de Android
- devMode : (opcional) Verdadero si el usuario es el propietario del script y luego se lanzará la última versión que la que se implementó utilizando la API de Script de Apps. (por defecto - Falso)
Para no unir la URL del formulario de Google en el script, pasaremos
form_url a la función
principal como argumento.
Atencion Cuando probamos el script, la función
principal no aceptaba nada, por lo que cambiaremos las primeras líneas de código en el archivo .gs de la siguiente manera:
function main(form_url) { var form = FormApp.openByUrl(form_url); .......
Como nuestra aplicación no es para Android y somos los propietarios del script, el cuerpo se verá así:
body = { "function": "main", "devMode": True, "parameters": form_url }
Ejecute el script y escriba el resultado de la ejecución en la variable resp:
resp = service.scripts().run(scriptId=script_id, body=body).execute()
Guarde resp en un archivo con el formato JSON conveniente:
import json with open('habr_auto.json', 'w', encoding='utf-8') as f: json.dump(resp['response']['result'], f, ensure_ascii=False, indent=4)
Atencion Debido al hecho de que la solicitud script.run () está esperando el resultado a través del socket, cuando el tiempo de ejecución excede el tiempo de espera, se producirá un error del siguiente tipo:
socket.timeout: The read operation timed out
Para evitar este comportamiento, recomiendo que al comienzo del programa establezca un límite en el tiempo de socket abierto, obviamente suficiente para que espere a que el script termine de ejecutarse. En mi caso, 120 segundos son suficientes:
import socket socket.setdefaulttimeout(120)
Voila! Ya está lista una tubería conveniente para la actualización remota y el lanzamiento de scripts de Google. El código completo adaptado para el lanzamiento desde la terminal se da en mi
github .
Además, daré el código de las funciones principales a continuación
login.py from pprint import pprint import pickle import os.path from googleapiclient.discovery import build from google_auth_oauthlib.flow import InstalledAppFlow from google.auth.transport.requests import Request def login(config): try: creds = None
update_script.py from pprint import pprint import json import sys from googleapiclient import errors from google_habr_login import login MANIFEST = ''' { "timeZone": "America/New_York", "exceptionLogging": "STACKDRIVER", "executionApi": { "access": "ANYONE" } } '''.strip() def update_project(service, script_id, script_file_name):
export_form.py from pprint import pprint import socket import json import sys from googleapiclient import errors from google_habr_login import login socket.setdefaulttimeout(120)
Para comenzar, debe colocar el archivo JSON con las claves de acceso de Google en la carpeta de credenciales y la configuración JSON en el mismo directorio que los scripts.
Luego, si queremos actualizar el script de forma remota, en la llamada del terminal:
python update_script.py <config_file_name> <script_id> <script_file_name>
En este caso:
- config_file_name : nombre del archivo JSON de configuración
- script_id - ID del script
- script_file_name : el nombre del archivo .gs que se cargará en google
Para ejecutar el script, llame a:
python export_form.py <config_file_name> <result_file_name> <script_id> <google_form_url>
En este caso:
- config_file_name : nombre del archivo JSON de configuración
- nombre_archivo_resultado - el nombre del archivo JSON en el que se descargará el formulario
- script_id - ID del script
- google_form_url - url de formulario de google
Gracias por su atención, esperando sus sugerencias y comentarios :)