Я хотел бы использовать библиотеку Playwright для очистки веб-страниц внутри Google Cloud Functions.
В целом я новичок в GCP и Python, поэтому на самом деле я не просто ищу решение, а больше изучаю лучшие практики.
Предположим, что мы используем самый простой сценарий использования Playwright для перехода на https://www.google.com, делаем снимок экрана и возвращаем его в браузер с помощью запроса на получение...
До сих пор я пробовал (безуспешно) - ниже:
main.py
import asyncio
from playwright.async_api import async_playwright
from google.cloud import functions_framework
from flask import send_file
import io
@functions_framework.http
def capture_screenshot(request):
screenshot = asyncio.run(take_screenshot())
return send_file(
io.BytesIO(screenshot),
mimetype='image/png',
as_attachment=False,
attachment_filename='screenshot.png'
)
async def take_screenshot():
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
await page.goto('https://www.google.com')
screenshot = await page.screenshot(full_page=True)
await browser.close()
return screenshot
требования.txt
playwright==1.34.0
Flask==2.3.3
google-cloud-functions==1.0.0
Код развертывается, но когда он запускается, в браузер возвращается следующая ошибка:
500 Внутренняя ошибка сервера: на сервере произошла внутренняя ошибка. и не смог выполнить ваш запрос. Либо сервер перегружено или в приложении возникла ошибка.
Наконец, просматривая логи, я получаю следующее:
[2024-08-11 03:41:08,376] ERROR in app: Exception on / [GET]
Traceback (most recent call last):
File "/layers/google.python.pip/pip/lib/python3.11/site-packages/flask/app.py", line 2190, in wsgi_app
response = self.full_dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/layers/google.python.pip/pip/lib/python3.11/site-packages/flask/app.py", line 1486, in full_dispatch_request
rv = self.handle_user_exception(e)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/layers/google.python.pip/pip/lib/python3.11/site-packages/flask/app.py", line 1484, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/layers/google.python.pip/pip/lib/python3.11/site-packages/flask/app.py", line 1469, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/layers/google.python.pip/pip/lib/python3.11/site-packages/functions_framework/__init__.py", line 99, in view_func
return function(request._get_current_object())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/layers/google.python.pip/pip/lib/python3.11/site-packages/functions_framework/__init__.py", line 80, in wrapper
return func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/workspace/main.py", line 9, in capture_screenshot
screenshot = asyncio.run(take_screenshot())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/layers/google.python.runtime/python/lib/python3.11/asyncio/runners.py", line 190, in run
return runner.run(main)
^^^^^^^^^^^^^^^^
File "/layers/google.python.runtime/python/lib/python3.11/asyncio/runners.py", line 118, in run
return self._loop.run_until_complete(task)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/layers/google.python.runtime/python/lib/python3.11/asyncio/base_events.py", line 654, in run_until_complete
return future.result()
^^^^^^^^^^^^^^^
File "/workspace/main.py", line 19, in take_screenshot
browser = await p.chromium.launch()
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/layers/google.python.pip/pip/lib/python3.11/site-packages/playwright/async_api/_generated.py", line 14655, in launch
await self._impl_obj.launch(
File "/layers/google.python.pip/pip/lib/python3.11/site-packages/playwright/_impl/_browser_type.py", line 95, in launch
Browser, from_channel(await self._channel.send("launch", params))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/layers/google.python.pip/pip/lib/python3.11/site-packages/playwright/_impl/_connection.py", line 61, in send
return await self._connection.wrap_api_call(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/layers/google.python.pip/pip/lib/python3.11/site-packages/playwright/_impl/_connection.py", line 482, in wrap_api_call
return await cb()
^^^^^^^^^^
File "/layers/google.python.pip/pip/lib/python3.11/site-packages/playwright/_impl/_connection.py", line 97, in inner_send
result = next(iter(done)).result()
^^^^^^^^^^^^^^^^^^^^^^^^^
playwright._impl._api_types.Error: Executable doesn't exist at /www-data-home/.cache/ms-playwright/chromium-1064/chrome-linux/chrome
╔════════════════════════════════════════════════════════════╗
║ Looks like Playwright was just installed or updated. ║
║ Please run the following command to download new browsers: ║
║ ║
║ playwright install ║
║ ║
║ <3 Playwright Team ║
╚════════════════════════════════════════════════════════════╝
Читая другие сообщения SO и другие фрагменты, которые я обнаружил, проблема, похоже, связана с драйвером Chromium, и было предложено создать Docker непосредственно в Google Run - но не уверен, как это работает - любые предложения или ресурсы будут очень ценю
🤔 А знаете ли вы, что...
Python поддерживает многозадачность и многопоточность.
Вероятно, это связано с тем, что драматург ожидает конкретную версию исполняемого файла Chrome, которая может не существовать в вашей среде. Я могу придумать один простой обходной путь: добавить вызов подпроцесса для установки хрома через CLI драматурга при вызове функции.
import subprocess
subprocess.run(["playwright", "install", "chromium"])
поэтому код будет
import asyncio
from playwright.async_api import async_playwright
from google.cloud import functions_framework
from flask import send_file
import io
import subprocess
@functions_framework.http
def capture_screenshot(request):
subprocess.run(["playwright", "install", "chromium"])
screenshot = asyncio.run(take_screenshot())
return send_file(
io.BytesIO(screenshot),
mimetype='image/png',
as_attachment=False,
attachment_filename='screenshot.png'
)
async def take_screenshot():
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
await page.goto('https://www.google.com')
screenshot = await page.screenshot(full_page=True)
await browser.close()
return screenshot
В качестве альтернативы, как вы упомянули, вы можете не использовать Google Cloud Functions и вместо этого преобразовать его в веб-приложение Dockerized Python, которое вы можете собрать и развернуть в Cloud Run. Причина использования этого подхода заключается в том, что в Dockerfile веб-приложения вы можете добавить дополнительные шаги настройки и установки в CLI, что невозможно с помощью Google Cloud Functions. В этом случае вы можете добавить строку
RUN pip install -r requirements.txt
RUN playwright install chromium
в файл docker, чтобы среда могла установить все зависимости Python вместе с версией Chrome, совместимой с драматургом, при создании образа для веб-приложения Python.