简体   繁体   English

如何从天才那里随机挑选一首歌曲并将一小部分歌词发送到 discord

[英]How to pick a random song from genius and send a small section of those lyrics to discord

@client.command()
async def lyrics(ctx):
    genius = lyricsgenius.Genius(api_key)
    message_ID = ctx.message.author.id
    await ctx.send("**name the song author:**")
    @client.listen("on_message")
    async def on_message(message):
        if message.author.id == int(message_ID):
            artist_name = message.content
            artist = genius.search_artist(artist_name)
            print(artist)
        else:
            return

This is what I have so far.这是我到目前为止所拥有的。 It asks the user to say an artist's name, and it searches through the list of songs that the artist has on genius.它要求用户说出艺术家的名字,然后搜索该艺术家在 genius 上的歌曲列表。 However, this takes a lot of time.然而,这需要很多时间。 I want it to be able to randomly pick a song, then get the lyrics, and then send a small section of the lyrics.我希望它能够随机挑选一首歌曲,然后获取歌词,然后发送一小段歌词。

I have decided to move my comments to an answer.我已决定将我的评论移至答案。 I have installed the package and played with it a little bit, the problem you are facing here is the implementation of search_artist : indeed, it fetches full song data which is not what we want.我已经安装了 package 并稍微玩了一下,您在这里面临的问题是search_artist的实现:确实,它获取了我们想要的完整歌曲数据。 search_songs is a more appropriate choice, and another alternative is reimplementing search_artist so that it would only return one result indeed. search_songs是一个更合适的选择,另一种选择是重新实现search_artist以便它确实只返回一个结果。 I will focus on the fetching lyrics logic only, without wrapping it into the Discord API.我将只关注获取歌词的逻辑,而不会将其包装到 Discord API 中。

The starting place to investigate for me was the public Genius API .为我调查的起点是公共Genius API You can not expect to somehow circumvent that, the library you are using is just a wrapper around it (see eg this definition ).您不能指望以某种方式规避这一点,您正在使用的库只是它的一个包装器(参见例如这个定义)。 There is no ready-made API for fetching a random song for a given artist on request, and for your task, one has to poll at least two API endpoints.没有现成的 API 可以根据请求为给定艺术家获取随机歌曲,对于您的任务,必须至少轮询两个 API 端点。 More specifically...进一步来说...

First you do GET /search for the artist query.首先你做GET /search艺术家查询。 You can not omit this step - at the beginning, you have an artist name, but not the artist ID.你不能省略这一步——一开始,你有一个艺术家名字,但没有艺术家 ID。 That will return a JSON result similar to the one below (I am using Try it! examples from the Genius API page linked above).这将返回类似于下面的 JSON 结果(我正在使用Try it!来自上面链接的 Genius API 页面的示例)。

