ISRC / app.py
Denny1911's picture
Update app.py
9ad86ec verified
Raw
History Blame Contribute Delete
9.6 kB
import gradio as gr
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import Select
from selenium.webdriver.chrome.options import Options
from bs4 import BeautifulSoup
import pandas as pd
import time
import re
import os
def parse_credits(credits_str):
"""精準拆解國圖的作品職者字串"""
c_str = re.sub(r'\s+', '', credits_str)
singer_match = re.search(r'表演者:?([^作編混]*?)(?:作詞|作曲|編曲|$)', c_str)
lyricist_match = re.search(r'作詞者:?([^作表編混]*?)(?:作曲|編曲|表演|$)', c_str)
composer_match = re.search(r'作曲者:?([^作表編混]*?)(?:作詞|編曲|表演|$)', c_str)
singer = singer_match.group(1).strip() if singer_match else ""
lyricist = lyricist_match.group(1).strip() if lyricist_match else ""
composer = composer_match.group(1).strip() if composer_match else ""
return singer.replace(':', '').replace(':', ''), lyricist.replace(':', '').replace(':', ''), composer.replace(':', '').replace(':', '')
def process_single_song(driver, song_name, artist_name):
"""處理單一歌曲的爬取核心邏輯"""
try:
url = "https://isrc-web.ncl.edu.tw/C200/C200"
driver.get(url)
# 1. 切換分頁
song_tab = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.ID, "profile-tab")))
song_tab.click()
time.sleep(0.5)
advanced_tab = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.ID, "v-pills-settings-tab")))
advanced_tab.click()
time.sleep(1.2)
# 2. 輸入歌名
field1_element = WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "songField1")))
Select(field1_element).select_by_value("NAME")
if song_name:
keyword1_box = driver.find_element(By.ID, "songKeyword1")
keyword1_box.clear()
keyword1_box.send_keys(str(song_name).strip())
# 3. 輸入歌手
field2_element = WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "songField2")))
Select(field2_element).select_by_value("ACTION")
if artist_name and not pd.isna(artist_name):
keyword2_box = driver.find_element(By.ID, "songKeyword2")
keyword2_box.clear()
keyword2_box.send_keys(str(artist_name).strip())
# 4. 送出與等待渲染
search_button = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.XPATH, '//button[@data-action="C20004"]')))
search_button.click()
time.sleep(3.5)
# 5. 解析
soup = BeautifulSoup(driver.page_source, 'html.parser')
result_table = soup.find('table', id='dataTable2')
data_list = []
if result_table and result_table.find('tbody'):
rows = result_table.find('tbody').find_all('tr')
for row in rows:
cols = row.find_all('td')
if len(cols) >= 8:
raw_credits = cols[4].text.strip()
release_date = cols[7].text.strip()
extracted_singer, lyricist, composer = parse_credits(raw_credits)
data_list.append({
"主要歌手": extracted_singer, "作詞者": lyricist, "作曲者": composer, "發行日期": release_date
})
if data_list:
df = pd.DataFrame(data_list)
df['datetime_parsed'] = pd.to_datetime(df['發行日期'], errors='coerce')
df = df.sort_values(by='datetime_parsed', ascending=True).reset_index(drop=True)
earliest_release_date = df.iloc[0]['發行日期'] if not df.empty else "未記載"
final_lyricist, final_composer = "", ""
target_artist = str(artist_name).strip() if (artist_name and not pd.isna(artist_name)) else ""
df_artist_match = df[df['主要歌手'] == target_artist].reset_index(drop=True) if target_artist else pd.DataFrame()
loop_df = df_artist_match if not df_artist_match.empty else df
for _, row in loop_df.iterrows():
if not final_lyricist and row['作詞者']: final_lyricist = row['作詞者']
if not final_composer and row['作曲者']: final_composer = row['作曲者']
if final_lyricist and final_composer: break
return final_lyricist if final_lyricist else "未記載", final_composer if final_composer else "未記載", earliest_release_date
else:
return "查無資料", "查無資料", "查無資料"
except Exception as e:
print(f"處理歌曲 {song_name} 時發生問題: {str(e)}")
return "錯誤", "錯誤", "錯誤"
def create_driver():
"""建立並初始化 Chrome 瀏覽器的捷徑函式"""
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
return webdriver.Chrome(options=chrome_options)
# 🎯 關鍵改動:加入 progress=gr.Progress() 參數來啟用進度條
def batch_process_file(file_obj, progress=gr.Progress()):
if file_obj is None:
return None, "請先上傳檔案!"
file_path = file_obj.name
if file_path.endswith('.csv'):
input_df = pd.read_csv(file_path, header=None)
else:
input_df = pd.read_excel(file_path, header=None, engine='openpyxl')
if input_df.shape[1] < 2:
return None, "錯誤:表格格式不正確,至少需要有兩欄資料(左邊歌名、右邊歌手)!"
song_col_idx = 0
artist_col_idx = 1
# 初始化第一個瀏覽器
driver = create_driver()
lyricists = []
composers = []
release_dates = []
total_songs = len(input_df)
# 🎯 核心進度條啟動:設定總步數
progress(0, desc="🚀 正在準備啟動自動化系統...")
for index, row in input_df.iterrows():
song = row[song_col_idx]
artist = row[artist_col_idx]
# 🎯 即時更新網頁上的進度條狀態與文字提示
progress((index / total_songs), desc=f"🎵 正在處理 ({index + 1}/{total_songs}): {song if not pd.isna(song) else ''}")
# 🎯 記憶體防爆優化:每查 30 首歌,就關掉重開一次瀏覽器清空快取
if index > 0 and index % 30 == 0:
driver.quit()
driver = create_driver()
if index == 0 and any(keyword in str(song) for keyword in ["歌名", "歌曲", "曲目", "Title", "Name"]):
lyricists.append("作詞者")
composers.append("作曲者")
release_dates.append("最早發行時間")
continue
if pd.isna(song):
lyricists.append("歌名空白")
composers.append("歌名空白")
release_dates.append("歌名空白")
continue
lyr, comp, rel = process_single_song(driver, song, artist)
lyricists.append(lyr)
composers.append(comp)
release_dates.append(rel)
# 🎯 在這裡加入「溫柔限速機制」:每查完一首,強迫機器人休息 1.5 秒
# 這樣查 100 首歌雖然會多花 150 秒,但可以確保 100% 穩當不被國圖封鎖!
time.sleep(1.5)
driver.quit()
progress(0.95, desc="💾 正在將清洗後的數據打包成 Excel 檔案...")
# 重新結合成全新表格
output_df = pd.DataFrame()
output_df['歌曲'] = input_df[song_col_idx]
output_df['歌手'] = input_df[artist_col_idx]
output_df['作詞者'] = lyricists
output_df['作曲者'] = composers
output_df['最早發行時間'] = release_dates
if any(keyword in str(output_df.iloc[0, 0]) for keyword in ["歌名", "歌曲", "曲目", "Title"]):
output_df.columns = ["歌曲", "歌手", "作詞者", "作曲者", "最早發行時間"]
output_df = output_df.drop(output_df.index[0])
output_file_path = "isrc_batch_results.xlsx"
output_df.to_excel(output_file_path, index=False, engine='openpyxl')
progress(1.0, desc="🎉 大功告成!")
return output_file_path, f"🎉 成功處理完成!共處理 {len(output_df)} 筆歌曲資料。"
# Gradio 批量介面保持不變
with gr.Blocks(title="ISRC 歌曲資料批量洗滌器") as demo:
gr.Markdown("# 🗂️ 臺灣 ISRC 歌曲詞曲資料【批量】查詢系統")
gr.Markdown("請上傳一個 Excel 或 CSV 表格檔。規定:**第一欄為「歌名」,第二欄為「歌手」**(有無標題列皆相容)。")
with gr.Row():
file_input = gr.File(label="請上傳 Excel (.xlsx) 或 CSV (.csv) 檔案", file_types=[".xlsx", ".csv"])
process_btn = gr.Button("開始批量轉換", variant="primary")
with gr.Row():
status_output = gr.Textbox(label="處理狀態", placeholder="尚未開始...")
file_output = gr.File(label="下載轉換完成的 Excel 表格")
process_btn.click(
fn=batch_process_file,
inputs=file_input,
outputs=[file_output, status_output]
)
demo.launch()