综合设计——多源异构数据采集与融合应用综合实践

这个项目属于哪个课程 https://edu.cnblogs.com/campus/fzu/2024DataCollectionandFusiontechnology/
组名、项目简介 组名:都给爷爬
项目目标:为心理疾病患者进行个性化的音乐疗愈
项目需求:市面上大多数音乐软件都需要会员而且存在打榜等现象,不能完全个性化推荐,我们希望我们的个性化音乐系统能为心理疾病患者带来音乐疗愈,因此选择了该公益项目
项目开展技术路线:python(django,pytorch,tensorflow)、爬虫技术、MySQL
团队成员学号 102202135
102202146
102202127
102202125
102202139
102202109
102202128
这个项目的目标 我们希望免费的公益的为有心理疾病的患者提供音乐疗愈的软件,我们可以根据患者、医生希望的音乐进行推荐,给患者相对舒服的音乐疗愈。
其他参考文献 【1】https://github.com/marl/openl3
【2】https://eva.fing.edu.uy/pluginfile.php/524749/mod_folder/content/0/BERT Pre-training of Deep Bidirectional Transformers for Language Understanding.pdf
【3】https://link.springer.com/chapter/10.1007/978-1-4842-6168-2_6
【4】https://blog.csdn.net/weixin_42645636/article/details/135777479

项目整体介绍

码云链接
该项目是基于智能推荐算法,为心理障碍人群提供免费公益的音乐播放平台。我们采用爬虫从音乐平台上爬取歌曲、歌单丰富我们的平台,为用户提供丰富的、多类的曲目进行选择。使用OpenGL->librosa对音频进行特征提取、Bert提取文本特征、resnet50提取图像特征,同时我们接入大模型,让大模型对用户现状进行分析,给出适合的音乐类型推荐,再将用户的行为特征一起考虑,构成我们的协同过滤推荐算法,实现音乐个性化的推荐,满足用户需求。

系统概述:

1. 前端

前端部分负责与用户交互,展示推荐结果和用户行为数据。在此项目中,前端将展示歌曲的推荐结果、用户偏好以及推荐算法反馈的数据。用户通过网页或移动端应用提交行为数据(如评分、评论、播放历史等),并接收根据多模态数据推荐的歌曲。前端与后端通过API进行数据交互,确保用户体验的流畅性和响应速度。

2. 后端

后端主要负责处理用户请求、管理数据存储、执行推荐算法及特征提取和融合等逻辑。具体实现包括以下几个模块:
特征提取模块:负责从文本(BERT)、图像(ResNet50)、音频(梅尔频谱图、MFCC等)中提取多模态特征。通过并行处理提升性能。
推荐算法模块:结合内容推荐与协同过滤算法,通过加权融合提升推荐的多样性和精准性。
用户偏好建模模块:根据用户的多维度行为(收藏、评分、评论、播放历史)建立偏好模型。
特征融合模块:通过多头注意力机制和线性投影融合音频、图像、文本特征,输出统一的特征向量。
特征存储模块:利用Django ORM进行数据库操作,通过事务确保数据一致性,缓存机制提高特征读取效率。

3. 数据库

数据库存储用户行为数据、歌曲信息以及提取的多模态特征。具体包括:
歌曲信息表:存储每首歌曲的基本信息(如歌名、歌手、专辑等)和相关的多模态特征(音频、文本、图像特征)。
用户行为数据表:存储用户的收藏、评分、评论及播放历史。
特征缓存表:存储提取的音频、文本、图像及融合后的特征,使用序列化存储以支持高效的读取和管理。

4. AI接口

AI接口主要包含两个部分:
特征提取接口:提供音频、文本和图像特征的提取服务,分别利用预训练的BERT、ResNet50和自定义音频处理模型进行处理。接口通过异步或并行处理提高特征提取的效率。
推荐接口:基于用户行为数据和歌曲特征,提供基于内容推荐和协同过滤推荐的服务。通过加权融合推荐算法,生成最终的推荐列表,返回给前端展示。

