So I’m creating a discord bot that is constantly scraping a webpage, checking if the item is in stock. When the item becomes in stock, send a message in the chat. Since the scraping is in a constant loop, I figured I would put that in a thread, one for each page that needs to be scraped. The problem is that each thread is producing the same error,
RuntimeError: Timeout context manager should be used inside a task
Does anyone know why, or a better approach to what I am trying to do?
async def searchPage(input_url): currentUrl = input_url driver = webdriver.Chrome(options=options, executable_path=DRIVER_PATH) driver.get(currentUrl) while True: try: shipIt = driver.find_element_by_css_selector('[data-test="shippingBlock"]') await alertUsers(currentUrl) await asyncio.sleep(5) except NoSuchElementException: continue #driver.quit() async def alertUsers(current_input): channel = client.get_channel(795513308869296181) await channel.send(f'Go buy this: {current_input}') async def create_threads(): thread1 = threading.Thread(target=asyncio.run, args=(searchPage("https://www.target.com/p/set-of-2-kid-century-modern-kids-chairs-b-spaces/-/A-81803789?preselect=80929605#lnk=sametab"),)) thread1.start() thread2 = threading.Thread(target=asyncio.run, args=(searchPage("https://www.target.com/p/wrangler-men-s-big-tall-relaxed-fit-jeans-with-flex/-/A-79321830?preselect=52428349#lnk=sametab"),)) thread2.start() @client.event async def on_ready(): guild = discord.utils.get(client.guilds, name=GUILD) print( f'{client.user} is connected to the following guild:n' f'{guild.name}(id: {guild.id})' ) await create_threads() client.run(TOKEN)
Advertisement
Answer
Some frameworks don’t like to run in threads
– i.e. all GUI frameworks has to run windows and widgets in main thread. They are called not thread-safe
.
And you may have similar problem – inside thread
you can’t use async
created in main thread.
You should run searchPage
in new thread as normal function and send results to main thread which should send some message to discord. You can use global variable or better queue
to send results. And main thread should run some function which periodically check queue
and send messages to discord.
Discord has @tasks.loop(second=...)
which can run periodically function.
Minimal working code
import discord from discord.ext import tasks import os import time import queue import threading from selenium import webdriver MY_CHANNEL = 795513308869296181 # queue to communicate with threads queue = queue.Queue() client = discord.Client() # --- normal functions --- def searchPage(url, queue): driver = webdriver.Chrome() #driver = webdriver.Chrome(options=options, executable_path=DRIVER_PATH) while True: try: driver.get(url) ship_it = driver.find_element_by_css_selector('[data-test="shippingBlock"]') #print('[DEBUG] queue put:', url) queue.put(url) time.sleep(5) except NoSuchElementException as ex: #print('[DEBUG] queue put: not found') #queue.put('not found') #print('Exception:', ex) pass #driver.quit() def create_threads(): urls = [ "https://www.target.com/p/set-of-2-kid-century-modern-kids-chairs-b-spaces/-/A-81803789?preselect=80929605#lnk=sametab", "https://www.target.com/p/wrangler-men-s-big-tall-relaxed-fit-jeans-with-flex/-/A-79321830?preselect=52428349#lnk=sametab", ] for url in urls: t = threading.Thread(target=searchPage, args=(url, queue)) t.start() # --- async functions --- @tasks.loop(seconds=5) async def alert_users(): #print('[DEBUG] alert_users') if not queue.empty(): current_input = queue.get() channel = client.get_channel(MY_CHANNEL) await channel.send(f'Go buy this: {current_input}') @client.event async def on_ready(): create_threads() alert_users.start() # start `task.loop` TOKEN = os.getenv('DISCORD_TOKEN') client.run(TOKEN)