Seleniumを非同期で使い、ページ収集したかった。しかし、収集前に処理が終了してしまうため、困っていた。
原因は.run_in_executor
であった。.run_in_executor
は同期関数を非同期化できる関数である。
しかし、loop.run_until_completeと併用でコード完了までプログラムは実行できるかもしれないが、
loop.run_until_completeの次の行へ処理が移るので、非同期処理群を完了してから、次の処理に移りたい時には使えない。最初どこに原因があるか分からず、asyncio.wait
併用で止めようと試みた。知らなかったのでMEMO.
.run_in_executor による非同期化 |
素直な非同期化関数 | |
---|---|---|
.run_until_complete で非同期可処理群の終了までプログラム終了を待つか? |
YES? | YES |
asyncio.wait と併用で、非同期可処理群の終了までコード停止。次の行へ処理進まないか? |
NO | YES |
- 問題のコード
"""
参考元: https://stackoverflow.com/questions/50303797/python-webdriver-and-asyncio
こちらのコードは、loop.run_until_completeがあっても、取得未完了でも、次の行のコードに到達してしまう。
"""
import asyncio
from concurrent.futures.thread import ThreadPoolExecutor
from selenium import webdriver
from loguru import logger
from selenium.webdriver.chrome import service
executor = ThreadPoolExecutor(10)
def scrape(url, *, loop):
logger.debug(f"scrape func in. {url=}")
# 注意) .run_in_executorは同期関数を非同期化できる関数である。
# しかし、loop.run_until_completeと併用でコード完了までプログラムは実行できるかもしれないが、
# loop.run_until_completeの次の行へ処理が移るので、非同期処理群を完了してから、次の処理に移りたい時は使えない
loop.run_in_executor(executor, scraper, url)
def scraper(url):
logger.debug(f"scraper func in. {url=}")
chromedriver = '/opt/homebrew/bin/chromedriver'
chrome_service = service.Service(executable_path=chromedriver)
driver = webdriver.Chrome(service=chrome_service)
driver.get(url)
print(driver.page_source)
print("END of scraper func")
return driver.page_source
loop = asyncio.get_event_loop()
for url in ["https://www.google.com/"] * 10:
scrape(url, loop=loop)
res = loop.run_until_complete(asyncio.gather(*asyncio.all_tasks(loop)))
asyncio.wait(res, return_when=asyncio.ALL_COMPLETED) # 非同期関数処理が終了前にこちらの行へ移る。
loop.close()
print(f".run_until_completeを抜けた!:{res=}")
解決策は.run_in_executor
を使わず、素直に非同期関数として実装する。
- 修正後のコード
"""
https://stackoverflow.com/questions/50303797/python-webdriver-and-asyncio
こちらのコードは、`loop.run_until_complete`で次の行のコードに到達せず待つ。
"""
import asyncio
from concurrent.futures.thread import ThreadPoolExecutor
from selenium import webdriver
from loguru import logger
from selenium.webdriver.chrome import service
executor = ThreadPoolExecutor(10)
async def scrape(url, *, loop): # 非同期関数化した。
logger.debug("scrape func in")
await loop.run_in_executor(executor, scraper, url)
def scraper(url):
logger.debug(f"scraper func in. {url=}")
chromedriver = '/opt/homebrew/bin/chromedriver'
chrome_service = service.Service(executable_path=chromedriver)
driver = webdriver.Chrome(service=chrome_service)
driver.get(url)
print("scraper func3")
print(driver.page_source)
print("END of scraper func")
return driver.page_source
loop = asyncio.get_event_loop()
for url in ["https://www.google.com/"] * 10:
scrape(url, loop=loop)
res = loop.run_until_complete(asyncio.gather(*[scrape(url, loop=loop) for url in ["https://www.google.com/"] * 10]))
print(f".run_until_completeを抜けた!") # gatherで非同期処理すべて終わってからこちらのコードへ移る
print(res)