hits: [ { highlights: [...],
index: "song",
type: "song",
result: {
annotation_count: 20,
api_path: "/songs/3039923", (...)

An alternative to that is calling search_artists ( note the s at the end! ).另一种方法是调用search_artists注意末尾的s )。 It can get fairly ugly, as the relevant information is nested deeply into the JSON. For example,它可能变得相当丑陋,因为相关信息嵌套在 JSON 中。例如,

genius.search_artists("Led")['sections'][0]['hits'][0]['result']['id']

returns 562 , which is the artist ID for Led Zeppelin.返回562 ,这是 Led Zeppelin 的艺术家 ID。 This line looks bad, but the lyricsgenius implementation is fairly similar to it anyway, albeit obviously has more precautions.这条线看起来很糟糕,但lyricsgenius 的实现与它非常相似,尽管显然有更多的预防措施。 At a bare minimum, it would make sense to wrap the block above in try... catch .至少,将上面的块包装在try... catch中是有意义的。

If picking randomly from all songs by the top artist returned by the search is not a requirement, a more straightforward way would be to just use a bare, non-artist-specific search:如果不需要从搜索返回的顶级艺术家的所有歌曲中随机挑选,更直接的方法是只使用一个裸露的、非艺术家特定的搜索:

songs = genius.search_songs("Led")['hits']

This would lead to some entries by other artists (eg Lo & Leduc , in the above case).这将导致其他艺术家的一些条目(例如在上述情况下的Lo & Leduc )。

At this point, we have the artist ID and other info, but no songs yet.此时,我们有艺术家 ID 和其他信息,但还没有歌曲。 And this is where it gets ugly.这就是它变得丑陋的地方。 Actually, it already did - Genius API does not, in fact, return all the song IDs in one query, and uses pagination instead.实际上,它已经做到了——Genius API 实际上并没有在一个查询中返回所有歌曲 ID,而是使用了分页。 It is sensible from a design perspective, but is nightmarish to work around in your use case.从设计的角度来看这是明智的,但在您的用例中变通是噩梦般的。 The call above returns 10 entries (default), by using per_page you can get up to 50 per API call, and by providing a page offset, you could browse individual songs.上面的调用返回 10 个条目(默认),通过使用 per_page,每个per_page调用最多可以获得 50 个条目,并且通过提供页面偏移量,您可以浏览单首歌曲。 Thus, you may not know in advance how many songs there will be.因此,您可能事先不知道会有多少首歌曲。 Another part of the problem seems to be that Genius is SSR-heavy, and takes a long time when lots of songs are being requested at once.问题的另一部分似乎是 Genius 是 SSR-heavy,并且在一次请求大量歌曲时需要很长时间。 This is fine for data scienc-y projects, but not so much for a discord bot.这对于数据科学项目来说很好,但对于 discord 机器人来说就不那么好了。

I might be blind, but I could not find a function or field in the API for the total number of songs per artist.我可能是盲人,但我找不到 function 或 API 中每个艺术家的歌曲总数的字段。 Likely, the backend uses SELECT... FROM... LIMIT offset, count or some such internally.后端可能在内部使用SELECT... FROM... LIMIT offset, count等。 Interestingly, I also have found that kind of binary searching the number of songs for a given artist might be faster or about the same as requesting a large amount of songs at once - think有趣的是,我还发现这种二进制搜索给定艺术家的歌曲数量可能比一次请求大量歌曲更快或大致相同 - 想想

genius.artist_songs(562, per_page=1, page=250)

vs对比

genius.artist_songs(562, per_page=50, page=5)

Responses returned by calls above will have a ['next_page'] field set if it exists, and you are not at the end of the song list just yet.上面调用返回的响应将设置一个['next_page']字段(如果存在),并且您还没有到达歌曲列表的末尾。 Again, this is not something you can circumvent.同样,这不是您可以规避的事情。 Now, an interesting problem would be optimizing this random search for optimal per_page counts and, possibly, forgoing the last few requests if the search step becomes too small, but an okay starting point would be something like this:现在,一个有趣的问题是优化这种随机搜索以获得最佳的per_page计数,并且如果搜索步骤变得太小,可能会放弃最后几个请求,但是一个好的起点是这样的:

max_page = 1000
page = max_page
next_page = 1
last_known_good_page = 0
last_known_bad_page = max_page
while True:
    next_page = genius.artist_songs(artist_id, per_page=1, page=page)['next_page']
    if next_page:
        last_known_good_page = next_page
        page = int((last_known_bad_page + page) / 2)
    else:
        last_known_bad_page = page
        page = int((last_known_good_page + page) / 2)
    if page - last_known_good_page < 1:
        break

Of course, be careful when integrating such indeterminate loops in your app.当然,在您的应用程序中集成此类不确定循环时要小心。 For example, if the artist has got more than max_page entries, it would not terminate at all .例如,如果艺术家有超过max_page个条目,它根本不会终止 For artist_id = 562 it gives a correct answer of 275 pages.对于artist_id = 562 ,它给出了 275 页的正确答案。 The code above runs about as fast as using per_page = 50 and max_page = 20 .上面的代码运行速度与使用per_page = 50max_page = 20一样快。 You could toy with these parameters a bit more, the logic is going to get a touch more convoluted if you are relying on multiple results per page, as you would then need to count them.您可以更多地使用这些参数,如果您依赖每页的多个结果,那么逻辑会变得更加复杂,因为您需要对它们进行计数。

After you have determined a number of songs, the rest is straightforward:确定歌曲数量后,rest 就很简单了:

random_id = genius.artist_songs(artist_id, per_page=1, page=random.randint(0, page), sort='popularity')['songs'][0]['id']
lyrics = genius.lyrics(random_id)

For random_id = 10 , determined by a Fair Dice Roll™, you get this nice output:对于由 Fair Dice Roll™ 确定的random_id = 10 ,您会得到这个漂亮的 output:

"When the Levee Breaks Lyrics[Intro]\n\n[Verse 1]\nIf it keeps on raining, levee's going to break\nIf it keeps on raining, the levee's going to break\n\n[Chorus 1]\nWhen the levee breaks, have no place to stay\n\n[Verse 2]\nMean old levee taught me to weep and moan, oh\nMean old levee taught me to weep and moan\n[Chorus 2]\nIt's got what it takes to make a mountain man leave his home\nOh well, oh well, oh well, ooh\n\n[Bridge 1]\nOh, don't it make you feel bad\nWhen you're trying to find your way home\nYou don't know which way to go\nIf you're going down south, they got no work to do\nIf you're going north to Chicago\nAh, ah, ah, hey\n\n[Instrumental Break]\n\n[Verse 3]\nCrying won't help you, praying won't do you no good\nNo, crying won't help you, praying won't do you no good\n\n[Chorus 3]\nWhen the levee breaks, mama, you got to move, ooh\n\n[Verse 4]\nAll last night I sat on the levee and moaned\nAll last night, sat on the levee and moaned\nThinking about my baby and my happy home\ "When the Levee Breaks 歌词[Intro]\n\n[Verse 1]\nIf it keeps on raing, levee's going to break\nIf it keeps on raining, the levee's going to break\n\n[Chorus 1]\nWhen堤坝决堤,无处停留\n\n[第 2 节]\n卑鄙的旧堤坝教我哭泣和呻吟,哦\n卑鄙的旧堤坝教我哭泣和呻吟\n[合唱 2]\n它就是这样需要让一个山民离开他的家\n哦好吧,哦好吧,哦好吧,哦哦\n\n[桥 1]\n哦,这不会让你感到难过\n当你试图找到回家的路时\n你不知道该走哪条路\n如果你往南走,他们就没有工作要做\n如果你往北去芝加哥\n啊,啊,啊,嘿\n\n[Instrumental Break] \n\n[第 3 节]\n哭泣对你没有帮助,祈祷对你没有好处\n不,哭泣对你没有帮助,祈祷对你没有好处\n\n[合唱 3] \n当堤坝破裂时,妈妈,你必须移动,哦\n\n[第 4 节]\n昨晚我坐在堤坝上呻吟\n昨晚我坐在堤坝上呻吟\n想着我的孩子和我快乐的家\ nOh-ho\nYou might also like[Bridge 2]\nAh, ah, ah, ah-ah\nAh, ah, ah, ah-ah\nOh, oh\n\n[Outro]\nGoing\nI'm going to Chicago\nGoing to Chicago\nSorry, but I can't take you, ah\nGoing down, going down now\nGoing down, I'm going down now\nGoing down, going down\nGoing down, going down\nOh...\nGoing down, going down now\nGoing down, going down now\nGoing down, going down now\nGoing down, going, dow- dow- dow- dow- down, now\nOoh, ooh34Embed" nOh-ho\n你可能也喜欢[Bridge 2]\n啊啊啊啊啊啊\n啊啊啊啊啊啊\n哦啊啊\n\n[Outro]\n去\n我去去芝加哥\n去芝加哥\n对不起,但我不能带你去,啊\n下去,现在下去\n下去,我现在下去\n下去,下去\n下去,下去\n哦。 ..\n下降,现在下降\n下降,现在下降\n下降,现在下降\n下降,下降,现在下降,下降,下降,现在\n哦,哦34嵌入“

A word of warning: the API is not well-suited for your problem AT ALL.一句警告:API 根本不适合您的问题。 I believe there is no way to do what you want actually fast, probably limiting yourself to one of the top (20? Maybe 50?) results and, optionally, not using an extra query for artist ID would be the way to go for your use case.我相信没有办法真正快速地做你想做的事,可能将自己限制在前几名(20 名?也许 50 名?)结果之一,并且,可选地,不使用额外的艺术家 ID 查询将是 go 的方式用例。 Otherwise, you will have to have users wait, and possibly cope a little bit by making the wait a little bit more fun.否则,您将不得不让用户等待,并可能通过让等待变得更有趣来应对。 I have also hit request timeouts a couple of times while testing, something to keep in mind so that your bot does not outright break.我在测试时也遇到过几次请求超时,请记住这一点,这样您的机器人就不会彻底崩溃。

A full run of the above code (I have limited it so to not hammer the API too much):完整运行以上代码(我对其进行了限制,以免对 API 施加太多影响):

7.91 s ± 848 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)

At least, this has produced lyrics for a truly random song for a top artist returned by the search (well, as long as they have fewer than max_page songs...).至少,这已经为搜索返回的顶级艺术家生成了一首真正随机歌曲的歌词(好吧,只要他们的歌曲少于max_page ...)。

If one of top-50 songs is good enough, it becomes a lot easier, with much fewer requests and about twice as fast:如果前 50 首歌曲中的一首足够好,它就会变得容易得多,请求更少,速度大约是原来的两倍:

artist_id = genius.search_artists("Led")['sections'][0]['hits'][0]['result']['id']
songs = genius.artist_songs(artist_id, per_page=50, sort='popularity')['songs']
lyrics = genius.lyrics(random.choice(songs)['id'])

3.57 s ± 362 ms per loop (mean ± std. dev. of 3 runs, 1 loop each). 

Try-catch blocks would further slow you down (easily over half a second in my testing). Try-catch 块会进一步减慢你的速度(在我的测试中很容易超过半秒)。 Bear in mind a random song might not have lyrics at all.请记住,一首随机歌曲可能根本没有歌词。

The original implementation looping through all the songs and fetching them is still way, way slower.循环遍历所有歌曲并获取它们的原始实现仍然非常慢。 Even for just 10 songs:即使只有 10 首歌:

genius.search_artist("Led", max_songs=10)
[SNIP]
Done. Found 10 songs.
22.8 s ± 987 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)

