Создание временного цикла внутри скрипта Discord Bot для перезагрузки веб-страницы (web scraper bot)

#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)
 

PEP 8 — Руководство по стилю для кода Python

Комментарии:

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() также можете посмотреть в документации.