【Scrapy实战避坑】5sing网站爬虫从0到1,踩遍动态渲染、正则匹配全坑(附完整解决方案)
今天给大家带来一篇超详细的Scrapy实战避坑指南------5sing网站(酷狗旗下伴奏/歌曲平台)爬虫开发全流程,全程还原我从启动项目到最终爬取成功的所有踩坑经历、排查思路,以及核心技术知识点补充,适合爬虫新手、Scrapy入门者参考,看完这篇,你能避开90%的同类爬虫坑!
先交代背景:本次目标是爬取5sing网站的伴奏歌曲信息(歌曲名、歌手、歌曲ID等核心数据),技术选型为Scrapy框架,本以为是常规的静态页面爬取,结果踩了一个又一个坑,从Selector输出异常到JSON提取失败,再到页面结构误解,耗时大半天终于搞定,全程干货无废话,建议收藏备用!
一、实战前提:环境准备与需求明确
1.1 环境配置(无坑,直接照搬)
本次实战无需额外安装复杂依赖,核心环境如下,大家可直接对应配置,避免因环境问题踩坑:
-
Python 3.8+(推荐3.9,兼容性最佳,避免版本过低导致Scrapy报错)
-
Scrapy 2.8+(直接通过
pip install scrapy安装,无需额外依赖) -
浏览器(Chrome/Firefox,用于查看页面源码、抓包分析)
-
终端/IDE(PyCharm、VS Code均可,我用的是终端直接运行,更贴近实战场景)
注意:本次实战无需安装Selenium、Playwright等动态渲染工具(后面会解释原因),也无需额外配置代理,简化环境,聚焦核心踩坑点。
1.2 需求明确(避免需求模糊导致的无效开发)
核心需求:爬取5sing网站首页(https://5sing.kugou.com/index.html)的伴奏歌曲数据,具体包括:
-
下载榜单、热门榜单的歌曲信息
-
核心字段:songId(歌曲ID)、songName(歌曲名)、singerName(歌手名)、userNickname(上传用户)
-
辅助需求:将爬取的页面HTML保存到本地,便于后续排查问题;将提取的JSON数据完整打印,验证爬取效果
二、实战推进过程:从启动项目到踩坑,全程还原
接下来,我将完整还原本次实战的每一步,包括项目初始化、代码编写、第一次运行报错、排查过程、二次踩坑、最终解决,每一个环节都对应一个坑点,大家可以对照自己的爬虫开发过程,避开同类问题。
2.1 第一步:初始化Scrapy项目
首先,打开终端,进入自己的学习目录,执行以下命令初始化Scrapy项目,步骤常规,无坑可踩:
bash
# 初始化项目
scrapy startproject music51
# 进入项目目录
cd music51
# 创建爬虫文件(爬虫名5sing,爬取域名5sing.kugou.com)
scrapy genspider 5sing 5sing.kugou.com
执行完成后,项目结构自动生成,核心文件为:spiders/5sing.py(爬虫核心逻辑)、settings.py(项目配置),后续所有开发都围绕这两个文件展开。
2.2 第二步:编写初始爬虫代码(第一个坑:Selector输出异常)
初始思路:既然是爬取页面数据,先尝试用Scrapy的Selector(XPath)定位目标元素,提取歌曲名。我打开5sing首页,通过浏览器F12查看元素,定位到歌曲列表的a标签,XPath路径为://*[@id="accompany"]/div/div[2]/div[2]/div[1]/div[2]/a,于是编写了如下初始代码:
python
import scrapy
class A5singSpider(scrapy.Spider):
name = "5sing"
allowed_domains = ["5sing.kugou.com"]
start_urls = ["https://5sing.kugou.com/index.html"]
def parse(self, response):
# 用XPath定位目标a标签,提取文本
song_title = response.xpath('//*[@id="accompany"]/div/div[2]/div[2]/div[1]/div[2]/a/text()').get()
print("歌曲名称:", song_title)
编写完成后,终端执行命令:scrapy crawl 5sing,结果却出乎意料------没有打印出预期的歌曲名,反而输出了一个奇怪的Selector对象:
bash
[<Selector query='//*[@id="accompany"]/div/div[2]/div[2]/div' data=']【踩坑1】Selector输出对象,而非真实文本,误以为代码报错当时看到这个输出,第一反应是代码写错了,或者XPath路径不对,反复检查XPath路径,确认和浏览器里的一致,却始终无法输出文本。甚至怀疑自己的Scrapy环境有问题,反复卸载重装Scrapy,耗时近1小时,结果还是一样。排查思路:后来我意识到,这个输出并不是报错,而是Scrapy的Selector对象本身------当我们用XPath定位元素时,如果没有正确提取文本,或者定位到的元素是动态渲染的模板,就会直接输出Selector对象,而不是我们需要的文本内容。补充知识:Scrapy的Selector提取数据时,必须搭配提取方法,否则只会返回Selector对象,常用提取方法有3种:.get():提取单个元素的文本/属性,返回字符串,没有匹配到则返回None.getall():提取所有匹配元素的文本/属性,返回列表.extract():和getall()功能一致,返回列表(Python3.8+后推荐用getall(),更简洁)我当时虽然加了.get(),但问题不在于提取方法,而在于定位的元素本身------这是我后续才发现的第二个坑。2.3 第三步:排查Selector异常,发现第二个坑(Vue模板占位符)为了排查问题,我决定先将Scrapy获取到的HTML源码保存到本地,查看源码中是否有目标元素,于是修改代码,添加保存HTML的逻辑:def parse(self, response):
html = response.text
# 保存HTML到本地,便于排查
with open("5sing.html", "w", encoding="utf-8") as f:
f.write(html)
# 再次尝试提取文本
song_title = response.xpath('//*[@id="accompany"]/div/div[2]/div[2]/div[1]/div[2]/a/text()').get()
print("歌曲名称:", song_title)
运行后,本地生成了5sing.html文件,双击打开后,搜索我定位的XPath路径对应的a标签,发现标签内的文本并不是预期的歌曲名,而是:{{item.songName}}。
看到这个占位符,我瞬间反应过来------5sing网站用的是Vue框架动态渲染页面,我定位的a标签是Vue的模板占位符,而不是真实的歌曲数据。
【踩坑2】误将Vue模板占位符当作真实元素,用XPath提取无效
当时的误区:以为浏览器里看到的歌曲名,会直接出现在HTML源码中,只要定位到对应的标签,就能提取到数据。但实际上,Vue框架会通过JavaScript将数据渲染到页面上,初始HTML源码中只有模板占位符({{item.songName}}),没有真实数据。
补充知识:前端动态渲染的3种常见方式及爬虫应对方案:
- Vue/React框架渲染:数据通常藏在页面的
此时,我又陷入了一个新的误区------以为这是Ajax异步加载,于是打开浏览器F12,进入Network→XHR,刷新页面,却没有找到任何返回歌曲数据的接口,这就引出了第三个坑。
2.4 第四步:排查动态渲染方式,踩第三个坑(误解页面渲染逻辑)
因为没有找到Ajax接口,我又开始怀疑自己的判断,甚至尝试用Selenium整合Scrapy,编写了如下代码(部分):
python
# 安装scrapy-selenium后编写的代码(无效)
from scrapy_selenium import SeleniumRequest
def start_requests(self):
yield SeleniumRequest(
url='https://5sing.kugou.com/index.html',
callback=self.parse
)
运行后,虽然能模拟浏览器加载页面,但提取到的还是{{item.songName}},并没有真实数据,这让我非常困惑------为什么用Selenium也无法提取到数据?
于是,我再次打开保存的5sing.html文件,仔细查看源码,终于在页面的
javascript
bz_songs = {"dowload":[{"songId":3818563,"songKind":3,"singerName":"佚名",...},{"songId":3822946,"singerName":"周杰伦",...}],"hot":[...]};
看到这段代码,我才恍然大悟------5sing网站的歌曲数据,并没有通过Ajax异步加载,也不是通过Selenium才能渲染,而是直接嵌在页面的
【踩坑3】误解动态渲染逻辑,盲目使用Selenium,浪费时间
这个坑是本次实战中最耗时的一个------因为对前端渲染方式判断错误,误以为是需要模拟浏览器才能加载数据,盲目引入Selenium,不仅增加了环境配置的复杂度,还没有解决问题。
核心总结:爬虫开发中,遇到动态渲染页面,不要第一时间就用Selenium,先查看页面源码,搜索是否有藏在
2.5 第五步:提取
既然找到了数据所在,接下来就是提取
于是,我修改代码,添加正则匹配逻辑:
python
import re
import json
def parse(self, response):
html = response.text
with open("5sing.html", "w", encoding="utf-8") as f:
f.write(html)
# 正则匹配bz_songs对应的JSON
pattern = re.compile(r'bz_songs\s*=\s*(\{.*?\})', re.S)
result = pattern.search(html)
if result:
json_str = result.group(1)
bz_songs = json.loads(json_str)
print(bz_songs)
运行后,终端没有任何输出,也没有报错,只有一个空行,这说明正则匹配失败,没有找到对应的JSON字符串。
【踩坑4】正则表达式编写不严谨,匹配不到目标数据
排查思路:我再次打开5sing.html文件,查看bz_songs的完整格式,发现这段JSON的结尾有一个分号(;),即完整格式是bz_songs = {...};
而我编写的正则表达式没有包含结尾的分号,导致匹配不到完整的JSON字符串------正则匹配时,必须严格对应页面中的格式,多一个字符、少一个字符,都会导致匹配失败。
补充知识:正则匹配JSON字符串的关键技巧(针对嵌在
-
匹配变量赋值:如果JSON是通过变量赋值的(如var data = {...};),正则需包含"变量名 + = + 空格 + JSON + 分号",确保匹配完整;
-
使用re.S修饰符:让正则中的.匹配换行符,因为
修改正则表达式,添加分号,代码如下:
python
pattern = re.compile(r'bz_songs\s*=\s*(\{.*?\});', re.S)
再次运行,终端终于输出了完整的JSON数据,也就是我最终成功提取到的歌曲信息,包含下载榜单和热门榜单的所有歌曲数据。
2.6 第六步:优化代码,完善功能
解决了正则匹配问题后,我对代码进行了优化,添加了请求头配置(避免被网站反爬)、异常处理(防止匹配失败导致程序崩溃),并完善了数据打印逻辑,最终的完整代码如下(可直接复制运行):
python
import json
import re
import scrapy
class A5singSpider(scrapy.Spider):
name = "5sing"
allowed_domains = ["5sing.kugou.com"]
# 保持首页地址,不修改页面(按需求要求)
start_urls = ["https://5sing.kugou.com/index.html"]
custom_settings = {
"DEFAULT_REQUEST_HEADERS": {
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'accept-language': 'zh-CN,zh;q=0.9',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36',
'cookie': 'sl-session=KnwoWozQ2WnAl0asRw39Rw==; kg_mid=94ae5cb0ff9fda8bd1e89ea8c46853a4; kg_dfid=3MqGF81g2Tq64cy0SV3kT5pr; kg_dfid_collect=d41d8cd98f00b204e9800998ecf8427e; ACK_SERVER_10015=%7B%22list%22%3A%5B%5B%22bjlogin-user.kugou.com%22%5D%5D%7D; cct=b149131f',
},
# 解决部分网站的Referer反爬
"REFERER_POLICY": "same-origin",
"REDIRECT_PRIORITIES": False
}
def parse(self, response):
html = response.text
# 保存HTML到本地,便于后续排查问题
with open("5sing.html", "w", encoding="utf-8") as f:
f.write(html)
# 核心:正则匹配bz_songs对应的JSON数据
try:
pattern = re.compile(r'bz_songs\s*=\s*(\{.*?\});', re.S)
result = pattern.search(html)
if result:
# 提取JSON字符串,转换为Python字典
json_str = result.group(1)
bz_songs = json.loads(json_str)
# 打印完整JSON数据,验证爬取效果
print("="*60)
print("✅ 成功提取 bz_songs 完整JSON:\n")
print(json.dumps(bz_songs, ensure_ascii=False, indent=2))
print("="*60)
# 单独提取歌曲名、歌手,便于后续使用
print("\n==== 下载榜单歌曲 ====")
for song in bz_songs['dowload']:
print(f"歌曲ID:{song['songId']} | 歌曲名:{song['songName']} | 歌手:{song['singerName']} | 上传用户:{song['userNickname']}")
print("\n==== 热门榜单歌曲 ====")
for song in bz_songs['hot']:
print(f"歌曲ID:{song['songId']} | 歌曲名:{song['songName']} | 歌手:{song['singerName']} | 上传用户:{song['userNickname']}")
else:
print("❌ 未匹配到 bz_songs 数据,请检查正则表达式或页面结构")
except Exception as e:
print(f"❌ 程序运行出错:{str(e)}")
运行命令:scrapy crawl 5sing,终端成功输出完整的JSON数据和单独提取的歌曲信息,本次实战终于成功!
三、本次实战核心踩坑总结(重中之重,必看)
本次5sing爬虫实战,看似简单,却踩了4个典型的爬虫坑,这些坑也是新手在开发动态页面爬虫时最容易遇到的,总结如下,帮大家避坑:
坑1:Selector输出对象,误以为报错
✅ 解决方案:提取数据时,必须搭配.get()/.getall()方法;如果输出Selector对象,先检查XPath路径是否正确,再检查是否是动态渲染的模板占位符。
坑2:误将Vue模板占位符当作真实元素
✅ 解决方案:遇到{{xxx}}这类占位符,说明是前端框架渲染,先查看页面源码的
坑3:误解动态渲染逻辑,盲目使用Selenium
✅ 解决方案:优先排查页面
坑4:正则表达式编写不严谨,匹配失败
✅ 解决方案:正则匹配时,严格对应页面中的数据格式(如结尾的分号、空格),使用re.S修饰符匹配换行,用非贪婪匹配(.*?)避免过度匹配;匹配失败时,先打印页面源码,确认目标数据的完整格式。
四、爬虫拓展知识与注意事项
4.1 拓展知识:Scrapy爬取动态页面的3种核心方案
结合本次实战,给大家补充Scrapy爬取动态页面的3种核心方案,按优先级排序(优先选择高效、简单的方式):
- 提取
4.2 注意事项(避坑加分项)
-
请求头配置:必须添加User-Agent、Cookie等信息,模拟浏览器请求,避免被网站识别为爬虫,导致无法获取页面数据;
-
异常处理:添加try-except捕获异常,防止正则匹配失败、JSON解析失败导致程序崩溃;
-
本地保存HTML:开发过程中,建议将获取到的HTML保存到本地,便于排查问题(如XPath定位错误、数据缺失等);
-
反爬应对:如果遇到IP被封,可添加代理IP;如果遇到Cookie失效,可定期更新Cookie(本次实战的Cookie可直接使用,有效期较长);
-
数据合规:爬取网站数据时,需遵守网站的robots协议,不要过度爬取,避免侵犯网站版权和数据权益。
五、总结与后续规划
本次5sing Scrapy爬虫实战,从踩坑到成功,全程耗时近4小时,核心收获不仅是完成了爬虫开发,更重要的是掌握了动态页面爬虫的排查思路和避坑技巧------对于爬虫新手来说,遇到问题不要急于求成,先排查页面结构、数据来源,再针对性解决,比盲目修改代码更高效。
后续规划:基于本次爬取的歌曲数据,可进一步拓展功能,比如:
-
将爬取的数据保存到CSV/Excel文件,便于查看和使用;
-
添加分页爬取逻辑,爬取更多页面的歌曲数据;
-
结合歌曲ID,爬取每首歌曲的详细信息(如播放量、下载量);
-
将爬虫程序封装成带UI的桌面应用,提升实用性。
最后,希望这篇实战避坑指南能帮助到各位爬虫爱好者,如果你在开发过程中遇到了类似的问题,欢迎在评论区留言交流,我会及时回复。如果觉得这篇文章对你有帮助,记得点赞、收藏、关注,后续我会分享更多Scrapy实战案例和爬虫避坑技巧!