5. 爬虫

通过爬取千千音乐(https://music.91q.com),解析请求获取API,获取歌曲和歌单相应的信息并存到数据库中。

6. 部署平台

使用华为云平台部署系统,保证系统的高可用和稳定性。

项目展示






个人任务

爬虫程序分析及编写

爬虫这一部分一共有三个程序,实现的是通过爬取千千音乐(http://music.91q.com)获得我们所需的音乐的详细信息并且存到数据库中,同时通过额外的API获取音乐文件的URL,下载音乐文件。

spiderPublic.py
import requests
import time
import hashlib
import os
import csv
import json


class PublicUtilsFn:
    def __init__(self):
        self.sheetUrl = "https://music.91q.com/v1/tracklist/list"
        self.songUrl = "https://music.91q.com/v1/tracklist/info"
        self.musicInfoUrl = "https://music.91q.com/v1/song/tracklink"
        self.secret = "0b50b02fd0d73a9c4c8c3a781c30845f"
        self.appid = 16073360

    def init_songSheet(self):
        if not os.path.exists('./songSheet.csv'):
            with open('./songSheet.csv', 'w', encoding='utf-8', newline='') as csvFile:
                write = csv.writer(csvFile)
                write.writerow([
                    'id',
                    'title',
                    'desc',
                    'tagList',
                    'img',
                    'type',
                    'musicNum',
                    'createTime'
                ])

    def init_song(self):
        if not os.path.exists('./song.csv'):
            with open('./song.csv', 'w', encoding='utf-8', newline='') as csvFile:
                write = csv.writer(csvFile)
                write.writerow([
                    'assetId',
                    'songSheetId',
                    'title',
                    'singer',
                    'type',
                    'albumTitle',
                    'duration',
                    'lyric',
                    'img',
                    'songUrl',
                    'createTime'
                ])

    def save_to_songSheetCsv(self, row):
        with open('songSheet.csv', 'a', encoding='utf8', newline='') as csvfile:
            write = csv.writer(csvfile)
            write.writerow(row)

    def save_to_songCsv(self, row):
        with open('song.csv', 'a', encoding='utf8', newline='') as csvfile:
            write = csv.writer(csvfile)
            write.writerow(row)

    def md5Encode(self, str):
        md5 = hashlib.md5()
        md5.update(str.encode('utf-8'))  # 使用MD5算法对字符串进行编码
        return md5.hexdigest()  # 返回编码后的16进制字符串

    def getTimeStamp(self):
        return int(time.time())  # 获取当前时间的时间戳,返回整数类型

    # 获取音乐详情
    def getMusicInfo(self, TSID):
        timestamp = self.getTimeStamp()
        args = {
            "sign": "",
            "appid": self.appid,
            "TSID": TSID,
            "timestamp": timestamp
        }
        signParam = "TSID={0}&appid={1}&timestamp={2}{3}".format(TSID, self.appid, timestamp, self.secret)
        args["sign"] = self.md5Encode(signParam)
        # 构建最终的URL
        final_url = self.musicInfoUrl + '?' + '&'.join(['{}={}'.format(key, value) for key, value in args.items()])
        print(final_url)
        res = requests.get(url=self.musicInfoUrl, params=args)
        return res.json()

主要功能:初始化时定义了几个常用的url,用于获取歌单列表,获取单个歌单的歌曲详情,获取歌曲的播放链接。利用MD5码将输入的字符串进行加密,生成API请求参数。然后将歌单信息和歌曲信息各自存到对应的CSV文件里。

spiderSong.py
import csv
import time

from spider.spiderPublic import PublicUtilsFn
import requests
import os
import django
import random
import pandas as pd

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'python个性化音乐推荐系统.settings')
django.setup()

from music.models import SongType, SongSheet, Song


def get_json(id):
    # 获取时间戳
    timestamp = publicObj.getTimeStamp()

    # 创建参数字典
    args = {
        "sign": "",
        "id": id,
        "pageSize": 50,
        "appid": publicObj.appid,
        "timestamp": timestamp,
    }

    # 创建签名参数字符串
    signParam = "appid={0}&id={1}&pageSize={2}&timestamp={3}{4}".format(
        publicObj.appid,
        args['id'],
        args["pageSize"],
        timestamp,
        publicObj.secret  # 确保 secret 是在参数列表中的最后一个,并且与 signParam 字符串中的占位符对应
    )

    # 添加签名到参数字典
    args["sign"] = publicObj.md5Encode(signParam)
    # 构建最终的URL
    final_url = publicObj.songUrl + '?' + '&'.join(['{}={}'.format(key, value) for key, value in args.items()])
    print(final_url)

    # 发送HTTP GET请求
    res = requests.get(url=publicObj.songUrl, params=args)

    # 打印响应结果
    print(res.json())
    return res.json()


def parse_json(response, songSheetId):
    dataList = response['data']['trackList']
    for song in dataList:
        try:
            id = song['assetId']
            songSheetId = songSheetId
            title = song['title']
            singer = song['artist'][0]['name']
            type = song['genre']
            albumTitle = song['albumTitle']
            duration = song['duration']
            lyric = song['lyric']
            img = song['pic']

            # 初始化重试次数
            max_retries = 2
            retry_count = 0
            # 初始化songUrl为空字符串
            songUrl = ''
            # 当重试次数小于最大重试次数时,进行尝试
            while retry_count < max_retries:
                try:
                    # 尝试获取歌曲的URL地址
                    songUrl1 = publicObj.getMusicInfo(id).get("data", {}).get("path")
                    songUrl2 = publicObj.getMusicInfo(id).get("data", {}).get("trail_audio_info", {}).get("path")

                    # 如果songUrl1存在,则赋值给songUrl
                    if songUrl1:
                        songUrl = songUrl1
                    # 否则,如果songUrl2存在,则赋值给songUrl
                    elif songUrl2:
                        songUrl = songUrl2
                    # 如果成功获取到URL,则退出循环
                    break
                except:
                    # 如果发生异常,增加重试次数
                    retry_count += 1
                    # 如果达到最大重试次数仍未成功,则设置songUrl为空字符串
                    if retry_count == max_retries:
                        songUrl = ''
            print(songUrl)
            # 获取歌曲的发布时间,只取前10个字符(可能是年月日时分秒)
            createTime = song['releaseDate'][:10]
            # 生成一个随机的整数作为文件名的部分
            randomFilename = str(random.randint(1, 10000000)) + '.mp3'
            try:
                download_audio(songUrl, '../media/music', randomFilename)
                print(randomFilename)
            except:
                randomFilename = ''  # 重置文件名为空字符串

            resultData = [
                id, songSheetId, title, singer, type, albumTitle, duration,
                lyric, img, f'music/{randomFilename}', createTime
            ]
            publicObj.save_to_songCsv(resultData)
            print('%s - 歌曲爬取完毕' % song['title'])
        except:
            continue


def download_audio(url, local_folder, filename):
    # 检查本地文件夹是否存在
    if not os.path.exists(local_folder):
        # 如果不存在,则创建该文件夹
        os.makedirs(local_folder)
    # 构建文件在本地系统中的完整路径
    local_path = os.path.join(local_folder, filename)
    # 使用requests库获取远程URL的内容
    response = requests.get(url)
    # 以写入模式打开目标文件
    with open(local_path, 'wb') as file:
        # 将响应内容写入到文件中
        file.write(response.content)


def clearData(output_file='./cleaned_songs.csv'):
    # 读取 CSV 文件
    df = pd.read_csv('./song.csv')
    print("总条数为%d" % df.shape[0])

    # 筛选 songUrl 不等于 'music/',并创建副本
    matching_rows = df[df['songUrl'] != 'music/'].copy()

    if not matching_rows.empty:
        print("正在去除没有音乐文件的歌曲...")

        # 将 title 列转换为小写以区分大小写
        matching_rows['title_lower'] = matching_rows['title'].str.lower()

        # 去除重复的 title,保持原始大小写
        unique_rows = matching_rows.drop_duplicates(subset='title_lower', keep='first')

        # 删除临时的 'title_lower' 列
        unique_rows = unique_rows.drop(columns=['title_lower'])

        if not unique_rows.empty:
            print("去除重复歌曲后条数为%d" % unique_rows.shape[0])

            # 保存去重后的数据到新的 CSV 文件
            unique_rows.to_csv(output_file, index=False)
            print(f"去重后的数据已保存到 {output_file}")

            return unique_rows.values
        else:
            print("没有找到有效的歌曲文件。")
            return None
    else:
        print("没有找到任何歌曲文件。")
        return None


def save_to_sql():
    data = clearData()
    allSheetList = SongSheet.objects.all()

    for song in data:
        try:
            for sheet in allSheetList:
                if song[1] == sheet.sheetId:
                    Song.objects.create(song_Sheet=sheet, title=song[2], singer=song[3],
                                        type=song[4],
                                        albumTitle=song[5],
                                        duration=song[6],
                                        lyric=song[7],
                                        img=song[8],
                                        songUrl=song[9],
                                        createTime=song[10])
        except:
            pass
    print('歌曲存入成功')


def main(startSheet, endSheet):
    songSheetList = []
    with open('./songSheet.csv', 'r', encoding='utf-8') as reader:
        readerCsv = csv.reader(reader)
        next(readerCsv)  # 跳过表头
        for songSheet in readerCsv:
            songSheetList.append(songSheet)
    # print(songSheetList)
    for songSheet in songSheetList[startSheet:endSheet]:
        print('歌单名称为:' + songSheet[1])
        resJson = get_json(songSheet[0])
        parse_json(resJson, songSheet[0])


if __name__ == '__main__':
    publicObj = PublicUtilsFn()
    publicObj.init_song()
    main(1, 480)
    save_to_sql()

主要功能:从指定的API获取包含歌曲的歌单信息,构建请求并获取响应。对于每个歌单中的歌曲,提取相关信息,并通过额外的API获取音乐文件的URL,下载音乐文件。将获取到的歌曲音频文件下载到本地指定目录,并保存到CSV文件中。从CSV文件中筛选有效数据,去除无效或重复的歌曲。将去重后的数据通过Django ORM保存到数据库中,包括歌单、歌曲等信息。主函数中,main(1, 480) 使得程序从 songSheet.csv 文件中获取第二行到第480行的数据(总共处理480个歌单)

spiderSheet.py
from spider.spiderPublic import PublicUtilsFn

import requests
import json
import os
import django
import pandas as pd

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'python个性化音乐推荐系统.settings')
django.setup()
from music.models import SongType, SongSheet