Further ways of improvement would likely include local caching like rabbibillclinton suggests or getting smart with finding the number of songs per artist.进一步的改进方法可能包括本地缓存,如 rabbibillclinton 建议的那样,或者通过查找每个艺术家的歌曲数量来变得聪明。 But this is kind of the gist of it;但这就是它的要点; it would not run fast, but it could run a-okay .它不会跑得很快,但它可以跑得很好

This code will find the lyrics like you want, but it also caches artists lyrics so you don't have to run it twice in case you've already retrieved an artist before.这段代码会找到你想要的歌词,但它也会缓存艺术家的歌词,这样你就不必运行它两次,以防你之前已经检索到一位艺术家。

import json

genius = lyricsgenius.Genius(api_key) # Running outside the command so it only runs once (faster).
@client.command()
async def lyrics(ctx, *, artist_name): # Asterisk means it accepts more than one word as an argument.
    try:
        with open(artist_name + ' lyrics', 'r') as f:
            lyrics = json.load(f)
    except:
        try:
            artist = genius.search_songs(artist_name, sort="title")
            artist.save_lyrics(filename=(artist_name +  "lyrics"))
            with open(artist_name + ' lyrics', 'r') as f:
                lyrics = json.load(f)
        except Exception as e:
            await ctx.send("Failed to find artist.")
            print(e)
            return

