#python #time #discord #discord.py #code-structure
#python #время #Discord #discord.py #структура кода
Вопрос:
В настоящее время я разрабатываю discord-бота, который очищает веб-страницу, которая постоянно обновляется для исправлений, связанных с сервером PBE. У меня бот успешно работает через Heroku прямо сейчас. Проблема, с которой я сталкиваюсь, заключается в том, что я хочу создать автоматическое (циклическое) обновление, которое перезагрузит запрошенный мной веб-сайт. В текущем виде он загружает только один экземпляр веб-сайта, и если этот веб-сайт изменяется / обновляется, ни один из моих материалов не будет обновляться, поскольку я использую «старый» запрос веб-сайта.
Есть ли у меня способ спрятать код внутри функции, чтобы я мог создать временной цикл или мне нужно создать его только по запросу моего веб-сайта? Как это будет выглядеть? Спасибо!
from bs4 import BeautifulSoup
from urllib.request import urlopen
from discord.ext import commands
import discord
# what I want the commands to start with
bot = commands.Bot(command_prefix='!')
# instantiating discord client
token = "************************************"
client = discord.Client()
# begin the scraping of passed in web page
URL = "*********************************"
page = urlopen(URL)
soup = BeautifulSoup(page, 'html.parser')
pbe_titles = soup.find_all('h1', attrs={'class': 'news-title'}) # using soup to find all header tags with the news-title
# class and storing them in pbe_titles
linksAndTitles = []
counter = 0
# finding tags that start with 'a' as in a href and appending those titles/links
for tag in pbe_titles:
for anchor in tag.find_all('a'):
linksAndTitles.append(tag.text.strip())
linksAndTitles.append(anchor['href'])
# counts number of lines stored inside linksAndTitles list
for i in linksAndTitles:
counter = counter 1
print(counter)
# separates list by line so that it looks nice when printing
allPatches = 'n'.join(str(line) for line in linksAndTitles[:counter])
# stores the first two lines in list which is the current pbe patch title and link
currPatch = 'n'.join(str(line) for line in linksAndTitles[:2])
# command that allows user to type in exactly what patch they want to see information for based off date
@bot.command(name='patch')
async def pbe_patch(ctx, *, arg):
if any(item.startswith(arg) for item in linksAndTitles):
await ctx.send(arg " exists!")
else:
await ctx.send('The date you entered: ' '"' arg '"' ' does not have a patch associated with it or that patch expired.')
# command that displays the current, most up to date, patch
@bot.command(name='current')
async def current_patch(ctx):
response = currPatch
await ctx.send(response)
bot.run(token)
Я поиграл с
while True:
циклы, но всякий раз, когда я что-то вкладываю в них, я не могу получить доступ к коду в других местах.
Комментарии:
1. использование
while True:
этого совершенно неправильная идея.discord
имеет объектtask
, который вы можете запускать с помощью таймера, чтобы повторять задачу каждые несколько минут. В конце концов, я бы использовал системную службуcron
для запуска периодически разделяемого скрипта, который получает данные со страницы и сохраняет в локальном файле иdiscord
должен считывать из этого файла.2. Так что это было бы идеальным соглашением для создания этого бота, да? Чтение из файла?
3. Кстати: если вы создали
bot
—commands.Bot(...)
— тогда вам не нужноclient
—discord.Client()
.bot
это специальный типclient
4. короче
counter = len(linksAndTitles)
5. вы можете сохранить заголовок и ссылку как один элемент ie. как список
linksAndTitles.append( [tag.text.strip(), linksAndTitles.append(anchor['href'])] )
или словарьlinksAndTitles.append( {"title": tag.text.strip(), "link": linksAndTitles.append(anchor['href'])} )
Ответ №1:
discord
имеет специальный декоратор tasks
для периодического запуска некоторого кода
from discord.ext import tasks
@tasks.loop(seconds=5.0)
async def scrape():
# ... your scraping code ...
# ... your commands ...
scrape.start()
bot.run(token)
и он будет повторять функцию scrape
каждые 5 секунд.
Документация: задачи
В Linux в конечном итоге я бы использовал стандартный сервис cron
для периодического запуска какого-либо скрипта. Этот скрипт может очищать данные и сохранять их в файле или базе данных, а discord
также может считывать из этого файла или базы данных. Но cron
проверяйте задачи каждые 1 минуту, чтобы он не мог запускать задачу чаще.
Редактировать:
Минимальный рабочий код.
Я использую страницу http://books.toscrape.com создан для обучения scrape.
Я изменил несколько элементов. Нет необходимости создавать client
, когда есть bot
, потому bot
что это особый вид client
Я сохраняю title
и link
как словарь
{
'title': tag.text.strip(),
'link': url anchor['href'],
}
поэтому позже легче создавать текст, например
title: A Light in the ...
link: http://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html
import os
import discord
from discord.ext import commands, tasks
from bs4 import BeautifulSoup
from urllib.request import urlopen
# default value at start (before `scrape` will assign new value)
# because some function may try to use these variables before `scrape` will create them
links_and_titles = [] # PEP8: `lower_case_namese`
counter = 0
items = []
bot = commands.Bot(command_prefix='!')
@tasks.loop(seconds=5)
async def scrape():
global links_and_titles
global counter
global items
url = "http://books.toscrape.com/"
page = urlopen(url)
soup = BeautifulSoup(page, 'html.parser')
#pbe_titles = soup.find_all('h1', attrs={'class': 'news-title'})
pbe_titles = soup.find_all('h3')
# remove previous content
links_and_titles = []
for tag in pbe_titles:
for anchor in tag.find_all('a'):
links_and_titles.append({
'title': tag.text.strip(),
'link': url anchor['href'],
})
counter = len(links_and_titles)
print('counter:', counter)
items = [f"title: {x['title']}nlink: {x['link']}" for x in links_and_titles]
@bot.command(name='patch')
async def pbe_patch(ctx, *, arg=None):
if arg is None:
await ctx.send('Use: !patch date')
elif any(item['title'].startswith(arg) for item in links_and_titles):
await ctx.send(arg " exists!")
else:
await ctx.send(f'The date you entered: "{arg}" does not have a patch associated with it or that patch expired.')
@bot.command(name='current')
async def current_patch(ctx, *, number=1):
if items:
responses = items[:number]
text = 'n----n'.join(responses)
await ctx.send(text)
else:
await ctx.send('no patches')
scrape.start()
token = os.getenv('DISCORD_TOKEN')
bot.run(token)
Комментарии:
1. Итак, если мой код очистки находится внутри функции, могу ли я просто вызвать функцию после scrape()?
2. если код будет внутри
scrape()
, он@tasks.loop(seconds=5.0)
будет запускаться автоматически каждые 5 секунд. Но вы все равно можете запустить его вручную, используяscrape()
. ie. вы можете запустить его при запуске, чтобы получить первое значение.3. Итак, я создал вызываемую функцию
scrape_and_store
, которая содержит весь приведенный выше код, который находит URL-адрес, анализирует его с помощью BS, затем сохраняет эту информацию в списке и возвращает список. Затем, когда я помещаю эту функцию в цикл, она все равно не обновляется. Бот работает нормально, ошибок нет — похоже, он просто не постоянно перетаскивает веб-страницу снова и снова. (Я тестирую его на своем собственном html и обновляю html, чтобы увидеть, вызывают ли команды какие-либо изменения)4. вы не вводите свой цикл,
@tasks.loop(seconds=5.0)
и он должен запускать его автоматически каждые 5 секунд. И использованиеreturn
бесполезно — потому что, когдаtask.loop
он будет запущен, теперь не будет, что делать с возвращаемыми значениями. Вы должны присвоить результатыglobal
переменным.5. Я забыл добавить
scrape.start()
. Теперь это в коде. Вы.start()
также можете посмотреть в документации.