typeList = [
    {
        'subCateId': 2385,
        'name': '古典'
    },
    {
        'subCateId': 2411,
        'name': '治愈'
    },
    {
        'subCateId': 2412,
        'name': '放松'
    },
]


def get_json(type, page):
    timestamp = publicObj.getTimeStamp()

    args = {
        "sign": "",
        "appid": publicObj.appid,
        "subCateId": type['subCateId'],
        "pageNo": page,
        "timestamp": timestamp,
        "pageSize": 50
    }

    signParam = "appid={0}&pageNo={1}&pageSize={2}&subCateId={3}&timestamp={4}{5}".format(
        publicObj.appid,
        args['pageNo'],
        args['pageSize'],
        args['subCateId'],
        timestamp,
        publicObj.secret  # 确保 secret 是在参数列表中的最后一个,并且与 signParam 字符串中的占位符对应
    )

    args["sign"] = publicObj.md5Encode(signParam)
    # print(args)
    # 构建最终的URL
    final_url = publicObj.sheetUrl + '?' + '&'.join(['{}={}'.format(key, value) for key, value in args.items()])
    print(final_url)
    res = requests.get(url=publicObj.sheetUrl, params=args)
    # print(res.json())
    # print()
    return res.json()


def parse_json(response, type):
    dataList = response['data']['result']
    for sheet in dataList:
        id = sheet['id']
        title = sheet['title']
        desc = sheet['desc']
        tagList = json.dumps(sheet['tagList'])
        img = sheet['pic']
        typeOf = type['name']
        musicNum = sheet['trackCount']
        createTime = sheet['addDate'][:10]
        resultData = [id, title, desc, tagList, img, typeOf, musicNum, createTime]
        publicObj.save_to_songSheetCsv(resultData)
    print('%s歌单爬取完毕' % type['name'])


def main(pageStart, pageEnd):
    for page in range(pageStart, pageEnd + 1):
        for type in typeList:
            resJson = get_json(type, page)
            parse_json(resJson, type)


def clearData():
    df = pd.read_csv('./songSheet.csv')
    print("歌单总条数为%d" % df.shape[0])
    return df.values


def save_to_sql():
    data = clearData()
    for sheet in data:
        try:
            # 添加不同类型的歌主题  title字段已经设置唯一性
            SongType.objects.create(title=sheet[5], desc=sheet[2], tagList=sheet[3])
        except:
            pass

    allTypeList = SongType.objects.all()
    for sheet in data:
        try:
            for type in allTypeList:
                if sheet[5] == type.title:
                    SongSheet.objects.create(song_type=type, title=sheet[1], sheetId=sheet[0], img=sheet[4],
                                             createTime=sheet[7], musicNum=sheet[6])
        except:
            pass

    print('类型、歌单存入成功')

if __name__ == '__main__':
    publicObj = PublicUtilsFn()
    publicObj.init_songSheet()
    main(1, 3)
    save_to_sql()

主要功能:从外部API爬取不同类型的歌单信息,如古典、治愈、放松,用于我们治疗具有心理疾病的病人的需求。解析数据并将歌单及其相关类型信息存储到数据库中。主函数的main(1, 3) 通过调用 get_json 函数获取指定类型(古典、治愈、放松)歌单的分页数据(从第1页到第3页),并将解析得到的歌单信息保存到CSV文件中。

