Commit ab7f4ed4 ab7f4ed482148853c589ce1b804246b6f39a1e87 by 沈秋雨

feat(etl): 实现 hikoon-data-spider 三平台数据获取

1 parent d260999d
1 import pymysql
2
3
4 def _ids_query(query_template: str, ids: list, conn: pymysql.Connection) -> list[dict]:
5 if not ids:
6 return []
7 placeholders = ','.join(['%s'] * len(ids))
8 query = query_template.format(placeholders=placeholders)
9 with conn.cursor() as cur:
10 cur.execute(query, ids)
11 return cur.fetchall()
12
13
14 # ─── QQ Music ────────────────────────────────────────────────────────────────
15
16 _QQ_SONGS_SQL = """
17 SELECT s.id, s.mid, s.album_id, s.cover, s.title, s.duration,
18 s.lyric, s.composer_name, s.lyricist_name, s.platform_index_url, s.published_at,
19 a.mid AS album_mid, a.cover AS album_cover, a.title AS album_title,
20 a.intro AS album_intro, a.type AS album_type, a.company_id,
21 a.company, a.is_owner, a.published_at AS album_published_at
22 FROM media_tencent_songs s
23 LEFT JOIN media_tencent_albums a ON a.id = s.album_id
24 WHERE s.mid IN ({placeholders})
25 """
26
27 _QQ_SINGERS_SQL = """
28 SELECT shs.song_id, shs.singer_id, sg.mid, sg.name, sg.avatar,
29 sg.sex, sg.area, sg.`index`, sg.intro, sg.home_url
30 FROM media_tencent_singer_has_songs shs
31 JOIN media_tencent_singers sg ON sg.id = shs.singer_id
32 WHERE shs.song_id IN ({placeholders})
33 """
34
35
36 def fetch_qq_songs(conn: pymysql.Connection, mids: list[str]) -> dict[str, dict]:
37 rows = _ids_query(_QQ_SONGS_SQL, mids, conn)
38 return {row['mid']: row for row in rows}
39
40
41 def fetch_qq_singers(conn: pymysql.Connection, song_ids: list[int]) -> dict[int, list[dict]]:
42 rows = _ids_query(_QQ_SINGERS_SQL, song_ids, conn)
43 result: dict[int, list] = {}
44 for row in rows:
45 result.setdefault(row['song_id'], []).append(row)
46 return result
47
48
49 # ─── Kugou ───────────────────────────────────────────────────────────────────
50
51 _KUGOU_SONGS_SQL = """
52 SELECT s.id, s.hid, s.album_audio_id, s.album_id, s.cover, s.title, s.duration,
53 s.lyric, s.composer_name, s.lyricist_name, s.platform_index_url, s.published_at,
54 a.cover AS album_cover, a.title AS album_title, a.intro AS album_intro,
55 a.type AS album_type, a.company_id, a.company, a.is_owner,
56 a.published_at AS album_published_at
57 FROM media_ku_gou_songs s
58 LEFT JOIN media_ku_gou_albums a ON a.id = s.album_id
59 WHERE s.id IN ({placeholders})
60 """
61
62 _KUGOU_SINGERS_SQL = """
63 SELECT shs.song_id, shs.singer_id, sg.name, sg.avatar,
64 sg.sex, sg.area, sg.`index`, sg.intro, sg.home_url
65 FROM media_ku_gou_singer_has_songs shs
66 JOIN media_ku_gou_singers sg ON sg.id = shs.singer_id
67 WHERE shs.song_id IN ({placeholders})
68 """
69
70
71 def fetch_kugou_songs(conn: pymysql.Connection, song_ids: list[int]) -> dict[int, dict]:
72 rows = _ids_query(_KUGOU_SONGS_SQL, song_ids, conn)
73 return {row['id']: row for row in rows}
74
75
76 def fetch_kugou_singers(conn: pymysql.Connection, song_ids: list[int]) -> dict[int, list[dict]]:
77 rows = _ids_query(_KUGOU_SINGERS_SQL, song_ids, conn)
78 result: dict[int, list] = {}
79 for row in rows:
80 result.setdefault(row['song_id'], []).append(row)
81 return result
82
83
84 # ─── Netease ─────────────────────────────────────────────────────────────────
85
86 _NETEASE_SONGS_SQL = """
87 SELECT s.id, s.album_id, s.cover, s.title, s.duration,
88 s.lyric, s.composer_name, s.lyricist_name, s.platform_index_url, s.published_at,
89 a.cover AS album_cover, a.title AS album_title, a.intro AS album_intro,
90 a.type AS album_type, a.company_id, a.company, a.is_owner,
91 a.published_at AS album_published_at
92 FROM media_netease_songs s
93 LEFT JOIN media_netease_albums a ON a.id = s.album_id
94 WHERE s.id IN ({placeholders})
95 """
96
97 _NETEASE_SINGERS_SQL = """
98 SELECT shs.song_id, shs.singer_id, sg.name, sg.avatar,
99 sg.sex, sg.area, sg.`index`, sg.intro, sg.home_url
100 FROM media_netease_singer_has_songs shs
101 JOIN media_netease_singers sg ON sg.id = shs.singer_id
102 WHERE shs.song_id IN ({placeholders})
103 """
104
105
106 def fetch_netease_songs(conn: pymysql.Connection, song_ids: list[int]) -> dict[int, dict]:
107 rows = _ids_query(_NETEASE_SONGS_SQL, song_ids, conn)
108 return {row['id']: row for row in rows}
109
110
111 def fetch_netease_singers(conn: pymysql.Connection, song_ids: list[int]) -> dict[int, list[dict]]:
112 rows = _ids_query(_NETEASE_SINGERS_SQL, song_ids, conn)
113 result: dict[int, list] = {}
114 for row in rows:
115 result.setdefault(row['song_id'], []).append(row)
116 return result
1 from unittest.mock import MagicMock, patch
2 from etl_to_crawler.spider import fetch_qq_songs, fetch_qq_singers
3
4 def _make_cursor(rows):
5 cur = MagicMock()
6 cur.__enter__ = MagicMock(return_value=cur)
7 cur.__exit__ = MagicMock(return_value=False)
8 cur.fetchall.return_value = rows
9 return cur
10
11 def test_fetch_qq_songs_keys_by_mid():
12 conn = MagicMock()
13 conn.cursor.return_value = _make_cursor([
14 {'mid': 'abc123', 'id': 1, 'title': '歌曲A', 'duration': 200,
15 'lyric': None, 'composer_name': None, 'lyricist_name': None,
16 'platform_index_url': None, 'published_at': None,
17 'cover': '', 'album_id': None,
18 'album_mid': None, 'album_title': None, 'album_cover': None,
19 'album_intro': None, 'album_type': None, 'company_id': 0,
20 'company': None, 'is_owner': 0, 'album_published_at': None}
21 ])
22 result = fetch_qq_songs(conn, ['abc123'])
23 assert 'abc123' in result
24 assert result['abc123']['title'] == '歌曲A'
25
26 def test_fetch_qq_songs_empty_list():
27 conn = MagicMock()
28 result = fetch_qq_songs(conn, [])
29 assert result == {}
30 conn.cursor.assert_not_called()
31
32 def test_fetch_qq_singers_keys_by_song_id():
33 conn = MagicMock()
34 conn.cursor.return_value = _make_cursor([
35 {'song_id': 1, 'singer_id': 10, 'mid': 'sg1', 'name': '歌手A',
36 'avatar': '', 'sex': 'M', 'area': '华语', 'index': 'G',
37 'intro': None, 'home_url': None}
38 ])
39 result = fetch_qq_singers(conn, [1])
40 assert 1 in result
41 assert result[1][0]['name'] == '歌手A'