Instead of waiting for another message containing the artist, this code just takes it in as another argument, example (assuming your prefix is some like ":"): !lyrics The Beatles .这段代码没有等待包含艺术家的另一条消息,而是将其作为另一个参数接收,例如(假设您的前缀类似于“:”): !lyrics The Beatles

The code will then search existing files in your directory for a JSON file called "The Beatles lyrics.json".然后,该代码将在您目录中的现有文件中搜索名为“The Beatles lyrics.json”的 JSON 文件。 If it finds said JSON file, the code will just grab the lyrics from there.如果找到 JSON 文件,代码将只从那里获取歌词。 Since you're not retrieving anything from Genius API, the code will run much faster.由于您没有从 Genius API 检索任何内容,因此代码运行速度会快得多。 If the artist name that the user inputted is new, no JSON file will exist for it.如果用户输入的艺术家名称是新的,则不会存在 JSON 文件。 If no file exists, the code will go ahead and retrieve it using your API, this will take much longer, but it only ever has to do this once.如果不存在文件,代码将提前 go 并使用您的 API 检索它,这将花费更长的时间,但它只需要执行一次。

Once the code above runs, your free to do anything you want with the lyrics variable.上面的代码运行后,您可以自由地使用lyrics变量做任何您想做的事情。 Hopefully it runs faster.希望它运行得更快。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 用beautifulsoup │python 3.8 从天才歌词中获取歌曲歌词 - Getting lyrics of song from genius lyrics with beautifulsoup │python 3.8 Python:从天才那里获取歌曲歌词 - 错误 - Python: Get songs Lyrics from genius - Error 如何让我的 Discord.py 机器人从与我的主代码相同的文件夹中的 .txt 文件中随机选择一条“行”? - How do I make my Discord.py bot pick a random "line" from a .txt file in the same folder as my main code? 如何获取频道中所有消息的列表,然后从该列表中随机选择一条消息作为消息发送? - How can I get a list of all the messages in a channel and then pick a random message from that list to send as a message? 关于Rap Genius w / Python的Web Scraping Rap歌词 - Web Scraping Rap lyrics on Rap Genius w/ Python 如何从文件夹中随机选择照片 - How to pick random photo from folder discord.py 随机成员选择 - discord.py random member pick 在 Python 中编程 Discord 机器人 - 如何让机器人从 subreddit 发送随机文本组? - Programming a Discord bot in Python- How do I make the bot send a random group of text from a subreddit? 如何从字典内的随机列表中选择随机项目 - How to pick random item from random list inside a dictionary 在 python 中编程 Discord 机器人 - 如何让机器人从一组图像中发送随机图像? - Programming a Discord bot in python- How do make the bot send a random image from a group of images?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM