簡體   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

這是我到目前為止所擁有的。 它要求用戶說出藝術家的名字,然后搜索該藝術家在 genius 上的歌曲列表。 然而,這需要很多時間。 我希望它能夠隨機挑選一首歌曲,然后獲取歌詞,然后發送一小段歌詞。

我已決定將我的評論移至答案。 我已經安裝了 package 並稍微玩了一下,您在這里面臨的問題是search_artist的實現:確實,它獲取了我們想要的完整歌曲數據。 search_songs是一個更合適的選擇,另一種選擇是重新實現search_artist以便它確實只返回一個結果。 我將只關注獲取歌詞的邏輯,而不會將其包裝到 Discord API 中。

為我調查的起點是公共Genius API 您不能指望以某種方式規避這一點,您正在使用的庫只是它的一個包裝器(參見例如這個定義)。 沒有現成的 API 可以根據請求為給定藝術家獲取隨機歌曲,對於您的任務,必須至少輪詢兩個 API 端點。 進一步來說...

首先你做GET /search藝術家查詢。 你不能省略這一步——一開始,你有一個藝術家名字,但沒有藝術家 ID。 這將返回類似於下面的 JSON 結果(我正在使用Try it!來自上面鏈接的 Genius API 頁面的示例)。

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

另一種方法是調用search_artists注意末尾的s )。 它可能變得相當丑陋,因為相關信息嵌套在 JSON 中。例如,

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

返回562 ,這是 Led Zeppelin 的藝術家 ID。 這條線看起來很糟糕,但lyricsgenius 的實現與它非常相似,盡管顯然有更多的預防措施。 至少,將上面的塊包裝在try... catch中是有意義的。

如果不需要從搜索返回的頂級藝術家的所有歌曲中隨機挑選,更直接的方法是只使用一個裸露的、非藝術家特定的搜索:

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

這將導致其他藝術家的一些條目(例如在上述情況下的Lo & Leduc )。

此時,我們有藝術家 ID 和其他信息,但還沒有歌曲。 這就是它變得丑陋的地方。 實際上,它已經做到了——Genius API 實際上並沒有在一個查詢中返回所有歌曲 ID,而是使用了分頁。 從設計的角度來看這是明智的,但在您的用例中變通是噩夢般的。 上面的調用返回 10 個條目(默認),通過使用 per_page,每個per_page調用最多可以獲得 50 個條目,並且通過提供頁面偏移量,您可以瀏覽單首歌曲。 因此,您可能事先不知道會有多少首歌曲。 問題的另一部分似乎是 Genius 是 SSR-heavy,並且在一次請求大量歌曲時需要很長時間。 這對於數據科學項目來說很好,但對於 discord 機器人來說就不那么好了。

我可能是盲人,但我找不到 function 或 API 中每個藝術家的歌曲總數的字段。 后端可能在內部使用SELECT... FROM... LIMIT offset, count等。 有趣的是,我還發現這種二進制搜索給定藝術家的歌曲數量可能比一次請求大量歌曲更快或大致相同 - 想想

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

對比

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

上面調用返回的響應將設置一個['next_page']字段(如果存在),並且您還沒有到達歌曲列表的末尾。 同樣,這不是您可以規避的事情。 現在,一個有趣的問題是優化這種隨機搜索以獲得最佳的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

當然,在您的應用程序中集成此類不確定循環時要小心。 例如,如果藝術家有超過max_page個條目,它根本不會終止 對於artist_id = 562 ,它給出了 275 頁的正確答案。 上面的代碼運行速度與使用per_page = 50max_page = 20一樣快。 您可以更多地使用這些參數,如果您依賴每頁的多個結果,那么邏輯會變得更加復雜,因為您需要對它們進行計數。

確定歌曲數量后,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)

對於由 Fair Dice Roll™ 確定的random_id = 10 ,您會得到這個漂亮的 output:

"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\n你可能也喜歡[Bridge 2]\n啊啊啊啊啊啊\n啊啊啊啊啊啊\n哦啊啊\n\n[Outro]\n去\n我去去芝加哥\n去芝加哥\n對不起,但我不能帶你去,啊\n下去,現在下去\n下去,我現在下去\n下去,下去\n下去,下去\n哦。 ..\n下降,現在下降\n下降,現在下降\n下降,現在下降\n下降,下降,現在下降,下降,下降,現在\n哦,哦34嵌入“

一句警告:API 根本不適合您的問題。 我相信沒有辦法真正快速地做你想做的事,可能將自己限制在前幾名(20 名?也許 50 名?)結果之一,並且,可選地,不使用額外的藝術家 ID 查詢將是 go 的方式用例。 否則,您將不得不讓用戶等待,並可能通過讓等待變得更有趣來應對。 我在測試時也遇到過幾次請求超時,請記住這一點,這樣您的機器人就不會徹底崩潰。

完整運行以上代碼(我對其進行了限制,以免對 API 施加太多影響):

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

至少,這已經為搜索返回的頂級藝術家生成了一首真正隨機歌曲的歌詞(好吧,只要他們的歌曲少於max_page ...)。

如果前 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 塊會進一步減慢你的速度(在我的測試中很容易超過半秒)。 請記住,一首隨機歌曲可能根本沒有歌詞。

循環遍歷所有歌曲並獲取它們的原始實現仍然非常慢。 即使只有 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)

進一步的改進方法可能包括本地緩存,如 rabbibillclinton 建議的那樣,或者通過查找每個藝術家的歌曲數量來變得聰明。 但這就是它的要點; 它不會跑得很快,但它可以跑得很好

這段代碼會找到你想要的歌詞,但它也會緩存藝術家的歌詞,這樣你就不必運行它兩次,以防你之前已經檢索到一位藝術家。

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

這段代碼沒有等待包含藝術家的另一條消息,而是將其作為另一個參數接收,例如(假設您的前綴類似於“:”): !lyrics The Beatles

然后,該代碼將在您目錄中的現有文件中搜索名為“The Beatles lyrics.json”的 JSON 文件。 如果找到 JSON 文件,代碼將只從那里獲取歌詞。 由於您沒有從 Genius API 檢索任何內容,因此代碼運行速度會快得多。 如果用戶輸入的藝術家名稱是新的,則不會存在 JSON 文件。 如果不存在文件,代碼將提前 go 並使用您的 API 檢索它,這將花費更長的時間,但它只需要執行一次。

上面的代碼運行后,您可以自由地使用lyrics變量做任何您想做的事情。 希望它運行得更快。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM