
㊗️本期内容已收录至专栏《Python爬虫实战》持续完善知识体系与项目实战建议先订阅收藏后续查阅更方便㊙️本期爬虫难度指数⭐ (入门级)福利一次订阅后专栏内的所有文章可永久免费看持续更新中保底1000(篇)硬核实战内容。全文目录 开篇语0️⃣ 前言Preface1️⃣ 摘要Abstract2️⃣ 背景与需求Why3️⃣ 合规与注意事项必写 4️⃣ 技术选型与整体流程What/How5️⃣ 环境准备与依赖安装可复现 ️6️⃣ 核心实现请求层Fetcher ️7️⃣ 核心实现解析层Parser 8️⃣ 数据存储与导出Storage 9️⃣ 运行方式与结果展示必写 ▶️ 常见问题与排错Troubleshooting 1️⃣1️⃣ 进阶优化可选但加分 1️⃣2️⃣ 总结与延伸阅读 文末✅ 专栏持续更新中建议收藏 订阅✅ 互动征集✅ 免责声明 开篇语哈喽各位小伙伴们你们好呀我是【喵手】。运营社区 C站 / 掘金 / 腾讯云 / 阿里云 / 华为云 / 51CTO欢迎大家常来逛逛一起学习一起进步我长期专注Python 爬虫工程化实战主理专栏 《Python爬虫实战》从采集策略到反爬对抗从数据清洗到分布式调度持续输出可复用的方法论与可落地案例。内容主打一个“能跑、能用、能扩展”让数据价值真正做到——抓得到、洗得净、用得上。专栏食用指南建议收藏✅ 入门基础环境搭建 / 请求与解析 / 数据落库✅ 进阶提升登录鉴权 / 动态渲染 / 反爬对抗✅ 工程实战异步并发 / 分布式调度 / 监控与容错✅ 项目落地数据治理 / 可视化分析 / 场景化应用专栏推广时间如果你想系统学爬虫而不是碎片化东拼西凑欢迎订阅专栏《Python爬虫实战》一次订阅后专栏内的所有文章可永久免费阅读持续更新中。订阅后更新会优先推送按目录学习更高效0️⃣ 前言Preface找谱子时总是迷失在繁杂的网页广告中不知道哪首歌适合自己当前的水平今天我们将编写一个 Python 爬虫深入公开曲谱分享网站的分类树把所有曲谱的元数据难度、调式、下载链接等全部抓取下来做成一个可以用 Excel 任意筛选的本地曲谱大典。读完这篇教程你将获得树形遍历技巧掌握如何将“父级分类如指弹吉他”的数据一层层向下传递给子级详情页。文本深度清洗学会处理类似“调式C大调”、“难度★★★☆☆”这种带有冗余前缀的脏数据。结构化曲谱库获得一份可直接用于练习计划制定的 CSV 数据集。1️⃣ 摘要Abstract本文旨在构建一个针对乐器谱例目录站的轻量级爬虫系统。我们将使用requests库请求网页BeautifulSoup解析 HTML DOM 树。程序首先解析分类列表页获取曲目链接随后深入曲谱详情页提取曲名、作者/编配者、调式、难度、下载链接、分类六大核心字段并通过字符串处理去除冗余标签最终将纯净数据导出为sheet_music_catalog.csv文件。2️⃣ 背景与需求Why为什么要爬高效练琴把数据存入表格后你可以轻松筛选出“C大调”、“难度低于3星”的所有流行歌曲有针对性地进行视奏练习。离线归档网站可能会关停先一步将下载链接和曲谱信息建档打造永不丢失的个人曲库。目标字段映射清单Category(分类 - e.g., “吉他谱 / 流行指弹”)Song_Title(曲名 - e.g., “River Flows in You”)Arranger(作者/编配者 - e.g., “郑成河”)Key_Signature(调式 - e.g., “G大调”, “Am”)Difficulty(难度 - e.g., “进阶”, “4星”)Download_URL(下载链接 - 指向 PDF 或图片的直链)3️⃣ 合规与注意事项必写 音乐圈极其看重版权作为技术极客我们必须守规矩版权界限我们抓取的是公开曲谱的“目录信息元数据”。对于公版音乐如巴赫、莫扎特的曲谱可以放心采集但对于受版权保护的流行音乐收费谱绝不可用爬虫绕过付费墙。频率控制曲谱站点的服务器通常比较小众脆弱。在循环请求详情页时务必加入time.sleep(2)的延时避免引发 DDoS 效应被封 IP。遵守 robots.txt抓取前查看网站根目录的防爬协议不触碰站长禁止抓取的区域。4️⃣ 技术选型与整体流程What/How技术选型静态网页采集模式。曲谱网站为了方便搜索引擎收录SEO通常采用传统的服务端渲染SSR。因此Requestsbs4的经典组合依然是最快、最稳的选择。整体流程层级穿透分类采集访问吉他/钢琴分类页 ➡️ 提取页面上所有的曲谱详情 URL。状态传递将当前所属的“分类名称”作为参数传递给下一步。详情挖掘请求曲谱详情 URL➡️ 提取调式、难度等文本 ➡️执行字符串清洗。数据汇总拼装字典 ➡️ 导出 CSV。5️⃣ 环境准备与依赖安装可复现 ️建议使用 Python 3.8 环境。安装依赖库pipinstallrequests beautifulsoup4 pandas lxml推荐项目结构music_scraper/ ├── spider.py # 爬虫核心代码 └── data/ # 存放最终的数据表 └── sheet_music_catalog.csv6️⃣ 核心实现请求层Fetcher ️封装一个带有重试机制的通用请求函数防止网络波动导致抓取中断。importrequestsimporttimeimportrandomfromrequests.exceptionsimportRequestExceptiondeffetch_html(url,retries3): 通用网页请求函数伪装浏览器请求 headers{User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36,Referer:https://www.google.com/}forattemptinrange(retries):try:responserequests.get(url,headersheaders,timeout10)response.raise_for_status()# 检查 404/500 等错误# 曲谱站偶尔会有 GBK 编码的老网站这里让 requests 自动推断response.encodingresponse.apparent_encodingreturnresponse.textexceptRequestExceptionase:print(f⚠️ 网络抖动正在重试 ({attempt1}/{retries}):{url}-{e})time.sleep(random.uniform(2,4))print(f❌ 彻底放弃请求:{url})returnNone7️⃣ 核心实现解析层Parser 这是本次的重点我们要传递分类状态并且清洗脏文本。frombs4importBeautifulSoupdefparse_category_page(html,base_url): 解析分类列表页获取曲谱链接和所属分类 返回格式: [{title: ..., url: ..., category: ...}, ...] soupBeautifulSoup(html,lxml)song_links[]# 假设分类名在页面的 h1 标签中 (e.g., h1吉他谱 - 流行指弹/h1)category_tagsoup.find(h1,class_cat-title)category_namecategory_tag.get_text(stripTrue)ifcategory_tagelse未分类# 假设曲目列表在一个 ul.music-list 中list_itemssoup.find_all(li,class_music-item)foriteminlist_items:a_tagitem.find(a)ifa_taganda_tag.get(href):hrefa_tag.get(href)full_urlhrefifhref.startswith(http)elsebase_url.rstrip(/)href song_links.append({Song_Title:a_tag.get_text(stripTrue),Category:category_name,Detail_URL:full_url})returnsong_linksdefparse_sheet_detail(html): 解析详情页抽取元数据进行字符串清洗 soupBeautifulSoup(html,lxml)# 设置默认值增强容错data{Arranger:佚名,Key_Signature:未知,Difficulty:未知,Download_URL:无链接}ifnothtml:returndatatry:# 很多网站的数据是包裹在文本里的比如 span编配周杰伦/span# 【清洗技巧】提取 text 后用 replace 去掉冒号前缀# 1. 作者/编配者author_tagsoup.find(span,class_meta-author)ifauthor_tag:raw_textauthor_tag.get_text(stripTrue)data[Arranger]raw_text.replace(编配,).replace(作者,).strip()# 2. 调式key_tagsoup.find(span,class_meta-key)ifkey_tag:raw_textkey_tag.get_text(stripTrue)data[Key_Signature]raw_text.replace(调式,).replace(Key: ,).strip()# 3. 难度diff_tagsoup.find(span,class_meta-diff)ifdiff_tag:raw_textdiff_tag.get_text(stripTrue)# 有时难度表现为 ★★★☆☆清洗时可能只想要前缀或原样保留data[Difficulty]raw_text.replace(难度,).strip()# 4. 下载链接 (通常指向一个网盘或者 PDF 直链)download_btnsoup.find(a,class_btn-download)ifdownload_btn:data[Download_URL]download_btn.get(href)exceptExceptionase:print(f⚠️ 解析详情页某些字段时报错:{e})returndata8️⃣ 数据存储与导出Storage 我们把分类页传下来的数据和详情页挖出来的数据拼在一起存为 CSV。importpandasaspdimportosdefsave_catalog(data_list,filenamesheet_music_catalog.csv):ifnotdata_list:print( 没有抓取到任何曲谱数据。)returndfpd.DataFrame(data_list)# 调整我们想要的列顺序cols[Category,Song_Title,Arranger,Key_Signature,Difficulty,Download_URL]# 过滤掉不存在的列防止报错cols[cforcincolsifcindf.columns]dfdf[cols]output_dirdataos.makedirs(output_dir,exist_okTrue)filepathos.path.join(output_dir,filename)df.to_csv(filepath,indexFalse,encodingutf-8-sig)print(f 曲谱目录大功告成共整理{len(df)}首曲目已存入 ➡️{filepath})9️⃣ 运行方式与结果展示必写 ▶️老规矩为了让你立刻就能跑通并看到清洗效果我为你准备了包含典型“脏数据”的本地沙箱环境 (Mock Server)。直接复制下方代码运行# # 沙箱服务器 (Mock Server)# USE_MOCKTruedefmock_request(url,*args,**kwargs):ifcategory/guitar-fingerstyleinurl:return htmlbody h1 classcat-title吉他谱 - 流行指弹/h1 ul classmusic-list li classmusic-itema href/score/1001一直很安静仙剑奇侠传/a/li li classmusic-itema href/score/1002Merry Christmas Mr. Lawrence/a/li /ul /body/html elifscore/1001inurl:return htmlbody div classscore-meta span classmeta-author编配张三丰/span span classmeta-key调式C大调转D/span span classmeta-diff难度★★☆☆☆ (初级)/span /div a classbtn-download hrefhttps://pan.baidu.com/s/xxxx下载PDF格式/a /body/html elifscore/1002inurl:return htmlbody div classscore-meta span classmeta-author作者押尾桑/span span classmeta-keyKey: Gm/span span classmeta-diff难度★★★★★ (大师)/span /div a classbtn-download hrefhttps://drive.google.com/xxxxDownload PDF/a /body/html returnifUSE_MOCK:fetch_htmlmock_request# # 爬虫核心调度引擎# if__name____main__:BASE_URLhttps://mock-sheet-music.comCATEGORY_URLf{BASE_URL}/category/guitar-fingerstyleprint( 乐器曲谱目录检索器启动...)final_dataset[]# 1. 抓取分类列表页print(f 正在扫描分类页:{CATEGORY_URL})list_htmlfetch_html(CATEGORY_URL)# 提取曲名 分类 详情页链接song_tasksparse_category_page(list_html,BASE_URL)print(f ↳ 发现{len(song_tasks)}首曲谱准备解析详细元数据...)# 2. 遍历详情页foriteminsong_tasks:detail_urlitem.pop(Detail_URL)# 取出 URL顺便从字典中移除该键print(f 正在解析曲谱:{item[Song_Title]})detail_htmlfetch_html(detail_url)# 提取调式、难度、编配等并自动清洗脏标签detail_metaparse_sheet_detail(detail_html)# 【关键整合】把两部分数据融合在一起item.update(detail_meta)item[Download_URL]detail_meta.get(Download_URL)final_dataset.append(item)# 真实抓取时一定要开启延时ifnotUSE_MOCK:time.sleep(random.uniform(1.5,3))# 3. 存储与展示save_catalog(final_dataset)print(\n 纯净数据集预览 (Dataset Preview):)iffinal_dataset:print(pd.DataFrame(final_dataset).to_markdown(indexFalse))示例输出结果CategorySong_TitleArrangerKey_SignatureDifficultyDownload_URL吉他谱 - 流行指弹一直很安静仙剑奇侠传张三丰C大调转D★★☆☆☆ (初级)https://pan.baidu.com/s/xxxx吉他谱 - 流行指弹Merry Christmas Mr. Lawrence押尾桑Gm★★★★★ (大师)https://drive.google.com/xxxx( 请注意看Arranger和Key_Signature列原本在 HTML 里的“编配”、“调式”等冗余字眼已经被极其干净地清洗掉了这就是数据工程师的魔法) 常见问题与排错Troubleshooting 有些曲谱没有“难度”或“调式”标签导致程序报错解法在parse_sheet_detail函数中我们使用了if diff_tag:来做判断。如果找不到该元素字典里对应的键就会保持初始默认值如未知这样就绝不会引发AttributeError: NoneType object has no attribute get_text的崩溃。遇到全是图片的谱子没有文本介绍怎么办解法这种通常是用图片名字来做曲名。可以使用 OCR光学字符识别如Tesseract技术或者退而求其次只抓取img alt...的alt属性作为标题。下载链接是 JavaScript 点击事件没有 href解法这属于动态交互。可以考虑引入Playwright模拟点击或者在 F12 的Network面板抓包找到点击后触发的 XHR/JSON 接口直接请求该接口获取真实下载地址。1️⃣1️⃣ 进阶优化可选但加分 PDF 实体下载器既然都拿到Download_URL了为什么不直接把谱子保存到电脑里呢你可以加入一个下载模块response requests.get(pdf_url); open(score.pdf, wb).write(response.content)。并发加速 (Asyncio)如果你要爬取包含 10,000 首钢琴谱的大型数据库单线程太慢了。推荐使用aiohttpasyncio进行异步高并发抓取速度能提升 10 倍以上但要注意控制并发数保护对方服务器。1️⃣2️⃣ 总结与延伸阅读 干得漂亮通过这个项目你不仅掌握了多层级网页的爬取逻辑状态传递还重点练习了**“脏文本清洗Text Cleaning”**。在实际的数据分析工作中爬取数据只占 20% 的时间剩下 80% 都在做清洗。现在你可以用这份代码去整理你心仪的曲谱网站了。把最终的 CSV 文件导入到 Notion 或者 Airtable 中加上各种筛选器你的私人数字琴房就此建成祝你练琴愉快 文末好啦以上就是本期的全部内容啦如果你在实践过程中遇到任何疑问欢迎在评论区留言交流我看到都会尽量回复咱们下期见小伙伴们在批阅的过程中如果觉得文章不错欢迎点赞、收藏、关注哦三连就是对我写作道路上最好的鼓励与支持❤️✅ 专栏持续更新中建议收藏 订阅墙裂推荐订阅专栏 《Python爬虫实战》本专栏秉承着以“入门 → 进阶 → 工程化 → 项目落地”的路线持续更新争取让每一期内容都做到✅ 讲得清楚原理✅ 跑得起来代码✅ 用得上场景✅ 扛得住工程化想系统提升的小伙伴强烈建议先订阅专栏 《Python爬虫实战》再按目录大纲顺序学习效率十倍上升✅ 互动征集想让我把【某站点/某反爬/某验证码/某分布式方案】等写成某期实战评论区留言告诉我你的需求我会优先安排实现(更新)哒~⭐️ 若喜欢我就请关注我叭更新不迷路⭐️ 若对你有用就请点赞支持一下叭给我一点点动力⭐️ 若有疑问就请评论留言告诉我叭我会补坑 更新迭代✅ 免责声明本文爬虫思路、相关技术和代码仅用于学习参考对阅读本文后的进行爬虫行为的用户本作者不承担任何法律责任。使用或者参考本项目即表示您已阅读并同意以下条款合法使用 不得将本项目用于任何违法、违规或侵犯他人权益的行为包括但不限于网络攻击、诈骗、绕过身份验证、未经授权的数据抓取等。风险自负 任何因使用本项目而产生的法律责任、技术风险或经济损失由使用者自行承担项目作者不承担任何形式的责任。禁止滥用 不得将本项目用于违法牟利、黑产活动或其他不当商业用途。使用或者参考本项目即视为同意上述条款,即 “谁使用谁负责” 。如不同意请立即停止使用并删除本项目。