Commit 8bd8c02b 8bd8c02b2d435d6a7d7ae5b91dbed2876e9918c3 by 沈秋雨

refactor(crawler): 统一新增 provider_name 与 crawler_source_data 字段

- 替换所有歌手、专辑和歌曲的 provider_name 常量为 PROVIDER_YINYAN
- 增加 crawler_source_data 字段传递原始采集数据 JSON
- 更新 upsert 函数,支持写入 provider_name 和 crawler_source_data 字段
- 修改数据库写入 SQL,新增 provider_name 和 crawler_source_data 两列
- 添加相关单元测试,验证 provider_name 和 crawler_source_data 是否正确写入
- 统一整合 QQ、酷狗、网易等平台数据结构,增强数据来源追踪能力
1 parent ac91b0fe
...@@ -26,7 +26,7 @@ from .lyric import strip_timestamps ...@@ -26,7 +26,7 @@ from .lyric import strip_timestamps
26 26
27 logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s') 27 logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s')
28 log = logging.getLogger(__name__) 28 log = logging.getLogger(__name__)
29 PROVIDER_KUGOU = 'kugou' 29 PROVIDER_YINYAN = 'yinyan'
30 30
31 31
32 def _safe_transfer(url, oss_key, bucket, base_url): 32 def _safe_transfer(url, oss_key, bucket, base_url):
...@@ -83,7 +83,13 @@ def _process_qq(hk_row: dict, pr: dict, spider_conn, pg_cur, bucket, base_url): ...@@ -83,7 +83,13 @@ def _process_qq(hk_row: dict, pr: dict, spider_conn, pg_cur, bucket, base_url):
83 build_oss_key('qq', 'singer', sg['mid'] + '.jpg'), 83 build_oss_key('qq', 'singer', sg['mid'] + '.jpg'),
84 bucket, base_url 84 bucket, base_url
85 ) 85 )
86 singer_rows.append({**sg, 'id': sg['singer_id'], 'avatar': avatar}) 86 singer_rows.append({
87 **sg,
88 'id': sg['singer_id'],
89 'avatar': avatar,
90 'provider_name': PROVIDER_YINYAN,
91 'crawler_source_data': _source_json(sg),
92 })
87 upsert_qq_singers(pg_cur, singer_rows) 93 upsert_qq_singers(pg_cur, singer_rows)
88 94
89 # singers JSONB 95 # singers JSONB
...@@ -103,6 +109,19 @@ def _process_qq(hk_row: dict, pr: dict, spider_conn, pg_cur, bucket, base_url): ...@@ -103,6 +109,19 @@ def _process_qq(hk_row: dict, pr: dict, spider_conn, pg_cur, bucket, base_url):
103 'intro': sp.get('album_intro'), 'type': sp.get('album_type') or '', 109 'intro': sp.get('album_intro'), 'type': sp.get('album_type') or '',
104 'company_id': sp.get('company_id') or 0, 'company': sp.get('company') or '', 110 'company_id': sp.get('company_id') or 0, 'company': sp.get('company') or '',
105 'is_owner': sp.get('is_owner') or 0, 'published_at': sp.get('album_published_at'), 111 'is_owner': sp.get('is_owner') or 0, 'published_at': sp.get('album_published_at'),
112 'provider_name': PROVIDER_YINYAN,
113 'crawler_source_data': _source_json({
114 'id': sp.get('album_id'),
115 'mid': sp.get('album_mid'),
116 'cover': sp.get('album_cover'),
117 'title': sp.get('album_title'),
118 'intro': sp.get('album_intro'),
119 'type': sp.get('album_type'),
120 'company_id': sp.get('company_id'),
121 'company': sp.get('company'),
122 'is_owner': sp.get('is_owner'),
123 'published_at': sp.get('album_published_at'),
124 }),
106 }]) 125 }])
107 126
108 # 写入 song 127 # 写入 song
...@@ -125,6 +144,8 @@ def _process_qq(hk_row: dict, pr: dict, spider_conn, pg_cur, bucket, base_url): ...@@ -125,6 +144,8 @@ def _process_qq(hk_row: dict, pr: dict, spider_conn, pg_cur, bucket, base_url):
125 'platform_index_url': sp.get('platform_index_url') or f'https://y.qq.com/n/ryqq/songDetail/{mid}', 144 'platform_index_url': sp.get('platform_index_url') or f'https://y.qq.com/n/ryqq/songDetail/{mid}',
126 'published_at': sp.get('published_at') or hk_row.get('issue_time'), 145 'published_at': sp.get('published_at') or hk_row.get('issue_time'),
127 'singers_json': singers_json, 146 'singers_json': singers_json,
147 'provider_name': PROVIDER_YINYAN,
148 'crawler_source_data': _source_json(sp),
128 }]) 149 }])
129 # 查询实际 UUID(ON CONFLICT DO NOTHING 时使用已有 UUID) 150 # 查询实际 UUID(ON CONFLICT DO NOTHING 时使用已有 UUID)
130 pg_cur.execute('SELECT id FROM crawler_qqmusic_songs WHERE platform_song_id = %s', (platform_song_id,)) 151 pg_cur.execute('SELECT id FROM crawler_qqmusic_songs WHERE platform_song_id = %s', (platform_song_id,))
...@@ -178,7 +199,7 @@ def _process_kugou(hk_row: dict, pr: dict, spider_conn, pg_cur, bucket, base_url ...@@ -178,7 +199,7 @@ def _process_kugou(hk_row: dict, pr: dict, spider_conn, pg_cur, bucket, base_url
178 **sg, 199 **sg,
179 'id': sg['singer_id'], 200 'id': sg['singer_id'],
180 'avatar': avatar, 201 'avatar': avatar,
181 'provider_name': PROVIDER_KUGOU, 202 'provider_name': PROVIDER_YINYAN,
182 'crawler_source_data': _source_json(sg), 203 'crawler_source_data': _source_json(sg),
183 }) 204 })
184 upsert_kugou_singers(pg_cur, singer_rows) 205 upsert_kugou_singers(pg_cur, singer_rows)
...@@ -198,7 +219,7 @@ def _process_kugou(hk_row: dict, pr: dict, spider_conn, pg_cur, bucket, base_url ...@@ -198,7 +219,7 @@ def _process_kugou(hk_row: dict, pr: dict, spider_conn, pg_cur, bucket, base_url
198 'type': sp.get('album_type') or '', 'company_id': sp.get('company_id') or 0, 219 'type': sp.get('album_type') or '', 'company_id': sp.get('company_id') or 0,
199 'company': sp.get('company') or '', 'is_owner': sp.get('is_owner') or 0, 220 'company': sp.get('company') or '', 'is_owner': sp.get('is_owner') or 0,
200 'published_at': sp.get('album_published_at'), 221 'published_at': sp.get('album_published_at'),
201 'provider_name': PROVIDER_KUGOU, 222 'provider_name': PROVIDER_YINYAN,
202 'crawler_source_data': _source_json({ 223 'crawler_source_data': _source_json({
203 'id': sp.get('album_id'), 224 'id': sp.get('album_id'),
204 'cover': sp.get('album_cover'), 225 'cover': sp.get('album_cover'),
...@@ -231,7 +252,7 @@ def _process_kugou(hk_row: dict, pr: dict, spider_conn, pg_cur, bucket, base_url ...@@ -231,7 +252,7 @@ def _process_kugou(hk_row: dict, pr: dict, spider_conn, pg_cur, bucket, base_url
231 'platform_index_url': sp.get('platform_index_url') or f'http://www.kugou.com/song/#hash={sp.get("hid") or pr.get("platform_mid", "")}', 252 'platform_index_url': sp.get('platform_index_url') or f'http://www.kugou.com/song/#hash={sp.get("hid") or pr.get("platform_mid", "")}',
232 'published_at': sp.get('published_at') or hk_row.get('issue_time'), 253 'published_at': sp.get('published_at') or hk_row.get('issue_time'),
233 'singers_json': singers_json, 254 'singers_json': singers_json,
234 'provider_name': PROVIDER_KUGOU, 255 'provider_name': PROVIDER_YINYAN,
235 'crawler_source_data': _source_json(sp), 256 'crawler_source_data': _source_json(sp),
236 }]) 257 }])
237 # 查询实际 UUID(ON CONFLICT DO NOTHING 时使用已有 UUID) 258 # 查询实际 UUID(ON CONFLICT DO NOTHING 时使用已有 UUID)
...@@ -281,7 +302,13 @@ def _process_netease(hk_row: dict, pr: dict, spider_conn, pg_cur, bucket, base_u ...@@ -281,7 +302,13 @@ def _process_netease(hk_row: dict, pr: dict, spider_conn, pg_cur, bucket, base_u
281 build_oss_key('netease', 'singer', str(sg['singer_id']) + '.jpg'), 302 build_oss_key('netease', 'singer', str(sg['singer_id']) + '.jpg'),
282 bucket, base_url 303 bucket, base_url
283 ) 304 )
284 singer_rows.append({**sg, 'id': sg['singer_id'], 'avatar': avatar}) 305 singer_rows.append({
306 **sg,
307 'id': sg['singer_id'],
308 'avatar': avatar,
309 'provider_name': PROVIDER_YINYAN,
310 'crawler_source_data': _source_json(sg),
311 })
285 upsert_netease_singers(pg_cur, singer_rows) 312 upsert_netease_singers(pg_cur, singer_rows)
286 313
287 singers_json = json.dumps([{ 314 singers_json = json.dumps([{
...@@ -312,6 +339,18 @@ def _process_netease(hk_row: dict, pr: dict, spider_conn, pg_cur, bucket, base_u ...@@ -312,6 +339,18 @@ def _process_netease(hk_row: dict, pr: dict, spider_conn, pg_cur, bucket, base_u
312 'type': sp.get('album_type') or '', 'company_id': sp.get('company_id') or 0, 339 'type': sp.get('album_type') or '', 'company_id': sp.get('company_id') or 0,
313 'company': sp.get('company') or '', 'is_owner': sp.get('is_owner') or 0, 340 'company': sp.get('company') or '', 'is_owner': sp.get('is_owner') or 0,
314 'published_at': sp.get('album_published_at'), 341 'published_at': sp.get('album_published_at'),
342 'provider_name': PROVIDER_YINYAN,
343 'crawler_source_data': _source_json({
344 'id': sp.get('album_id'),
345 'cover': sp.get('album_cover'),
346 'title': sp.get('album_title'),
347 'intro': sp.get('album_intro'),
348 'type': sp.get('album_type'),
349 'company_id': sp.get('company_id'),
350 'company': sp.get('company'),
351 'is_owner': sp.get('is_owner'),
352 'published_at': sp.get('album_published_at'),
353 }),
315 }]) 354 }])
316 355
317 song_uuid = str(uuid.uuid4()) 356 song_uuid = str(uuid.uuid4())
...@@ -332,6 +371,8 @@ def _process_netease(hk_row: dict, pr: dict, spider_conn, pg_cur, bucket, base_u ...@@ -332,6 +371,8 @@ def _process_netease(hk_row: dict, pr: dict, spider_conn, pg_cur, bucket, base_u
332 'platform_index_url': sp.get('platform_index_url') or f'https://music.163.com/#/song?id={song_id}', 371 'platform_index_url': sp.get('platform_index_url') or f'https://music.163.com/#/song?id={song_id}',
333 'published_at': sp.get('published_at') or hk_row.get('issue_time'), 372 'published_at': sp.get('published_at') or hk_row.get('issue_time'),
334 'singers_json': singers_json, 373 'singers_json': singers_json,
374 'provider_name': PROVIDER_YINYAN,
375 'crawler_source_data': _source_json(sp),
335 }]) 376 }])
336 # 查询实际 UUID(ON CONFLICT DO NOTHING 时使用已有 UUID) 377 # 查询实际 UUID(ON CONFLICT DO NOTHING 时使用已有 UUID)
337 pg_cur.execute('SELECT id FROM crawler_netease_songs WHERE platform_song_id = %s', (song_id,)) 378 pg_cur.execute('SELECT id FROM crawler_netease_songs WHERE platform_song_id = %s', (song_id,))
......
...@@ -25,14 +25,17 @@ def upsert_qq_singers(cur, singers: list[dict]) -> None: ...@@ -25,14 +25,17 @@ def upsert_qq_singers(cur, singers: list[dict]) -> None:
25 return 25 return
26 sql = """ 26 sql = """
27 INSERT INTO crawler_qqmusic_singers 27 INSERT INTO crawler_qqmusic_singers
28 (id, mid, name, avatar, sex, area, "index", intro, home_url, created_at, updated_at) 28 (id, mid, name, avatar, sex, area, "index", intro, home_url,
29 VALUES (%s, %s, %s, %s, %s::singer_sex, %s::singer_area, %s::singer_index, %s, %s, NOW(), NOW()) 29 created_at, updated_at, provider_name, crawler_source_data)
30 VALUES (%s, %s, %s, %s, %s::singer_sex, %s::singer_area, %s::singer_index, %s, %s,
31 NOW(), NOW(), %s, %s::json)
30 ON CONFLICT (id) DO NOTHING 32 ON CONFLICT (id) DO NOTHING
31 """ 33 """
32 rows = [( 34 rows = [(
33 s['id'], s['mid'], s['name'], s.get('avatar', ''), 35 s['id'], s['mid'], s['name'], s.get('avatar', ''),
34 s.get('sex') or 'U', s.get('area') or '其他', s.get('index') or '#', 36 s.get('sex') or 'U', s.get('area') or '其他', s.get('index') or '#',
35 s.get('intro'), s.get('home_url'), 37 s.get('intro'), s.get('home_url'),
38 s.get('provider_name'), s.get('crawler_source_data'),
36 ) for s in singers] 39 ) for s in singers]
37 cur.executemany(sql, rows) 40 cur.executemany(sql, rows)
38 41
...@@ -42,14 +45,16 @@ def upsert_qq_albums(cur, albums: list[dict]) -> None: ...@@ -42,14 +45,16 @@ def upsert_qq_albums(cur, albums: list[dict]) -> None:
42 return 45 return
43 sql = """ 46 sql = """
44 INSERT INTO crawler_qqmusic_albums 47 INSERT INTO crawler_qqmusic_albums
45 (id, mid, cover, title, intro, type, company_id, company, is_owner, published_at, created_at, updated_at) 48 (id, mid, cover, title, intro, type, company_id, company, is_owner, published_at,
46 VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), NOW()) 49 created_at, updated_at, provider_name, crawler_source_data)
50 VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), NOW(), %s, %s::json)
47 ON CONFLICT (id) DO NOTHING 51 ON CONFLICT (id) DO NOTHING
48 """ 52 """
49 rows = [( 53 rows = [(
50 a['id'], a.get('mid', ''), a.get('cover', ''), a.get('title', ''), 54 a['id'], a.get('mid', ''), a.get('cover', ''), a.get('title', ''),
51 a.get('intro'), a.get('type', ''), a.get('company_id', 0) or 0, 55 a.get('intro'), a.get('type', ''), a.get('company_id', 0) or 0,
52 a.get('company', ''), a.get('is_owner', 0) or 0, a.get('published_at'), 56 a.get('company', ''), a.get('is_owner', 0) or 0, a.get('published_at'),
57 a.get('provider_name'), a.get('crawler_source_data'),
53 ) for a in albums] 58 ) for a in albums]
54 cur.executemany(sql, rows) 59 cur.executemany(sql, rows)
55 60
...@@ -61,8 +66,9 @@ def upsert_qq_songs(cur, songs: list[dict]) -> None: ...@@ -61,8 +66,9 @@ def upsert_qq_songs(cur, songs: list[dict]) -> None:
61 INSERT INTO crawler_qqmusic_songs 66 INSERT INTO crawler_qqmusic_songs
62 (id, platform_song_id, mid, album_id, cover, title, name, duration, 67 (id, platform_song_id, mid, album_id, cover, title, name, duration,
63 lyric, composer_name, lyricist_name, url, lyric_url, 68 lyric, composer_name, lyricist_name, url, lyric_url,
64 platform_index_url, published_at, singers, status, created_at, updated_at) 69 platform_index_url, published_at, singers, status, created_at, updated_at,
65 VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s::jsonb, 0, NOW(), NOW()) 70 provider_name, crawler_source_data)
71 VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s::jsonb, 0, NOW(), NOW(), %s, %s::json)
66 ON CONFLICT (platform_song_id) DO NOTHING 72 ON CONFLICT (platform_song_id) DO NOTHING
67 """ 73 """
68 rows = [( 74 rows = [(
...@@ -71,6 +77,7 @@ def upsert_qq_songs(cur, songs: list[dict]) -> None: ...@@ -71,6 +77,7 @@ def upsert_qq_songs(cur, songs: list[dict]) -> None:
71 s.get('lyric'), s.get('composer_name'), s.get('lyricist_name'), 77 s.get('lyric'), s.get('composer_name'), s.get('lyricist_name'),
72 s.get('url', ''), s.get('lyric_url'), 78 s.get('url', ''), s.get('lyric_url'),
73 s.get('platform_index_url'), s.get('published_at'), s.get('singers_json', '[]'), 79 s.get('platform_index_url'), s.get('published_at'), s.get('singers_json', '[]'),
80 s.get('provider_name'), s.get('crawler_source_data'),
74 ) for s in songs] 81 ) for s in songs]
75 cur.executemany(sql, rows) 82 cur.executemany(sql, rows)
76 83
...@@ -195,14 +202,17 @@ def upsert_netease_singers(cur, singers: list[dict]) -> None: ...@@ -195,14 +202,17 @@ def upsert_netease_singers(cur, singers: list[dict]) -> None:
195 return 202 return
196 sql = """ 203 sql = """
197 INSERT INTO crawler_netease_singers 204 INSERT INTO crawler_netease_singers
198 (id, name, avatar, sex, area, "index", intro, home_url, created_at, updated_at) 205 (id, name, avatar, sex, area, "index", intro, home_url,
199 VALUES (%s, %s, %s, %s::netease_singer_sex, %s::netease_singer_area, %s::netease_singer_index, %s, %s, NOW(), NOW()) 206 created_at, updated_at, provider_name, crawler_source_data)
207 VALUES (%s, %s, %s, %s::netease_singer_sex, %s::netease_singer_area, %s::netease_singer_index, %s, %s,
208 NOW(), NOW(), %s, %s::json)
200 ON CONFLICT (id) DO NOTHING 209 ON CONFLICT (id) DO NOTHING
201 """ 210 """
202 rows = [( 211 rows = [(
203 s['id'], s['name'], s.get('avatar', ''), 212 s['id'], s['name'], s.get('avatar', ''),
204 s.get('sex') or 'U', s.get('area') or '其他', s.get('index') or '#', 213 s.get('sex') or 'U', s.get('area') or '其他', s.get('index') or '#',
205 s.get('intro'), s.get('home_url'), 214 s.get('intro'), s.get('home_url'),
215 s.get('provider_name'), s.get('crawler_source_data'),
206 ) for s in singers] 216 ) for s in singers]
207 cur.executemany(sql, rows) 217 cur.executemany(sql, rows)
208 218
...@@ -212,14 +222,16 @@ def upsert_netease_albums(cur, albums: list[dict]) -> None: ...@@ -212,14 +222,16 @@ def upsert_netease_albums(cur, albums: list[dict]) -> None:
212 return 222 return
213 sql = """ 223 sql = """
214 INSERT INTO crawler_netease_albums 224 INSERT INTO crawler_netease_albums
215 (id, cover, title, intro, type, company_id, company, is_owner, published_at, created_at, updated_at) 225 (id, cover, title, intro, type, company_id, company, is_owner, published_at,
216 VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), NOW()) 226 created_at, updated_at, provider_name, crawler_source_data)
227 VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), NOW(), %s, %s::json)
217 ON CONFLICT (id) DO NOTHING 228 ON CONFLICT (id) DO NOTHING
218 """ 229 """
219 rows = [( 230 rows = [(
220 a['id'], a.get('cover', ''), a.get('title', ''), a.get('intro'), 231 a['id'], a.get('cover', ''), a.get('title', ''), a.get('intro'),
221 a.get('type', ''), a.get('company_id', 0) or 0, 232 a.get('type', ''), a.get('company_id', 0) or 0,
222 a.get('company', ''), a.get('is_owner', 0) or 0, a.get('published_at'), 233 a.get('company', ''), a.get('is_owner', 0) or 0, a.get('published_at'),
234 a.get('provider_name'), a.get('crawler_source_data'),
223 ) for a in albums] 235 ) for a in albums]
224 cur.executemany(sql, rows) 236 cur.executemany(sql, rows)
225 237
...@@ -231,8 +243,9 @@ def upsert_netease_songs(cur, songs: list[dict]) -> None: ...@@ -231,8 +243,9 @@ def upsert_netease_songs(cur, songs: list[dict]) -> None:
231 INSERT INTO crawler_netease_songs 243 INSERT INTO crawler_netease_songs
232 (id, platform_song_id, album_id, cover, title, name, duration, 244 (id, platform_song_id, album_id, cover, title, name, duration,
233 lyric, composer_name, lyricist_name, url, lyric_url, 245 lyric, composer_name, lyricist_name, url, lyric_url,
234 platform_index_url, published_at, album, singers, status, created_at, updated_at) 246 platform_index_url, published_at, album, singers, status, created_at, updated_at,
235 VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s::json, %s::jsonb, 0, NOW(), NOW()) 247 provider_name, crawler_source_data)
248 VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s::json, %s::jsonb, 0, NOW(), NOW(), %s, %s::json)
236 ON CONFLICT (platform_song_id) DO NOTHING 249 ON CONFLICT (platform_song_id) DO NOTHING
237 """ 250 """
238 rows = [( 251 rows = [(
...@@ -241,6 +254,7 @@ def upsert_netease_songs(cur, songs: list[dict]) -> None: ...@@ -241,6 +254,7 @@ def upsert_netease_songs(cur, songs: list[dict]) -> None:
241 s.get('lyric'), s.get('composer_name'), s.get('lyricist_name'), 254 s.get('lyric'), s.get('composer_name'), s.get('lyricist_name'),
242 s.get('url', ''), s.get('lyric_url'), 255 s.get('url', ''), s.get('lyric_url'),
243 s.get('platform_index_url'), s.get('published_at'), s.get('album_json'), s.get('singers_json', '[]'), 256 s.get('platform_index_url'), s.get('published_at'), s.get('album_json'), s.get('singers_json', '[]'),
257 s.get('provider_name'), s.get('crawler_source_data'),
244 ) for s in songs] 258 ) for s in songs]
245 cur.executemany(sql, rows) 259 cur.executemany(sql, rows)
246 260
......
...@@ -3,8 +3,12 @@ from etl_to_crawler.writer import ( ...@@ -3,8 +3,12 @@ from etl_to_crawler.writer import (
3 upsert_kugou_albums, 3 upsert_kugou_albums,
4 upsert_kugou_singers, 4 upsert_kugou_singers,
5 upsert_kugou_songs, 5 upsert_kugou_songs,
6 upsert_netease_albums,
7 upsert_netease_singers,
6 upsert_netease_songs, 8 upsert_netease_songs,
9 upsert_qq_albums,
7 upsert_qq_singers, 10 upsert_qq_singers,
11 upsert_qq_songs,
8 upsert_qq_singer_songs, 12 upsert_qq_singer_songs,
9 upsert_yinyan_song_records, 13 upsert_yinyan_song_records,
10 ) 14 )
...@@ -75,7 +79,7 @@ def test_upsert_netease_songs_writes_album_json_column(): ...@@ -75,7 +79,7 @@ def test_upsert_netease_songs_writes_album_json_column():
75 sql, rows = cur.executemany.call_args[0] 79 sql, rows = cur.executemany.call_args[0]
76 assert 'album, singers' in sql 80 assert 'album, singers' in sql
77 assert '%s::json, %s::jsonb' in sql 81 assert '%s::json, %s::jsonb' in sql
78 assert rows[0][-2] == '{"id": 20, "title": "专辑"}' 82 assert rows[0][14] == '{"id": 20, "title": "专辑"}'
79 83
80 84
81 def test_upsert_kugou_songs_writes_provider_and_source_data(): 85 def test_upsert_kugou_songs_writes_provider_and_source_data():
...@@ -98,14 +102,14 @@ def test_upsert_kugou_songs_writes_provider_and_source_data(): ...@@ -98,14 +102,14 @@ def test_upsert_kugou_songs_writes_provider_and_source_data():
98 'platform_index_url': 'https://www.kugou.com/song/#hash=hash', 102 'platform_index_url': 'https://www.kugou.com/song/#hash=hash',
99 'published_at': '2020-01-01', 103 'published_at': '2020-01-01',
100 'singers_json': '[]', 104 'singers_json': '[]',
101 'provider_name': 'kugou', 105 'provider_name': 'yinyan',
102 'crawler_source_data': '{"id": 200}', 106 'crawler_source_data': '{"id": 200}',
103 }]) 107 }])
104 108
105 sql, rows = cur.executemany.call_args[0] 109 sql, rows = cur.executemany.call_args[0]
106 assert 'provider_name, crawler_source_data' in sql 110 assert 'provider_name, crawler_source_data' in sql
107 assert '%s::json' in sql 111 assert '%s::json' in sql
108 assert rows[0][-2:] == ('kugou', '{"id": 200}') 112 assert rows[0][-2:] == ('yinyan', '{"id": 200}')
109 113
110 114
111 def test_upsert_kugou_singers_writes_provider_and_source_data(): 115 def test_upsert_kugou_singers_writes_provider_and_source_data():
...@@ -119,13 +123,13 @@ def test_upsert_kugou_singers_writes_provider_and_source_data(): ...@@ -119,13 +123,13 @@ def test_upsert_kugou_singers_writes_provider_and_source_data():
119 'index': 'G', 123 'index': 'G',
120 'intro': '简介', 124 'intro': '简介',
121 'home_url': 'https://example.com/singer', 125 'home_url': 'https://example.com/singer',
122 'provider_name': 'kugou', 126 'provider_name': 'yinyan',
123 'crawler_source_data': '{"id": 1}', 127 'crawler_source_data': '{"id": 1}',
124 }]) 128 }])
125 129
126 sql, rows = cur.executemany.call_args[0] 130 sql, rows = cur.executemany.call_args[0]
127 assert 'provider_name, crawler_source_data' in sql 131 assert 'provider_name, crawler_source_data' in sql
128 assert rows[0][-2:] == ('kugou', '{"id": 1}') 132 assert rows[0][-2:] == ('yinyan', '{"id": 1}')
129 133
130 134
131 def test_upsert_kugou_albums_writes_provider_and_source_data(): 135 def test_upsert_kugou_albums_writes_provider_and_source_data():
...@@ -140,10 +144,136 @@ def test_upsert_kugou_albums_writes_provider_and_source_data(): ...@@ -140,10 +144,136 @@ def test_upsert_kugou_albums_writes_provider_and_source_data():
140 'company': '唱片公司', 144 'company': '唱片公司',
141 'is_owner': 1, 145 'is_owner': 1,
142 'published_at': '2020-01-01', 146 'published_at': '2020-01-01',
143 'provider_name': 'kugou', 147 'provider_name': 'yinyan',
144 'crawler_source_data': '{"id": 20}', 148 'crawler_source_data': '{"id": 20}',
145 }]) 149 }])
146 150
147 sql, rows = cur.executemany.call_args[0] 151 sql, rows = cur.executemany.call_args[0]
148 assert 'provider_name, crawler_source_data' in sql 152 assert 'provider_name, crawler_source_data' in sql
149 assert rows[0][-2:] == ('kugou', '{"id": 20}') 153 assert rows[0][-2:] == ('yinyan', '{"id": 20}')
154
155
156 def test_upsert_qq_entities_write_provider_and_source_data():
157 cur = MagicMock()
158 upsert_qq_singers(cur, [{
159 'id': 1,
160 'mid': 'singer-mid',
161 'name': '歌手',
162 'avatar': 'https://example.com/avatar.jpg',
163 'sex': 'M',
164 'area': '华语',
165 'index': 'G',
166 'intro': '简介',
167 'home_url': 'https://example.com/singer',
168 'provider_name': 'yinyan',
169 'crawler_source_data': '{"id": 1}',
170 }])
171 sql, rows = cur.executemany.call_args[0]
172 assert 'provider_name, crawler_source_data' in sql
173 assert rows[0][-2:] == ('yinyan', '{"id": 1}')
174
175 cur = MagicMock()
176 upsert_qq_albums(cur, [{
177 'id': 20,
178 'mid': 'album-mid',
179 'cover': 'https://example.com/album.jpg',
180 'title': '专辑',
181 'intro': '简介',
182 'type': '专辑类型',
183 'company_id': 7,
184 'company': '唱片公司',
185 'is_owner': 1,
186 'published_at': '2020-01-01',
187 'provider_name': 'yinyan',
188 'crawler_source_data': '{"id": 20}',
189 }])
190 sql, rows = cur.executemany.call_args[0]
191 assert 'provider_name, crawler_source_data' in sql
192 assert rows[0][-2:] == ('yinyan', '{"id": 20}')
193
194 cur = MagicMock()
195 upsert_qq_songs(cur, [{
196 'song_uuid': 'song-uuid',
197 'platform_song_id': 200,
198 'mid': 'song-mid',
199 'album_id': 20,
200 'cover': 'https://example.com/cover.jpg',
201 'title': '标题',
202 'name': '词曲名',
203 'duration': 180,
204 'lyric': '歌词',
205 'composer_name': '曲作者',
206 'lyricist_name': '词作者',
207 'url': 'https://example.com/audio.mp3',
208 'lyric_url': 'https://example.com/lyric.lrc',
209 'platform_index_url': 'https://y.qq.com/n/ryqq/songDetail/song-mid',
210 'published_at': '2020-01-01',
211 'singers_json': '[]',
212 'provider_name': 'yinyan',
213 'crawler_source_data': '{"id": 200}',
214 }])
215 sql, rows = cur.executemany.call_args[0]
216 assert 'provider_name, crawler_source_data' in sql
217 assert rows[0][-2:] == ('yinyan', '{"id": 200}')
218
219
220 def test_upsert_netease_entities_write_provider_and_source_data():
221 cur = MagicMock()
222 upsert_netease_singers(cur, [{
223 'id': 1,
224 'name': '歌手',
225 'avatar': 'https://example.com/avatar.jpg',
226 'sex': 'M',
227 'area': '华语',
228 'index': 'G',
229 'intro': '简介',
230 'home_url': 'https://example.com/singer',
231 'provider_name': 'yinyan',
232 'crawler_source_data': '{"id": 1}',
233 }])
234 sql, rows = cur.executemany.call_args[0]
235 assert 'provider_name, crawler_source_data' in sql
236 assert rows[0][-2:] == ('yinyan', '{"id": 1}')
237
238 cur = MagicMock()
239 upsert_netease_albums(cur, [{
240 'id': 20,
241 'cover': 'https://example.com/album.jpg',
242 'title': '专辑',
243 'intro': '简介',
244 'type': '专辑类型',
245 'company_id': 7,
246 'company': '唱片公司',
247 'is_owner': 1,
248 'published_at': '2020-01-01',
249 'provider_name': 'yinyan',
250 'crawler_source_data': '{"id": 20}',
251 }])
252 sql, rows = cur.executemany.call_args[0]
253 assert 'provider_name, crawler_source_data' in sql
254 assert rows[0][-2:] == ('yinyan', '{"id": 20}')
255
256 cur = MagicMock()
257 upsert_netease_songs(cur, [{
258 'song_uuid': 'song-uuid',
259 'platform_song_id': 300,
260 'album_id': 20,
261 'album_json': '{"id": 20, "title": "专辑"}',
262 'cover': 'https://example.com/cover.jpg',
263 'title': '标题',
264 'name': '词曲名',
265 'duration': 180,
266 'lyric': '歌词',
267 'composer_name': '曲作者',
268 'lyricist_name': '词作者',
269 'url': 'https://example.com/audio.mp3',
270 'lyric_url': 'https://example.com/lyric.lrc',
271 'platform_index_url': 'https://music.163.com/#/song?id=300',
272 'published_at': '2020-01-01',
273 'singers_json': '[]',
274 'provider_name': 'yinyan',
275 'crawler_source_data': '{"id": 300}',
276 }])
277 sql, rows = cur.executemany.call_args[0]
278 assert 'provider_name, crawler_source_data' in sql
279 assert rows[0][-2:] == ('yinyan', '{"id": 300}')
......