爬取内容示例结果展示截图:

心得体会:

在这次项目的任务中,我负责的是编写爬虫程序爬取所需音乐的任务。我体会到了爬虫的数据获取、解析、存储环节。首先,我需要从指定的 API 获取数据。通过构建请求参数、生成签名并发送 HTTP 请求,成功地获取了返回的 JSON 格式数据。为了便于后续处理,我将数据解析为适合存储和分析的格式。通过对 JSON 数据的遍历和提取,成功提取了每个歌单的关键字段,如 id, title, desc, tagList 等信息。此外,我还学习到如何通过异常处理机制避免数据插入时发生错误。例如,在插入 SongType 和 SongSheet 时,我通过 try-except 语句捕捉并忽略了重复插入的情况,这确保了程序的稳定性和高效性。
在开发过程中,也遇到了一些问题和挑战:
1.部分接口可能会有请求频率限制,导致爬虫频繁报错。为了避免被封禁,我在开发中模拟了合理的请求间隔,并采取了错误重试机制。
2.不同歌单的数据格式可能略有差异,尤其是 tagList 这种复杂的数据结构,需要确保在解析时统一格式,避免出错。
3.在任务的初期,我试图想办法爬取VIP音乐,但经过多次的尝试,发现能力有限

posted @ 2024-12-16 01:21  Russell-Cadian  阅读(23)  评论(0编辑  收藏  举报