浙江新高一手把手教你 Python3 如何用 selenium 和 playwright 爬取 JS 动态渲染页面
作为浙江的新高一,即将面临严肃的住校生活。虽说杭城高中宿舍水平冠绝全国,但总是缺点什么————没有乐趣!
所以脑洞大开的我决定偷偷带 mp3 到宿舍,睡前洗澡来一曲,快乐似神仙♪٩(´ω`)و♪。然而巧妇难无米之炊,我手中的 mp3 无法连接网络,需要计算机传输音乐软件,因此需要下载大量音乐。酷狗、网易云我就不说了,下载音乐伤感情。于是我就找到了一个神仙音乐网站———— https://www.xxxx.com。这里有很多免费音乐可以免费下载,但是必须要等待 35s。如下

真伤感情!于是我无奈打开了开发者工具。发现我点开音乐播放键后,Network 瞬间传过来两个文件:

有 mp3 文件!访问一下,是可以下载的。
眉头一皱,计上心来————可不可以通过监听这个请求来截获音乐 URL,以此实现音乐下载?事不宜迟,动起来吧!顺便锻炼自己爬取 JS 动态渲染页面的能力。
可有人访问了网站会问:为什么说是 JS 渲染的呢?audio 节点的 src不是有音乐地址吗?我们不妨做个实验:
1 from lxml import etree 2 import requests 3 4 headers = { 5 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36" 6 } 7 8 resp = requests.get("https://www.xxxx.com/music/12764", headers=headers)#自己点击后把网站改过来,这里保护该网站 9 html = etree.HTML(resp.text) 10 location = html.xpath("//audio[@id='custom-audio-player']/@src") 11 12 print(location)
惊奇地发现结果是:
[]
什么都没有?为了验证我们还可以把resp.text保存到本地,看看html:
1 <!doctype html> 2 <html lang="zh-CN"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="ie=edge"> 6 <meta http-equiv="Cache-Control" content="no-transform"/> 7 <meta http-equiv="Cache-Control" content="no-siteapp"/> 8 <meta name="referrer" content="same-origin"> 9 <meta name="viewport" content="width=device-width, initial-scale=1"> 10 <meta name="keywords" content="大海航行靠舵手-中国人民解放军军乐团.mp3, 大海航行靠舵手-中国人民解放军军乐团 在线播放, 大海航行靠舵手-中国人民解放军军乐团 MP3下载, 大海航行靠舵手-中国人民解放军军乐团 歌词下载, 免费下载"> 11 <meta name="description" 12 content="《大海航行靠舵手》是 中国人民解放军军乐团 演唱的歌曲,歌曲宝提供《大海航行靠舵手-中国人民解放军军乐团.mp3》歌曲在线免费下载与播放,大海航行靠舵手 歌词下载,免费下载MP3音乐就上歌曲宝,曲库完整,更新迅速,试听流畅,支持高品质、无损音质~"> 13 <meta name="mobile-web-app-capable" content="yes"> 14 <meta name="apple-mobile-web-app-status-bar-style" content="black"> 15 <meta name="apple-mobile-web-app-title" content="音乐MP3免费下载网站"> 16 <meta name="application-name" content="音乐MP3免费下载网站"> 17 <meta name="format-detection" content="telephone=no"> 18 <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon"/> 19 20 <title>大海航行靠舵手-中国人民解放军军乐团-MP3在线免费播放与下载 - 歌曲宝MP3音乐下载</title> 21 22 <link rel="stylesheet" href="/static/css/loading.css"> 23 <link rel="stylesheet" href="//code.bdstatic.com/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css"/> 24 <link rel="stylesheet" href="//code.bdstatic.com/npm/font-awesome@4.7.0/css/font-awesome.min.css"/> 25 <link rel="stylesheet" href="//code.bdstatic.com/npm/jquery-confirm@3.3.4/dist/jquery-confirm.min.css"/> 26 27 28 <script src="//code.bdstatic.com/npm/jquery@3.7.1/dist/jquery.min.js"></script> 29 30 31 <script defer src="//code.bdstatic.com/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script> 32 <script defer src="//code.bdstatic.com/npm/bootstrap@4.6.1/dist/js/bootstrap.min.js"></script> 33 <script defer src="//code.bdstatic.com/npm/jquery-confirm@3.3.4/dist/jquery-confirm.min.js"></script> 34 <script type="text/javascript"> 35 window.user = JSON.parse('{\u0022isLogin\u0022:false,\u0022isVip\u0022:false,\u0022config\u0022:[]}'); 36 </script> 37 </head> 38 <body> 39 40 41 <div id="loader-wrapper"> 42 <div class="loader"></div> 43 </div> 44 45 <script> 46 document.addEventListener('DOMContentLoaded', function() { 47 document.body.classList.add('loaded'); 48 49 setTimeout(function() { 50 const loaderWrapper = document.getElementById('loader-wrapper'); 51 if (loaderWrapper) { 52 loaderWrapper.remove(); 53 } 54 }, 500); 55 }); 56 </script> 57 58 59 <div class="container" style="max-width: 900px"> 60 <nav class="navbar navbar-expand-md navbar-light px-0 mb-1 font-weight-normal"> 61 62 <a class="navbar-brand d-flex align-items-center" style="max-width: calc(100% - 135px);" href="/"> 63 <img src="/static/img/logo.png" width="50" height="50" class="mr-2 flex-shrink-0" alt="logo"> 64 65 <div class="d-flex flex-column justify-content-center" style="min-width: 0;"> 66 <span class="font-weight-bolder text-truncate d-block" style="font-size: 1.15rem; color: #50514F; line-height: 1.3;"> 67 歌曲宝 68 </span> 69 <span class="domain-text-enhanced mt-1"> 70 Gequbao.com 71 </span> 72 </div> 73 </a> 74 75 <div class="collapse navbar-collapse order-3 order-md-2" id="navbarSupportedContent"> 76 <ul class="navbar-nav mr-auto"> 77 <li class="nav-item"> 78 <a class="nav-link" href="/hot-news">资讯</a> 79 </li> 80 <li class="nav-item dropdown"> 81 <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" 82 aria-haspopup="true" aria-expanded="false"> 83 排行榜 84 </a> 85 <div class="dropdown-menu" aria-labelledby="navbarDropdown" style="border-top: 0.15rem solid #1e9fff;"> 86 <a class="dropdown-item" href="/top/week-search"><i class="fa fa-trophy" style="color: #ff6347"></i> 周搜索榜</a> 87 <a class="dropdown-item" href="/top/week-download"><i class="fa fa-trophy" style="color: #ff6347"></i> 周下载榜</a> 88 </div> 89 </li> 90 <li class="nav-item active"> 91 <a class="nav-link reddot" href="/hot-music">推荐</a> 92 </li> 93 </ul> 94 </div> 95 96 <div class="d-flex align-items-center ml-auto flex-shrink-0 order-2 order-md-3"> 97 98 <ul class="navbar-nav flex-row align-items-center mr-2 mr-md-0"> 99 <li class="nav-item"> 100 <a class="btn btn-outline-dark btn-sm custom-login-btn" href="/member/auth/login"> 101 <i class="fa fa-user-circle"></i> 102 <span class="d-none d-md-inline ml-1">登录 / 注册</span> 103 <span class="d-md-none ml-1">登录</span> 104 </a> 105 </li> 106 </ul> 107 108 <button class="navbar-toggler border-0 px-1" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" 109 aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> 110 <span class="navbar-toggler-icon"></span> 111 </button> 112 </div> 113 </nav> 114 115 <div class="row mb-2"> 116 </div> 117 118 119 <div class="card shadow-sm mb-1 custom-player-wrapper border-0"> 120 <div class="card-body py-3 px-3 px-md-4"> 121 122 123 <div class="custom-player-layout mb-2 pb-3"> 124 125 126 <div class="player-cover-box" id="player-toggle-btn"> 127 <img src="http://img1.kuwo.cn/star/albumcover/500/56/49/1656485620.jpg" alt="cover" class="player-cover-img"> 128 <div class="player-play-overlay"> 129 <i class="fa fa-play" id="player-icon" style="margin-left: 4px;"></i> 130 </div> 131 </div> 132 133 134 <div class="player-text-info text-left"> 135 <h1 class="song-title-styled mb-1 d-flex align-items-center justify-content-start"> 136 <span class="text-truncate" title="大海航行靠舵手-中国人民解放军军乐团">大海航行靠舵手</span> 137 </h1> 138 <div class="song-author-styled mb-1 text-truncate"> 139 中国人民解放军军乐团 140 </div> 141 <div class="current-lyric-styled mb-1 text-truncate" id="current-lyric-line" 142 style="min-height: 22px;"> 143 暂无歌词信息 144 </div> 145 </div> 146 147 148 <div class="player-controls-area"> 149 150 <div class="d-flex align-items-center mb-2 w-100" id="progress-container"> 151 <span id="time-current" class="text-muted small mr-3 font-weight-bold" 152 style="font-family: monospace;">00:00</span> 153 154 <div class="player-progress-bar flex-grow-1 position-relative" id="progress-bar-bg"> 155 <div class="player-progress-buffered" id="progress-bar-buffered" style="width: 0;"></div> 156 <div class="player-progress-fill" id="progress-bar-fill" style="width: 0;"></div> 157 <div class="player-progress-handle" id="progress-bar-handle"></div> 158 </div> 159 160 <span id="time-total" class="text-muted small ml-3 font-weight-bold" 161 style="font-family: monospace;">01:44</span> 162 163 164 <div 165 class="volume-control-wrapper position-relative d-flex align-items-center ml-2 ml-md-3 flex-shrink-0"> 166 167 168 <span 169 class="text-secondary p-0 border-0 shadow-none d-flex align-items-center cursor-pointer"> 170 <i class="fa fa-volume-up" id="volume-icon"></i> 171 </span> 172 173 174 <div class="volume-slider-container"> 175 <div class="player-volume-bar" id="volume-bar-bg"> 176 <div class="player-volume-fill" id="volume-bar-fill" style="height: 100%;"></div> 177 <div class="player-volume-handle" id="volume-bar-handle"></div> 178 </div> 179 </div> 180 </div> 181 182 <button id="btn-loop-mode" 183 class="btn btn-link text-secondary p-0 border-0 shadow-none ml-1 d-flex align-items-center active-loop" 184 title="关闭单曲循环"> 185 <i class="fa fa-retweet"></i> 186 </button> 187 188 <button id="btn-playback-rate" 189 class="btn btn-link font-smaller text-secondary p-0 border-0 shadow-none ml-1 font-weight-bold" 190 style="text-decoration: none;" title="切换倍速"> 191 1.0x 192 </button> 193 </div> 194 195 196 <div class="d-flex justify-content-start align-items-center flex-nowrap action-btn-group"> 197 <button 198 class="btn btn-primary-blue rounded-pill px-4 py-1 mr-2 mr-md-3 mb-1 d-flex align-items-center shadow-sm flex-shrink-1" 199 id="btn-main-play"> 200 <i class="fa fa-play-circle mr-1" style="font-size: 1.1rem;"></i> 201 <span id="main-play-text" class="font-weight-bold text-nowrap">播放</span> 202 </button> 203 <button id="music-favorite-btn" 204 class="btn btn-ui-white rounded-pill px-4 py-1 mr-2 mr-md-3 mb-1 font-weight-bold shadow-sm flex-shrink-1 text-nowrap" 205 data-status="0"> 206 <i class="fa fa-heart mr-1"></i> 收藏 207 </button> 208 <button id="music-add-playlist-btn" 209 class="btn btn-ui-white rounded-pill px-4 py-1 mr-2 mr-md-3 mb-1 font-weight-bold shadow-sm flex-shrink-1 text-nowrap"> 210 <i class="fa fa-plus-square mr-1"></i> 歌单 211 </button> 212 </div> 213 </div> 214 </div> 215 216 217 <audio id="custom-audio-player" 218 preload="metadata"></audio> 219 220 <div class="quote-warning mb-2" style="font-size: 0.85rem"> 221 <i class="fa fa-info-circle"></i> 222 如无法下载或有错误,请刷新重试或点此 <a id="music-feedback-btn" style="text-decoration: underline;" 223 href="javascript:void(0)">反馈</a> 给我们 224 </div> 225 226 <div class="input-group mb-1 overflow-hidden"> 227 <div class="input-group-prepend"> 228 <i class="fa fa-music input-group-text"></i> 229 </div> 230 <span class="form-control bg-light nowrap-hidden"> 231 大海航行靠舵手-中国人民解放军军乐团.mp3 232 </span> 233 <div class="input-group-append"> 234 <a id="btn-download-mp3" class="btn btn-primary" target="_blank"> 235 <i class="fa fa-download"></i> 236 下载歌曲</a> 237 </div> 238 </div> 239 <div class="input-group overflow-hidden"> 240 <div class="input-group-prepend"> 241 <i class="fa fa-file-text-o input-group-text"></i> 242 </div> 243 <span class="form-control bg-light nowrap-hidden"> 244 [ti:大海航行靠舵手] 245 [ar:中国人民解放军军乐团] 246 [al:... 247 </span> 248 <div class="input-group-append"> 249 <a id="btn-download-lrc" class="btn btn-primary"><i class="fa fa-download"></i> 下载歌词</a> 250 </div> 251 </div> 252 <div class="mt-1"> 253 <div class="content-lrc mt-1" id="content-lrc">[ti:大海航行靠舵手]<br /> 254 [ar:中国人民解放军军乐团]<br /> 255 [al:大海航行靠舵手-中国人民解放军军乐团]<br /> 256 [00:00.00]该歌曲暂无歌词</div> 257 <div class="expand" data-status="up">展开</div> 258 </div> 259 </div> 260 <div class="card-footer bg-white" id="download-log"> 261 <div style="overflow:hidden;height:1rem;line-height: 1rem;"> 262 <ul class="list-group"> 263 <li class="text-truncate"> 264 <a href="/music/25269030" title="泸沽湖 (cover: 麻园诗人) - 王梓" style="font-size: small" 265 target="_blank"> 266 <span class="text-muted"> 267 网友刚刚下载了 268 </span> 269 泸沽湖 (cover: 麻园诗人) - 王梓 270 </a> 271 </li> 272 <li class="text-truncate"> 273 <a href="/music/5055964" title="Look What God Gave Her - Thomas Rhett" style="font-size: small" 274 target="_blank"> 275 <span class="text-muted"> 276 网友刚刚下载了 277 </span> 278 Look What God Gave Her - Thomas Rhett 279 </a> 280 </li> 281 <li class="text-truncate"> 282 <a href="/music/30618" title="谁 - 曲肖冰" style="font-size: small" 283 target="_blank"> 284 <span class="text-muted"> 285 网友刚刚下载了 286 </span> 287 谁 - 曲肖冰 288 </a> 289 </li> 290 <li class="text-truncate"> 291 <a href="/music/48961280" title="岸边客 - 夏子航(2603)" style="font-size: small" 292 target="_blank"> 293 <span class="text-muted"> 294 网友刚刚下载了 295 </span> 296 岸边客 - 夏子航(2603) 297 </a> 298 </li> 299 <li class="text-truncate"> 300 <a href="/music/777875" title="谁来爱我 - 容祖儿" style="font-size: small" 301 target="_blank"> 302 <span class="text-muted"> 303 网友刚刚下载了 304 </span> 305 谁来爱我 - 容祖儿 306 </a> 307 </li> 308 <li class="text-truncate"> 309 <a href="/music/38245" title="Mystery of Love - Sufjan Stevens" style="font-size: small" 310 target="_blank"> 311 <span class="text-muted"> 312 网友刚刚下载了 313 </span> 314 Mystery of Love - Sufjan Stevens 315 </a> 316 </li> 317 <li class="text-truncate"> 318 <a href="/music/34262510" title="잘 알지도 못하면서 - 清冷音" style="font-size: small" 319 target="_blank"> 320 <span class="text-muted"> 321 网友刚刚下载了 322 </span> 323 잘 알지도 못하면서 - 清冷音 324 </a> 325 </li> 326 <li class="text-truncate"> 327 <a href="/music/39031" title="天下有情人 - 唐艺" style="font-size: small" 328 target="_blank"> 329 <span class="text-muted"> 330 网友刚刚下载了 331 </span> 332 天下有情人 - 唐艺 333 </a> 334 </li> 335 <li class="text-truncate"> 336 <a href="/music/65751" title="光阴的故事 - 罗大佑" style="font-size: small" 337 target="_blank"> 338 <span class="text-muted"> 339 网友刚刚下载了 340 </span> 341 光阴的故事 - 罗大佑 342 </a> 343 </li> 344 <li class="text-truncate"> 345 <a href="/music/3085774" title="工资不够花(DJ猎人版) - 落笔" style="font-size: small" 346 target="_blank"> 347 <span class="text-muted"> 348 网友刚刚下载了 349 </span> 350 工资不够花(DJ猎人版) - 落笔 351 </a> 352 </li> 353 <li class="text-truncate"> 354 <a href="/music/2137" title="枕着光的她 - 任素汐" style="font-size: small" 355 target="_blank"> 356 <span class="text-muted"> 357 网友刚刚下载了 358 </span> 359 枕着光的她 - 任素汐 360 </a> 361 </li> 362 <li class="text-truncate"> 363 <a href="/music/63325502" title="迷人的危险 - 姚晓棠" style="font-size: small" 364 target="_blank"> 365 <span class="text-muted"> 366 网友刚刚下载了 367 </span> 368 迷人的危险 - 姚晓棠 369 </a> 370 </li> 371 <li class="text-truncate"> 372 <a href="/music/595" title="春天里 - 汪峰" style="font-size: small" 373 target="_blank"> 374 <span class="text-muted"> 375 网友刚刚下载了 376 </span> 377 春天里 - 汪峰 378 </a> 379 </li> 380 <li class="text-truncate"> 381 <a href="/music/9653" title="光年之外 - G.E.M. 邓紫棋" style="font-size: small" 382 target="_blank"> 383 <span class="text-muted"> 384 网友刚刚下载了 385 </span> 386 光年之外 - G.E.M. 邓紫棋 387 </a> 388 </li> 389 <li class="text-truncate"> 390 <a href="/music/753716" title="风起流年 - 周深" style="font-size: small" 391 target="_blank"> 392 <span class="text-muted"> 393 网友刚刚下载了 394 </span> 395 风起流年 - 周深 396 </a> 397 </li> 398 <li class="text-truncate"> 399 <a href="/music/24374" title="遗失的美好 - 张韶涵" style="font-size: small" 400 target="_blank"> 401 <span class="text-muted"> 402 网友刚刚下载了 403 </span> 404 遗失的美好 - 张韶涵 405 </a> 406 </li> 407 </ul> 408 </div> 409 </div> 410 <script type="text/javascript"> 411 $(document).ready(function () { 412 autoScroll('#download-log'); 413 }); 414 </script> 415 </div> 416 417 418 419 <div class="modal fade" id="addToPlaylistModal" tabindex="-1" role="dialog" aria-hidden="true"> 420 <div class="modal-dialog modal-sm modal-dialog-centered" role="document"> 421 <div class="modal-content modal-content-custom"> 422 <div class="modal-header border-bottom-0 pt-3 px-3"> 423 <h6 class="modal-title font-weight-bold">加入歌单</h6> 424 <button type="button" class="close" data-dismiss="modal" aria-label="关闭"> 425 <span aria-hidden="true">×</span> 426 </button> 427 </div> 428 <div class="modal-body px-3 py-2"> 429 <form id="form-add-to-playlist"> 430 <input type="hidden" name="music_id" id="target-add-music-id"> 431 <div class="form-group mb-0"> 432 <select class="form-control form-control-radius" name="playlist_id" 433 id="select-playlist-list" 434 required> 435 <option value="">正在加载歌单...</option> 436 </select> 437 </div> 438 </form> 439 </div> 440 <div class="modal-footer border-top-0 pb-3 px-3 d-flex justify-content-between"> 441 <button type="button" class="btn btn-sm btn-outline-primary rounded-pill flex-grow-1 mr-2" 442 data-toggle="modal" 443 data-target="#createPlaylistModal" data-dismiss="modal"> 444 新建歌单 445 </button> 446 <button type="button" class="btn btn-sm btn-primary rounded-pill flex-grow-1 ml-2" 447 id="btn-confirm-add-to-playlist"> 448 确认加入 449 </button> 450 </div> 451 </div> 452 </div> 453 </div> 454 <div class="modal fade" id="createPlaylistModal" tabindex="-1" role="dialog" aria-hidden="true"> 455 <div class="modal-dialog modal-sm modal-dialog-centered" role="document"> 456 <div class="modal-content modal-content-custom"> 457 <div class="modal-header border-bottom-0 pt-3 px-3"> 458 <h6 class="modal-title font-weight-bold">新建歌单</h6> 459 <button type="button" class="close" data-dismiss="modal" aria-label="关闭"> 460 <span aria-hidden="true">×</span> 461 </button> 462 </div> 463 <div class="modal-body px-3 py-2"> 464 <form id="form-create-playlist"> 465 <div class="form-group"> 466 <label class="text-muted small font-weight-bold">歌单名称</label> 467 <input type="text" class="form-control form-control-radius" id="new-playlist-title" 468 name="title" required 469 placeholder="例如:学习专用白噪音、华语经典..." maxlength="30"> 470 </div> 471 <div class="form-group mb-0"> 472 <label class="text-muted small font-weight-bold">简介 (可选)</label> 473 <textarea class="form-control form-control-radius" id="new-playlist-desc" 474 name="description" rows="2" 475 placeholder="简单描述一下这个歌单..." maxlength="100" 476 style="resize: none;"></textarea> 477 </div> 478 </form> 479 </div> 480 <div class="modal-footer border-bottom-0 pb-3 px-3 d-flex justify-content-between"> 481 <button type="button" class="btn btn-light rounded-pill px-4" data-dismiss="modal" 482 data-toggle="modal" 483 data-target="#addToPlaylistModal">返回 484 </button> 485 <button type="button" class="btn btn-primary rounded-pill px-4" id="btn-confirm-create-playlist"> 486 确认新建 487 </button> 488 </div> 489 </div> 490 </div> 491 </div> 492 493 494 495 <div class="card shadow-sm mb-2"> 496 <div class="card-body"> 497 <h6 class="card-title"> 498 <a href="/hot-music" class="text-red font-weight-bolder"> 499 <i class="fa fa-star text-orange mr-1"></i> 热门推荐 500 </a> 501 <small> 502 <a class="float-right text-muted" href="/hot-music">更多 <i class="fa fa-angle-right"></i></a> 503 </small> 504 </h6> 505 <div class="list-group list-group-flush"> 506 <a href="/music/7235" title="雨爱-杨丞琳" 507 class="list-group-item list-group-item-action px-0 text-primary text-truncate d-flex justify-content-between align-items-center"> 508 <span class="ml-1 text-truncate"> 509 <span style="color: #336699" class="font-weight-bolder">雨爱</span> 510 - 511 <small class="text-muted">杨丞琳</small> 512 </span> 513 <i class="fa fa-play-circle" style="font-size: larger"></i> 514 </a> 515 <a href="/music/91400" title="小半-陈粒" 516 class="list-group-item list-group-item-action px-0 text-primary text-truncate d-flex justify-content-between align-items-center"> 517 <span class="ml-1 text-truncate"> 518 <span style="color: #CCCC00" class="font-weight-bolder">小半</span> 519 - 520 <small class="text-muted">陈粒</small> 521 </span> 522 <i class="fa fa-play-circle" style="font-size: larger"></i> 523 </a> 524 <a href="/music/60432100" title="海屿你-白慕寒" 525 class="list-group-item list-group-item-action px-0 text-primary text-truncate d-flex justify-content-between align-items-center"> 526 <span class="ml-1 text-truncate"> 527 <span style="color: #009999" class="font-weight-bolder">海屿你</span> 528 - 529 <small class="text-muted">白慕寒</small> 530 </span> 531 <i class="fa fa-play-circle" style="font-size: larger"></i> 532 </a> 533 <a href="/music/8117" title="我怀念的-孙燕姿" 534 class="list-group-item list-group-item-action px-0 text-primary text-truncate d-flex justify-content-between align-items-center"> 535 <span class="ml-1 text-truncate"> 536 <span style="color: #FF6600" class="font-weight-bolder">我怀念的</span> 537 - 538 <small class="text-muted">孙燕姿</small> 539 </span> 540 <i class="fa fa-play-circle" style="font-size: larger"></i> 541 </a> 542 <a href="/music/4198" title="一路向北-周杰伦" 543 class="list-group-item list-group-item-action px-0 text-primary text-truncate d-flex justify-content-between align-items-center"> 544 <span class="ml-1 text-truncate"> 545 <span style="color: #CC0066" class="font-weight-bolder">一路向北</span> 546 - 547 <small class="text-muted">周杰伦</small> 548 </span> 549 <i class="fa fa-play-circle" style="font-size: larger"></i> 550 </a> 551 <a href="/music/4200" title="枫-周杰伦" 552 class="list-group-item list-group-item-action px-0 text-primary text-truncate d-flex justify-content-between align-items-center"> 553 <span class="ml-1 text-truncate"> 554 <span style="color: #FF6666" class="font-weight-bolder">枫</span> 555 - 556 <small class="text-muted">周杰伦</small> 557 </span> 558 <i class="fa fa-play-circle" style="font-size: larger"></i> 559 </a> 560 <a href="/music/62551762" title="岁月如笔写春秋-郭少杰" 561 class="list-group-item list-group-item-action px-0 text-primary text-truncate d-flex justify-content-between align-items-center"> 562 <span class="ml-1 text-truncate"> 563 <span style="color: #339999" class="font-weight-bolder">岁月如笔写春秋</span> 564 - 565 <small class="text-muted">郭少杰</small> 566 </span> 567 <i class="fa fa-play-circle" style="font-size: larger"></i> 568 </a> 569 <a href="/music/4197" title="花海-周杰伦" 570 class="list-group-item list-group-item-action px-0 text-primary text-truncate d-flex justify-content-between align-items-center"> 571 <span class="ml-1 text-truncate"> 572 <span style="color: #CC3399" class="font-weight-bolder">花海</span> 573 - 574 <small class="text-muted">周杰伦</small> 575 </span> 576 <i class="fa fa-play-circle" style="font-size: larger"></i> 577 </a> 578 <a href="/music/6441" title="盛夏的果实-莫文蔚" 579 class="list-group-item list-group-item-action px-0 text-primary text-truncate d-flex justify-content-between align-items-center"> 580 <span class="ml-1 text-truncate"> 581 <span style="color: #FF33CC" class="font-weight-bolder">盛夏的果实</span> 582 - 583 <small class="text-muted">莫文蔚</small> 584 </span> 585 <i class="fa fa-play-circle" style="font-size: larger"></i> 586 </a> 587 <a href="/music/11285" title="后来-刘若英" 588 class="list-group-item list-group-item-action px-0 text-primary text-truncate d-flex justify-content-between align-items-center"> 589 <span class="ml-1 text-truncate"> 590 <span style="color: #0099CC" class="font-weight-bolder">后来</span> 591 - 592 <small class="text-muted">刘若英</small> 593 </span> 594 <i class="fa fa-play-circle" style="font-size: larger"></i> 595 </a> 596 </div> 597 </div> 598 </div> 599 600 601 <div class="row mb-2"> 602 </div> 603 604 </div> 605 606 607 <footer class="footer-modern text-center"> 608 <div class="container"> 609 <div class="mb-3"> 610 <a href="javascript:void(0)" class="mx-2" data-toggle="modal" data-target="#feedbackModal" id="feedbackBtn" 611 rel="nofollow"> 612 <i class="fa fa-commenting-o mr-1"></i>反馈建议 613 </a> 614 615 <span class="text-muted opacity-50">|</span> 616 <a href="javascript:void(0)" class="get-setting-btn mx-2" rel="nofollow" data-key="mzsm_mark"> 617 <i class="fa fa-shield mr-1"></i>免责声明 618 </a> 619 620 621 <span class="text-muted opacity-50">|</span> 622 <a href="/friendly-links" class="mx-2"> 623 <i class="fa fa-link mr-1"></i>友情链接 624 </a> 625 626 </div> 627 628 629 <div class="text-muted small"> 630 <a href="/" class="text-decoration-none"> 631 歌曲宝 - gequbao.com © 2026 632 </a> 633 634 <span class="mx-1 opacity-50">|</span> 635 636 <a href="javascript:void(0)" class="text-decoration-none js-add-bookmark" title="加入书签"> 637 <i class="fa fa-star text-warning"></i> 收藏本站 638 </a> 639 </div> 640 </div> 641 642 643 <span style="display: none"> 644 645 <script> var _mtj = _mtj || []; (function () { var mtj = document.createElement("script"); mtj.src = "https://node31.aizhantj.com:21233/tjjs/?k=t8h1bavogbi"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(mtj, s); })(); </script> 646 </span> 647 </footer> 648 649 <div class="modal fade" id="feedbackModal" tabindex="-1" role="dialog" aria-labelledby="feedbackModalLabel" 650 aria-hidden="true"> 651 <div class="modal-dialog modal-dialog-centered" role="document"> 652 <div class="modal-content border-0 shadow-lg"> 653 <div class="modal-header border-0 pb-0"> 654 <h5 class="modal-title font-weight-bold text-primary" id="feedbackModalLabel"> 655 <i class="fa fa-commenting-o mr-2"></i>意见反馈 656 </h5> 657 <button type="button" id="feedbackModalClose" class="close text-muted" data-dismiss="modal" 658 aria-label="关闭"> 659 <span aria-hidden="true">×</span> 660 </button> 661 </div> 662 663 <div class="modal-body p-4"> 664 <p class="text-muted font-smaller mb-4">您的建议是我们进步的动力<b class="text-muted">(如需提交截图信息请登录后进行提交)</b>。</p> 665 666 <div class="form-group mb-3"> 667 <label for="feedbackType" class="font-weight-bold small text-secondary">反馈类型</label> 668 <div class="input-group"> 669 <div class="input-group-prepend"> 670 <span class="input-group-text bg-light border-right-0"><i 671 class="fa fa-list-ul text-secondary"></i></span> 672 </div> 673 <select class="form-control border-left-0 focus-shadow" id="feedbackType" required> 674 <option value="" selected disabled>请选择反馈类型</option> 675 <option value="function">功能建议(网站功能改进建议)</option> 676 <option value="music">歌曲收录(请提供歌手和歌名)</option> 677 <option value="other">其他</option> 678 </select> 679 </div> 680 </div> 681 682 <div class="form-group mb-3" id="link-group" style="display: none"> 683 <label for="link" class="font-weight-bold small text-secondary">错误链接</label> 684 <div class="input-group"> 685 <div class="input-group-prepend"> 686 <span class="input-group-text bg-light border-right-0"><i 687 class="fa fa-link text-secondary"></i></span> 688 </div> 689 <input type="text" class="form-control border-left-0 focus-shadow" placeholder="请输入出错的网址链接" 690 id="link"> 691 </div> 692 </div> 693 694 <div class="form-group mb-3"> 695 <label for="feedbackContent" class="font-weight-bold small text-secondary">反馈内容</label> 696 <div class="input-group"> 697 <div class="input-group-prepend"> 698 <span class="input-group-text bg-light border-right-0" 699 style="align-items: flex-start; padding-top: 0.7rem;"><i 700 class="fa fa-pencil text-secondary"></i></span> 701 </div> 702 <textarea class="form-control border-left-0 focus-shadow" id="feedbackContent" rows="4" required 703 placeholder="请详细描述您遇到的问题或建议..." 704 style="resize: none;"></textarea> 705 </div> 706 </div> 707 708 <div class="form-group mb-4"> 709 <label for="feedbackContact" class="font-weight-bold small text-secondary">联系邮箱</label> 710 <div class="input-group"> 711 <div class="input-group-prepend"> 712 <span class="input-group-text bg-light border-right-0"><i 713 class="fa fa-envelope-o text-secondary"></i></span> 714 </div> 715 <input type="email" class="form-control border-left-0 focus-shadow" id="feedbackContact" 716 placeholder="example@email.com" required> 717 </div> 718 <small class="form-text text-muted">我们将通过邮件向您同步处理结果。</small> 719 </div> 720 721 <div class="d-flex justify-content-between align-items-center mt-2"> 722 <button type="button" id="feedbackResetFormBtn" class="btn btn-link text-muted pl-0" 723 style="text-decoration: none;"> 724 <i class="fa fa-refresh mr-1"></i>清空 725 </button> 726 <button type="button" id="feedbackSubmitFormBtn" class="btn btn-primary px-4 shadow-sm" 727 style="border-radius: 2rem;"> 728 <i class="fa fa-paper-plane mr-1"></i> 提交反馈 729 </button> 730 </div> 731 </div> 732 </div> 733 </div> 734 </div> 735 736 737 738 <script type="text/javascript"> 739 window.appData = JSON.parse('{\u0022mp3_id\u0022:435890,\u0022play_id\u0022:\u0022eyJpdiI6IklwNG9mUDl2MHVldUJ4bFE4SW84V3c9PSIsInZhbHVlIjoiUitzRFBzUU51RnBPcm82cG1WOFQrMFB6aFRSR1BwZDJpNnc2VElOTlQ5RDd3VkNVcDQrMnRVRlpObnUzYWpWRG5Yc3h1TWRsaGxJYUpYUDNoNmIyV0E9PSIsIm1hYyI6IjBjMTg4ZDhjZGNlNDkzYTdlNjQ1NDgwMmU3OTFjZGFkZTRmZDU0Yjc3MTViOGFjZTM5OWQxMThjYTczMTk1ZjEiLCJ0YWciOiIifQ==\u0022,\u0022mp3_title\u0022:\u0022\\u5927\\u6d77\\u822a\\u884c\\u9760\\u8235\\u624b\u0022,\u0022mp3_author\u0022:\u0022\\u4e2d\\u56fd\\u4eba\\u6c11\\u89e3\\u653e\\u519b\\u519b\\u4e50\\u56e2\u0022,\u0022mp3_type\u0022:0,\u0022mp3_cover\u0022:\u0022http:\\\/\\\/img1.kuwo.cn\\\/star\\\/albumcover\\\/500\\\/56\\\/49\\\/1656485620.jpg\u0022,\u0022mp3_duration\u0022:\u002201:44\u0022,\u0022mp3_extra_urls\u0022:[{\u0022id\u0022:1985721,\u0022share_link\u0022:\u0022aHR0cHM6Ly9wYW4ucXVhcmsuY24vcy8wNjhjNjQ1ZmRjYmI=\u0022,\u0022pwd\u0022:null,\u0022type\u0022:\u0022\\u5938\\u514b\\u7f51\\u76d8\u0022,\u0022color\u0022:\u0022#118AB2\u0022,\u0022compel_wap\u0022:false,\u0022icon\u0022:\u0022\\\/static\\\/img\\\/quark-icon.png\u0022}],\u0022ap_preload\u0022:\u0022metadata\u0022,\u0022is_robot\u0022:false,\u0022extra_url_compel\u0022:false,\u0022lrc_is_empty\u0022:true,\u0022ad_type\u0022:1,\u0022extra_recommend_wap_url\u0022:\u0022aHR0cHM6Ly9wYW4ucXVhcmsuY24vcy8wNjhjNjQ1ZmRjYmI=\u0022,\u0022vip_down_url\u0022:true}'); 740 </script> 741 742 <link rel="preload" as="style" href="https://www.gequbao.com/build/assets/app-C-B9Q8LU.css" /><link rel="modulepreload" as="script" href="https://www.gequbao.com/build/assets/app-CV03aXhW.js" /><link rel="modulepreload" as="script" href="https://www.gequbao.com/build/assets/helper-CCnQIFFp.js" /><link rel="stylesheet" href="https://www.gequbao.com/build/assets/app-C-B9Q8LU.css" /><script type="module" src="https://www.gequbao.com/build/assets/app-CV03aXhW.js"></script> 743 <script defer src="//code.bdstatic.com/npm/jquery.qrcode@1.0.3/jquery.qrcode.min.js"></script> 744 <link rel="modulepreload" as="script" href="https://www.gequbao.com/build/assets/music-0Vjloyl4.js" /><link rel="modulepreload" as="script" href="https://www.gequbao.com/build/assets/helper-CCnQIFFp.js" /><link rel="modulepreload" as="script" href="https://www.gequbao.com/build/assets/download-helper-BYaTCaYd.js" /><script type="module" src="https://www.gequbao.com/build/assets/music-0Vjloyl4.js"></script> 745 746 <button id="backToTop" class="back-to-top" title="回到顶部" aria-label="回到顶部"> 747 <i class="fa fa-arrow-up"></i> 748 </button> 749 750 </body> 751 </html>
对比一下:

![]()
什么!mp3地址消失了!我们只能解释:这个mp3资源地址是js当场渲染的。其实,requests这种低级爬虫只能得起骨架html,不可得起血肉——网站资源!
那么这种js渲染血肉的网站,要怎么爬取呢?
诶!我们可以用程序模拟浏览器访问该网页,让js渲染完后直接监听network里的mp3资源请求!这样简单高效,易于爬取。(当然你也可以在js渲染完后直接获取其填充血肉后的html,也可以达到目的,这里我的思路是监听资源请求截获其地址)。这就是selenium和playwright干的活了————模拟浏览器来应对js渲染页面。
因此我们可以写一个简单的程序:
1 from playwright.sync_api import sync_playwright 2 import requests 3 import os 4 import time 5 import re 6 7 headers = { 8 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36" 9 } 10 11 browser = p.chromium.launch(headless=False)#打开浏览器 12 context = browser.new_context( 13 user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36[reference:3]")#明确UA 14 page = context.new_page()#打开新标签页 15 with page.expect_request(re.compile(r'\.mp3|from=vip'), timeout=8000) as capture:#监听正则符合r'\.mp3|from=vip'的网址,大家可以去自己总结一下规律,允许延迟8秒之内 16 page.goto("具体音乐地址,自己改一下")#去该音乐网址 17 time.sleep(1.5)#休息一下,速度不要太快被网页当做爬虫,为了逼真 18 page.click("text=播放", timeout=5000, force=False)#播放音乐,让音乐文件加载 19 request = capture.value#提取请求核心信息 20 print(request.url) 21 result1 = requests.get(request.url, headers=headers)#访问音乐地址 22 with open(f"{os.path.expanduser("~/Music/")}{search}/{i}.mp3", "wb") as p:#大家存储地址可以改 23 p.write(result1.content)#写入爬取音乐文件的具体信息 24 print("欧耶!")
有几个问题:
1、我们不是很逼真,有的时候网站怀疑我们会索要验证码,类似下方:

2、有的时候网站略怀疑我们,会故意“磨洋工”——js死活不加载渲染音乐的地址,压根爬不了
这时你肯定会报playwright的timeout错误————这不是python内置的timeout错误,这是playwright独有的。
那我们可以异常处理,分三层:先看看能否一次成功;再看网页如果“磨洋工”就刷新一下重新加载,同时达到逼近真人的效果(真人遇到这种情况一定会刷新网页);最后如果前面两次不成功,那一定是网站索要验证码,那我们就利用ddddocr这个库解决这种简单验证码。
我简单改改,顺便封装成函数形式,加上爬取歌词的功能,而且在执行前先试着用一下requests,看看网页有没有可能犯傻让我们捡便宜:
1 import time 2 from playwright.sync_api import sync_playwright 3 from playwright._impl._errors import TimeoutError as PlaywrightTimeoutError 4 import re 5 import requests 6 import base64 7 import ddddocr 8 import os 9 10 #爬取音乐 11 def get_music(search, list_songs): 12 for i in list_songs: 13 resp = requests.get(list_songs[i], headers=headers) 14 html = etree.HTML(resp.text) 15 location = html.xpath("//audio[@id='custom-audio-player']/@src") 16 if location == []: 17 with sync_playwright() as p: 18 browser = p.chromium.launch(headless=True) 19 context = browser.new_context( 20 user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36[reference:3]") 21 page = context.new_page() 22 try: 23 with page.expect_request(re.compile(r'\.mp3|from=vip'), timeout=8000) as capture: 24 page.goto(list_songs[i]) 25 time.sleep(1.5) 26 page.click("text=播放", timeout=5000, force=False) 27 request = capture.value 28 print(request.url) 29 result1 = requests.get(request.url, headers=headers) 30 with open(f"{os.path.expanduser("~/Music/")}{search}/{i}.mp3", "wb") as p: 31 p.write(result1.content) 32 print("欧耶!我是第一次那一支的!") 33 html = page.content() 34 pattern = re.compile(r'<div[^>]*id="content-lrc"[^>]*>(.*?)</div>', re.DOTALL) 35 match = pattern.search(html) 36 if match: 37 raw = match.group(1) 38 # 把 <br> 替换为换行,清理多余标签(如果有) 39 lrc_text = re.sub(r'<br\s*/?>', '\n', raw) 40 lrc_text = re.sub(r'<.*?>', '', lrc_text) # 移除其他残留标签 41 with open(f"{os.path.expanduser("~/Music/")}{search}/{i}歌词.txt", "w") as p: 42 p.write(lrc_text) 43 print(f"{i}歌词已下载") 44 browser.close() 45 except PlaywrightTimeoutError: 46 try: 47 page.reload() 48 with page.expect_request(re.compile(r'\.mp3|from=vip'), timeout=8000) as capture: 49 page.goto(list_songs[i]) 50 time.sleep(1.5) 51 page.click("text=播放", timeout=5000, force=False) 52 request = capture.value 53 print(request.url) 54 result = requests.get(request.url, headers=headers) 55 with open(f"{os.path.expanduser("~/Music/")}{search}/{i}.mp3", "wb") as p: 56 p.write(result.content) 57 print("欧耶!我是刷新那一支的!") 58 html = page.content() 59 pattern = re.compile(r'<div[^>]*id="content-lrc"[^>]*>(.*?)</div>', re.DOTALL) 60 match = pattern.search(html) 61 if match: 62 raw = match.group(1) 63 # 把 <br> 替换为换行,清理多余标签(如果有) 64 lrc_text = re.sub(r'<br\s*/?>', '\n', raw) 65 lrc_text = re.sub(r'<.*?>', '', lrc_text) # 移除其他残留标签 66 with open(f"{os.path.expanduser("~/Music/")}{search}/{i}歌词.txt", "w") as p: 67 p.write(lrc_text) 68 print(f"{i}歌词已下载") 69 browser.close() 70 except PlaywrightTimeoutError: 71 page.goto(list_songs[i]) 72 time.sleep(1.5) 73 page.click("text=播放", timeout=5000, force=False) 74 page.wait_for_selector("input[placeholder^='请输入验证码']", timeout=3000) 75 img_el = page.locator("img[id^='play-captcha-img-']") 76 src = img_el.get_attribute("src") 77 ocr = ddddocr.DdddOcr() 78 base64_data = src.split(",")[1] 79 img_bytes = base64.b64decode(base64_data) 80 code = ocr.classification(img_bytes) 81 time.sleep(1) 82 page.fill("xpath=//input[@placeholder='请输入验证码']", code) 83 time.sleep(1) 84 page.click("xpath=//button[@class='btn btn-blue']") 85 time.sleep(1) 86 with page.expect_request(re.compile(r'\.mp3|from=vip'), timeout=8000) as capture: 87 page.goto(list_songs[i]) 88 page.click("text=播放", timeout=5000, force=False) 89 request = capture.value 90 print(request.url) 91 result = requests.get(request.url, headers=headers) 92 with open(f"{os.path.expanduser("~/Music/")}{search}/{i}.mp3", "wb") as p: 93 p.write(result.content) 94 print("欧耶!我是验证码那一支的!") 95 html = page.content() 96 pattern = re.compile(r'<div[^>]*id="content-lrc"[^>]*>(.*?)</div>', re.DOTALL) 97 match = pattern.search(html) 98 if match: 99 raw = match.group(1) 100 # 把 <br> 替换为换行,清理多余标签(如果有) 101 lrc_text = re.sub(r'<br\s*/?>', '\n', raw) 102 lrc_text = re.sub(r'<.*?>', '', lrc_text) # 移除其他残留标签 103 with open(f"{os.path.expanduser("~/Music/")}{search}/{i}歌词.txt", "w") as p: 104 p.write(lrc_text) 105 print(f"{i}歌词已下载") 106 browser.close() 107 time.sleep(1.5) 108 else: 109 result = requests.get(location[0], headers=headers) 110 with open(f"{os.path.expanduser("~/Music/")}{search}/{i}.mp3", "wb") as p: 111 p.write(result.content) 112 print("欧耶!我是html那一支的!") 113 pattern = re.compile(r'<div[^>]*id="content-lrc"[^>]*>(.*?)</div>', re.DOTALL) 114 match = pattern.search(resp.text) 115 if match: 116 raw = match.group(1) 117 # 把 <br> 替换为换行,清理多余标签(如果有) 118 lrc_text = re.sub(r'<br\s*/?>', '\n', raw) 119 lrc_text = re.sub(r'<.*?>', '', lrc_text) # 移除其他残留标签 120 with open(f"{os.path.expanduser("~/Music/")}{search}/{i}歌词.txt", "w") as p: 121 p.write(lrc_text) 122 print(f"{i}歌词已下载")
这里base64_data = src.split(",")[1]是为了取验证码图片主要信息,舍弃头部文件属性;img_bytes是把64位图像信息格式化一下,从而直接喂ddddocr获得验证码。
我们补写一下其他必要的函数:
import time from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.edge.options import Options from playwright.sync_api import sync_playwright from playwright._impl._errors import TimeoutError as PlaywrightTimeoutError import re from lxml import etree import requests import base64 import ddddocr import os headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36" } def make_dir(search):#创建歌曲文件夹 folder_path = f"{os.path.expanduser("~/Music/")}{search}" if not os.path.exists(folder_path): os.makedirs(folder_path) print("目录已创建") else: pass def search_content(search):#查找歌曲目录 edge_options = Options() edge_options.add_argument("--headless=new") edge_options.add_argument('--window-size=1920,1080') browser1 = webdriver.Edge(options=edge_options)#打开 Safari 浏览器 browser1.get("https://www.xxxx.com")#打开歌曲包,自己改一下网址 #输入 input_option =browser1.find_element(By.XPATH, "//input[@class='form-control text-center border-primary rounded shadow-sm s-input-ele js-search-input']") input_option.send_keys(search) #访问 input_option.send_keys(Keys.RETURN) time.sleep(2) #歌曲列表 list_music = dict() get_locations = browser1.find_elements(By.XPATH, "//div[@class='card-text']/div[@class='row no-gutters py-2d5 border-top align-items-center']/div[@class='col-9 col-md-8']/a") for i in get_locations: list_music[i.get_attribute("title")] = i.get_attribute("href") print(list_music) browser1.close() return list_music
这用了selenium,抱歉,因为最先写search_content,还没想到用playwright。
gui.py的文件我也给你们吧!
# gui.py import tkinter as tk from tkinter import scrolledtext, messagebox, Listbox, MULTIPLE, EXTENDED import threading import sys from io import StringIO import get # 导入你的 get.py 模块 class MusicDownloaderGUI: def __init__(self, root): self.root = root root.title("音乐下载器 - 歌曲宝") root.geometry("650x600") # ---- 搜索区域 ---- tk.Label(root, text="请输入搜索关键词:", font=("微软雅黑", 12)).pack(pady=5) self.entry = tk.Entry(root, width=40, font=("微软雅黑", 12)) self.entry.pack(pady=5) self.entry.bind('<Return>', lambda e: self.search_songs()) self.search_btn = tk.Button(root, text="搜索歌曲", command=self.search_songs, bg="#2196F3", fg="white", font=("微软雅黑", 12)) self.search_btn.pack(pady=5) # ---- 歌曲列表(支持多选) ---- tk.Label(root, text="歌曲列表(按住 Ctrl 或 Shift 多选):", font=("微软雅黑", 10)).pack(pady=5) frame = tk.Frame(root) frame.pack(padx=10, pady=5, fill=tk.BOTH, expand=True) self.listbox = Listbox(frame, selectmode=EXTENDED, height=10, font=("微软雅黑", 10)) self.listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar = tk.Scrollbar(frame, orient=tk.VERTICAL, command=self.listbox.yview) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.listbox.config(yscrollcommand=scrollbar.set) # ---- 下载按钮 ---- self.download_btn = tk.Button(root, text="下载选中的歌曲", command=self.download_selected, bg="#4CAF50", fg="white", font=("微软雅黑", 12), state=tk.DISABLED) self.download_btn.pack(pady=5) # ---- 日志文本框 ---- tk.Label(root, text="运行日志:", font=("微软雅黑", 10)).pack(pady=2) self.log_text = scrolledtext.ScrolledText(root, wrap=tk.WORD, width=70, height=15, font=("Consolas", 9)) self.log_text.pack(padx=10, pady=5, fill=tk.BOTH, expand=True) # ---- 状态栏 ---- self.status_var = tk.StringVar() self.status_var.set("就绪") self.status_bar = tk.Label(root, textvariable=self.status_var, bd=1, relief=tk.SUNKEN, anchor=tk.W) self.status_bar.pack(side=tk.BOTTOM, fill=tk.X) # 存储搜索结果 self.songs_dict = {} self.keyword = "" # 重定向 stdout 到日志 self.stdout_redirect = StringIO() sys.stdout = self.stdout_redirect self.update_log() def update_log(self): """定期刷新日志显示""" content = self.stdout_redirect.getvalue() if content: self.log_text.insert(tk.END, content) self.log_text.see(tk.END) self.stdout_redirect.truncate(0) self.stdout_redirect.seek(0) self.root.after(200, self.update_log) def log(self, msg): """直接写入日志(用于非 print 信息)""" self.log_text.insert(tk.END, msg + "\n") self.log_text.see(tk.END) def search_songs(self): """搜索歌曲并填充列表""" keyword = self.entry.get().strip() if not keyword: messagebox.showwarning("提示", "请输入搜索关键词") return self.search_btn.config(state=tk.DISABLED) self.download_btn.config(state=tk.DISABLED) self.listbox.delete(0, tk.END) self.log_text.delete(1.0, tk.END) self.log(f"正在搜索:{keyword}") # 后台执行搜索 def task(): try: self.songs_dict = get.search_content(keyword) self.keyword = keyword if not self.songs_dict: self.log("未找到任何歌曲") else: for title in self.songs_dict.keys(): self.listbox.insert(tk.END, title) self.log(f"找到 {len(self.songs_dict)} 首歌曲") self.root.after(0, lambda: self.download_btn.config(state=tk.NORMAL)) except Exception as e: self.log(f"搜索出错:{e}") finally: self.root.after(0, lambda: self.search_btn.config(state=tk.NORMAL)) threading.Thread(target=task, daemon=True).start() def download_selected(self): """下载选中的歌曲""" selected_indices = self.listbox.curselection() if not selected_indices: messagebox.showinfo("提示", "请至少选择一首歌曲") return # 构建选中歌曲字典 selected_titles = [self.listbox.get(idx) for idx in selected_indices] selected_dict = {title: self.songs_dict[title] for title in selected_titles} self.download_btn.config(state=tk.DISABLED) self.search_btn.config(state=tk.DISABLED) self.log(f"开始下载 {len(selected_dict)} 首歌曲...") # 创建文件夹(确保存在) try: get.make_dir(self.keyword) # 调用你的创建目录函数 except Exception as e: self.log(f"创建目录失败:{e}") # 后台执行下载 def task(): try: # 调用 get_music,传入选中的字典 get.get_music(self.keyword, selected_dict) self.log("所有选中歌曲处理完毕!") except Exception as e: self.log(f"下载过程出错:{e}") finally: self.root.after(0, lambda: self.download_btn.config(state=tk.NORMAL)) self.root.after(0, lambda: self.search_btn.config(state=tk.NORMAL)) threading.Thread(target=task, daemon=True).start() if __name__ == "__main__": root = tk.Tk() app = MusicDownloaderGUI(root) root.mainloop()
好了~程序结束~,希望你可以利用这12.88kb左右的文件,下载 nGB 的音乐与歌词!希望我的高中生活在音乐沐浴中扬帆起航!

浙公网安备 33010602011771号