def ret_loop(): loop = events.new_event_loop() try: events.set_event_loop(loop) # loop.set_debug(debug) return loop.run_until_complete(get_browser()) # except Exception as e: finally: try: _cancel_all_tasks(loop) loop.run_until_complete(loop.shutdown_asyncgens()) finally: events.set_event_loop(None) loop.close() # print(e) # print('adfafdadsf') if __name__=='__main__': # asyncio.run(get_browser()) # asyncio.get_event_loop().run_until_complete(get_browser()) ret_loop()
我自己写的这个ret_loop方法其实是照抄的
asyncio.run
__all__ = 'run', from . import coroutines from . import events from . import tasks def run(main, *, debug=False): """Execute the coroutine and return the result. This function runs the passed coroutine, taking care of managing the asyncio event loop and finalizing asynchronous generators. This function cannot be called when another asyncio event loop is running in the same thread. If debug is True, the event loop will be run in debug mode. This function always creates a new event loop and closes it at the end. It should be used as a main entry point for asyncio programs, and should ideally only be called once. Example: async def main(): await asyncio.sleep(1) print('hello') asyncio.run(main()) """ if events._get_running_loop() is not None: raise RuntimeError( "asyncio.run() cannot be called from a running event loop") if not coroutines.iscoroutine(main): raise ValueError("a coroutine was expected, got {!r}".format(main)) loop = events.new_event_loop() try: events.set_event_loop(loop) loop.set_debug(debug) return loop.run_until_complete(main) finally: try: _cancel_all_tasks(loop) loop.run_until_complete(loop.shutdown_asyncgens()) finally: events.set_event_loop(None) loop.close()
发现报错的是最后一句话,完整的错误信息如下:
Error in atexit._run_exitfuncs: Traceback (most recent call last): File "C:UsersAdministratorAppDataLocalProgramsPythonPython38libsite-packagespyppeteerlauncher.py", line 174, in _close_process self._loop.run_until_complete(self.killChrome()) File "C:UsersAdministratorAppDataLocalProgramsPythonPython38libasyncioase_events.py", line 591, in run_until_complete self._check_closed() File "C:UsersAdministratorAppDataLocalProgramsPythonPython38libasyncioase_events.py", line 508, in _check_closed raise RuntimeError('Event loop is closed') RuntimeError: Event loop is closed sys:1: RuntimeWarning: coroutine 'Launcher.killChrome' was never awaited RuntimeWarning: Enable tracemalloc to get the object allocation traceback
那这个
'Launcher.killChrome'为什么失败呢?
找到这个方法:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """Chromium process launcher module.""" import asyncio import atexit import json from urllib.request import urlopen from urllib.error import URLError import logging import os import os.path from pathlib import Path import shutil import signal import subprocess import sys import tempfile import time from typing import Any, Dict, List, TYPE_CHECKING from pyppeteer import __pyppeteer_home__ from pyppeteer.browser import Browser from pyppeteer.connection import Connection from pyppeteer.errors import BrowserError from pyppeteer.helper import addEventListener, debugError, removeEventListeners from pyppeteer.target import Target from pyppeteer.util import check_chromium, chromium_executable from pyppeteer.util import download_chromium, merge_dict, get_free_port if TYPE_CHECKING: from typing import Optional # noqa: F401 logger = logging.getLogger(__name__) pyppeteer_home = Path(__pyppeteer_home__) CHROME_PROFILE_PATH = pyppeteer_home / '.dev_profile' DEFAULT_ARGS = [ '--disable-background-networking', '--disable-background-timer-throttling', '--disable-breakpad', '--disable-browser-side-navigation', '--disable-client-side-phishing-detection', '--disable-default-apps', '--disable-dev-shm-usage', '--disable-extensions', '--disable-features=site-per-process', '--disable-hang-monitor', '--disable-popup-blocking', '--disable-prompt-on-repost', '--disable-sync', '--disable-translate', '--metrics-recording-only', '--no-first-run', '--safebrowsing-disable-auto-update', ] AUTOMATION_ARGS = [ '--enable-automation', '--password-store=basic', '--use-mock-keychain', ] class Launcher(object): """Chrome process launcher class.""" def __init__(self, options: Dict[str, Any] = None, # noqa: C901 **kwargs: Any) -> None: """Make new launcher.""" self.options = merge_dict(options, kwargs) self.port = get_free_port() self.url = f'http://127.0.0.1:{self.port}' self.chrome_args: List[str] = [] self._loop = self.options.get('loop', asyncio.get_event_loop()) logLevel = self.options.get('logLevel') if logLevel: logging.getLogger('pyppeteer').setLevel(logLevel) if not self.options.get('ignoreDefaultArgs', False): self.chrome_args.extend(DEFAULT_ARGS) self.chrome_args.append( f'--remote-debugging-port={self.port}', ) self.chromeClosed = True if self.options.get('appMode', False): self.options['headless'] = False elif not self.options.get('ignoreDefaultArgs', False): self.chrome_args.extend(AUTOMATION_ARGS) self._tmp_user_data_dir: Optional[str] = None self._parse_args() if self.options.get('devtools'): self.chrome_args.append('--auto-open-devtools-for-tabs') self.options['headless'] = False if 'headless' not in self.options or self.options.get('headless'): self.chrome_args.extend([ '--headless', '--disable-gpu', '--hide-scrollbars', '--mute-audio', ]) def _is_default_url() -> bool: for arg in self.options['args']: if not arg.startswith('-'): return False return True if (not self.options.get('ignoreDefaultArgs') and isinstance(self.options.get('args'), list) and _is_default_url()): self.chrome_args.append('about:blank') if 'executablePath' in self.options: self.exec = self.options['executablePath'] else: if not check_chromium(): download_chromium() self.exec = str(chromium_executable()) self.cmd = [self.exec] + self.chrome_args def _parse_args(self) -> None: if (not isinstance(self.options.get('args'), list) or not any(opt for opt in self.options['args'] if opt.startswith('--user-data-dir'))): if 'userDataDir' not in self.options: if not CHROME_PROFILE_PATH.exists(): CHROME_PROFILE_PATH.mkdir(parents=True) self._tmp_user_data_dir = tempfile.mkdtemp( dir=str(CHROME_PROFILE_PATH)) self.chrome_args.append('--user-data-dir={}'.format( self.options.get('userDataDir', self._tmp_user_data_dir))) if isinstance(self.options.get('args'), list): self.chrome_args.extend(self.options['args']) def _cleanup_tmp_user_data_dir(self) -> None: for retry in range(100): if self._tmp_user_data_dir and os.path.exists( self._tmp_user_data_dir): shutil.rmtree(self._tmp_user_data_dir, ignore_errors=True) if os.path.exists(self._tmp_user_data_dir): time.sleep(0.01) else: break else: raise IOError('Unable to remove Temporary User Data') async def launch(self) -> Browser: # noqa: C901 """Start chrome process and return `Browser` object.""" self.chromeClosed = False self.connection: Optional[Connection] = None options = dict() options['env'] = self.options.get('env') if not self.options.get('dumpio'): options['stdout'] = subprocess.PIPE options['stderr'] = subprocess.STDOUT self.proc = subprocess.Popen( # type: ignore self.cmd, **options, ) def _close_process(*args: Any, **kwargs: Any) -> None: if not self.chromeClosed: self._loop.run_until_complete(self.killChrome()) # don't forget to close browser process if self.options.get('autoClose', True): atexit.register(_close_process) if self.options.get('handleSIGINT', True): signal.signal(signal.SIGINT, _close_process) if self.options.get('handleSIGTERM', True): signal.signal(signal.SIGTERM, _close_process) if not sys.platform.startswith('win'): # SIGHUP is not defined on windows if self.options.get('handleSIGHUP', True): signal.signal(signal.SIGHUP, _close_process) connectionDelay = self.options.get('slowMo', 0) self.browserWSEndpoint = self._get_ws_endpoint() logger.info(f'Browser listening on: {self.browserWSEndpoint}') self.connection = Connection( self.browserWSEndpoint, self._loop, connectionDelay) ignoreHTTPSErrors = bool(self.options.get('ignoreHTTPSErrors', False)) setDefaultViewport = not self.options.get('appMode', False) browser = await Browser.create( self.connection, [], ignoreHTTPSErrors, setDefaultViewport, self.proc, self.killChrome) await self.ensureInitialPage(browser) return browser
注意看下这个方法:
async def launch(self) -> Browser: # noqa: C901 """Start chrome process and return `Browser` object.""" self.chromeClosed = False self.connection: Optional[Connection] = None options = dict() options['env'] = self.options.get('env') if not self.options.get('dumpio'): options['stdout'] = subprocess.PIPE options['stderr'] = subprocess.STDOUT self.proc = subprocess.Popen( # type: ignore self.cmd, **options, ) def _close_process(*args: Any, **kwargs: Any) -> None: if not self.chromeClosed: self._loop.run_until_complete(self.killChrome()) # don't forget to close browser process if self.options.get('autoClose', True): atexit.register(_close_process) if self.options.get('handleSIGINT', True): signal.signal(signal.SIGINT, _close_process) if self.options.get('handleSIGTERM', True): signal.signal(signal.SIGTERM, _close_process) if not sys.platform.startswith('win'): # SIGHUP is not defined on windows if self.options.get('handleSIGHUP', True): signal.signal(signal.SIGHUP, _close_process) connectionDelay = self.options.get('slowMo', 0) self.browserWSEndpoint = self._get_ws_endpoint() logger.info(f'Browser listening on: {self.browserWSEndpoint}') self.connection = Connection( self.browserWSEndpoint, self._loop, connectionDelay) ignoreHTTPSErrors = bool(self.options.get('ignoreHTTPSErrors', False)) setDefaultViewport = not self.options.get('appMode', False) browser = await Browser.create( self.connection, [], ignoreHTTPSErrors, setDefaultViewport, self.proc, self.killChrome) await self.ensureInitialPage(browser) return browser
于是乎,找到了bug之源!!!!!~
就是他们
def _close_process(*args: Any, **kwargs: Any) -> None: if not self.chromeClosed: self._loop.run_until_complete(self.killChrome()) # don't forget to close browser process if self.options.get('autoClose', True): atexit.register(_close_process) if self.options.get('handleSIGINT', True): signal.signal(signal.SIGINT, _close_process) if self.options.get('handleSIGTERM', True): signal.signal(signal.SIGTERM, _close_process) if not sys.platform.startswith('win'): # SIGHUP is not defined on windows if self.options.get('handleSIGHUP', True): signal.signal(signal.SIGHUP, _close_process)
程序结束时候注册一个事件,就是上面那个函数,会使用当前的loop来close掉浏览器
可是这个run方法,已经在程序退出之前就finally里面close掉了loop
于是乎,GG思密达,我也是强迫症,看不得红色!!!!虽然并没有什么影响!!!!
就!是!不!能!报!红!!!!
解决方法当然很简单:
直接
asyncio.get_event_loop().run_until_complete(get_browser())
不用你run就行了
当然还有一种,关闭那个程序退出时候自动杀掉进程的方法,然而,我并不想这么做,为什么?,我!就!不!
def ret_loop():
loop = events.new_event_loop()
try:
events.set_event_loop(loop)
# loop.set_debug(debug)
return loop.run_until_complete(get_browser())
# except Exception as e:
finally:
try:
_cancel_all_tasks(loop)
loop.run_until_complete(loop.shutdown_asyncgens())
finally:
events.set_event_loop(None)
loop.close()
# print(e)
# print('adfafdadsf')
if __name__=='__main__':
# asyncio.run(get_browser())
# asyncio.get_event_loop().run_until_complete(get_browser())
ret_loop()