Scrapy多级请求实战:5sing伴奏网爬取踩坑与优化全记录(JSON提取+Xpath解析)
前言:本次实战围绕5sing伴奏网热榜歌曲爬取展开,核心需求是获取首页热榜歌曲基础信息,并深入详情页提取歌曲分类、格式、大小、下载量等完整数据。开发过程中,核心突破点在于发现网站数据存储的差异化的特点------首页热榜数据以JSON字段形式嵌入页面源码,详情页则为标准HTML结构,由此完成了从Xpath解析到正则提取的切换,同时实现Scrapy多级请求(即大家常说的"二次爬取",专业表述为Scrapy多级请求/二级页面爬取),全程踩坑不断,最终完成优化落地,特此整理成实战笔记,供各位爬虫爱好者参考避坑。
一、核心需求与技术架构
1.1 爬取目标
目标网站:5sing.kugou.com(5sing伴奏网)
爬取范围:首页热榜(hot)歌曲,需获取两类数据:
-
一级页面(首页):歌曲ID、歌曲名称、歌手、上传用户ID、上传用户昵称
-
二级页面(详情页):歌曲分类、文件格式、文件大小、下载次数
1.2 专业技术架构
Scrapy多级请求(二级页面爬取)
-
发起一级请求:请求首页(start_urls),获取页面源码;
-
解析一级页面:提取热榜歌曲的基础信息(歌曲ID等),构造详情页URL;
-
发起二级请求:通过Scrapy.Request()携带基础信息,请求每首歌的详情页;
-
解析二级页面:提取详情页扩展信息,与一级页面数据合并,统一输出。
核心核心依赖:Scrapy框架、正则表达式(re)、Xpath解析、JSON数据解析。
二、开发全过程:踩坑→排查→优化
本次开发最核心的难点的是:首页与详情页的数据存储方式完全不同,初期因惯性思维使用Xpath解析首页,导致爬取失败,后续通过排查源码发现JSON字段存储的规律,切换为正则提取,逐步完成优化。以下是完整踩坑与优化记录,全程还原真实开发场景。
2.1 初始开发:惯性思维踩坑------Xpath解析首页失败
2.1.1 初始思路
基于过往爬取静态网页的经验,默认首页热榜歌曲信息会以HTML标签(如div、li、table)形式渲染,因此初始开发时,直接使用Xpath定位热榜歌曲的各个字段,核心代码如下:
python
def parse(self, response):
# 错误思路:用Xpath定位热榜歌曲
song_list = response.xpath('//div[@class="hot-song"]/li')
for song in song_list:
singer = song.xpath('./div[@class="singer"]/text()').get()
song_name = song.xpath('./div[@class="song-name"]/text()').get()
yield {
'singer': singer,
'song_name': song_name
}
2.1.2 踩坑现象
运行命令 scrapy crawl 5sing 后,控制台无任何数据输出,也无报错信息,排查后发现两个关键问题:
-
Xpath路径无误,但无法提取到任何文本(返回None);
-
查看页面源码(F12),在Elements面板能看到热榜歌曲列表,但在Page Source(页面原始源码)中,找不到对应的HTML标签。
2.1.3 排查过程
-
排除反爬问题:已配置请求头(User-Agent、Cookie),且能正常获取首页源码,排除反爬拦截;
-
分析页面渲染方式:通过对比Elements和Page Source,发现热榜歌曲数据并非静态HTML渲染,而是通过JavaScript动态加载,数据藏在页面源码的JSON字符串中;
-
定位数据位置:全局搜索页面源码(Ctrl+F),关键词"hot",最终发现热榜数据被包裹在
bz_songs = {"download": [...], "hot": [...]}这个JSON结构中,所有热榜歌曲的基础信息都在"hot"对应的列表里。
2.2 优化第一步:切换解析方式------从Xpath到正则提取JSON
2.2.1 核心突破点
首页数据存储方式:JSON字段嵌入页面源码,无法用Xpath解析,需通过正则表达式(re)截取JSON片段,再解析为Python列表,提取所需字段。
2.2.2 优化代码(核心片段)
python
import scrapy
import re
import json
def parse(self, response):
# 1. 获取首页原始源码
html_text = response.text
# 2. 正则提取 "hot": [...] 对应的JSON片段(精准匹配,避免干扰)
pattern = r'"hot":(\[.*?\])' # 捕获hot对应的列表内容
hot_json = re.findall(pattern, html_text, re.S)[0] # re.S让.匹配换行符
# 3. JSON转Python列表,提取基础信息
hot_list = json.loads(hot_json)
print(f"成功提取到{len(hot_list)}首热榜歌曲,开始构造详情页请求...")
# 4. 循环构造详情页URL,发起二级请求
for song in hot_list:
# 用歌曲ID构造详情页URL(踩坑后发现songId是核心标识,无songUrl字段)
detail_url = f"https://5sing.kugou.com/bz/{song['songId']}.html"
# 用meta传递一级页面数据,供详情页解析时合并
yield scrapy.Request(
url=detail_url,
callback=self.parse_detail, # 详情页解析函数
meta={
'singer': song['singerName'],
'song_name': song['songName'],
'user_id': song['userId'],
'user_nickname': song['userNickname']
}
)
2.2.3 踩坑补充:构造详情页URL时的小失误
初期构造详情页URL时,误写为 f"https://5sing.kugou.com/bz/{song['songUrl']}.html",运行后报错"KeyError: 'songUrl'",排查后发现:JSON字段中只有"songId"(歌曲唯一标识),无"songUrl"字段,修正为用songId构造URL后,成功发起二级请求。
2.3 优化第二步:详情页解析------Xpath回归使用,适配HTML结构
2.3.1 关键发现:详情页与首页数据存储方式差异
| 页面类型 | 数据存储方式 | 解析方式 |
|---|---|---|
| 首页(一级页面) | JSON字符串嵌入源码,动态加载 | 正则表达式(re)提取JSON,再解析 |
| 详情页(二级页面) | 标准HTML标签静态渲染 | Xpath解析,直接定位字段 |
2.3.2 详情页解析代码
python
def parse_detail(self, response):
# 1. 接收一级页面传递的基础数据
base_data = response.meta
# 2. Xpath定位详情页扩展信息(分类、格式、大小、下载次数)
category = response.xpath('/html/body/div[5]/div[1]/div[1]/div[3]/div[1]/ul/li[2]/text()').get()
file_format = response.xpath('/html/body/div[5]/div[1]/div[1]/div[3]/div[1]/ul/li[3]/text()').get()
file_size = response.xpath('/html/body/div[5]/div[1]/div[1]/div[3]/div[1]/ul/li[4]/text()').get()
download_count = response.xpath('/html/body/div[5]/div[1]/div[1]/div[3]/div[1]/ul/li[5]/text()').get()
# 3. 合并一级、二级页面数据,统一输出
yield {
'song_id': response.url.split('/')[-1].replace('.html', ''), # 从URL中提取歌曲ID
'singer': base_data['singer'],
'song_name': base_data['song_name'],
'user_id': base_data['user_id'],
'user_nickname': base_data['user_nickname'],
'category': category,
'format': file_format,
'size': file_size,
'download_count': download_count
}
print(f"✅ 完成歌曲《{base_data['song_name']}》详情页爬取,数据已输出")
2.4 优化第三步:添加日志打印,便于调试与监控
优化前,运行爬虫后无法直观看到爬取进度,若出现失败也难以定位问题,因此添加简单清晰的日志打印,监控每一步爬取状态,核心优化如下:
python
def parse(self, response):
print("=" * 50)
print("✅ 首页请求成功,开始提取热榜JSON数据...")
html_text = response.text
pattern = r'"hot":(\[.*?\])'
hot_json = re.findall(pattern, html_text, re.S)[0]
hot_list = json.loads(hot_json)
print(f"✅ 成功提取{len(hot_list)}首热榜歌曲,开始发起详情页请求")
print("=" * 50)
# 后续循环构造请求...
def parse_detail(self, response):
base_data = response.meta
song_name = base_data['song_name']
print(f"🎵 正在爬取详情页:{song_name} | URL:{response.url}")
# 后续Xpath解析、数据合并...
print(f"✅ {song_name} 详情页爬取完成,数据已输出")
2.5 最终优化:规范代码结构,提升可维护性
结合前面的踩坑与优化,整理出完整可运行的爬虫代码,同时规范请求头配置、字段命名(全部改为英文,符合开发规范),避免冗余代码,确保代码可直接复制运行。
三、完整可运行代码
3.1 爬虫文件(spiders/5sing_spider.py)
python
import scrapy
import re
import json
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',
'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/147.0.0.0 Safari/537.36',
'cookie': 'kg_mid=94ae5cb0ff9fda8bd1e89ea8c46853a4; kg_dfid=3MqGF81g2Tq64cy0SV3kT5pr; sl-session=Z1wSDz6f3GmaGwefEC9jzA==; kg_dfid_collect=d41d8cd98f00b204e9800998ecf8427e; ACK_SERVER_10015=%7B%22list%22%3A%5B%5B%22bjlogin-user.kugou.com%22%5D%5D%7D; cct=01259e80; wsp_volume=0.8',
}
}
def parse(self, response):
print("=" * 50)
print("✅ 首页请求成功,开始提取热榜JSON数据...")
html_text = response.text
# 正则精准提取hot对应的JSON列表
pattern = r'"hot":(\[.*?\])'
hot_json = re.findall(pattern, html_text, re.S)[0]
hot_list = json.loads(hot_json)
print(f"✅ 成功提取{len(hot_list)}首热榜歌曲,开始发起详情页请求")
print("=" * 50)
for song in hot_list:
# 用songId构造详情页URL,避免KeyError
detail_url = f"https://5sing.kugou.com/bz/{song['songId']}.html"
# 传递基础数据到详情页
yield scrapy.Request(
url=detail_url,
callback=self.parse_detail,
meta={
'singer': song['singerName'],
'song_name': song['songName'],
'user_id': song['userId'],
'user_nickname': song['userNickname']
}
)
def parse_detail(self, response):
base_data = response.meta
song_name = base_data['song_name']
print(f"🎵 正在爬取详情页:{song_name} | URL:{response.url}")
# Xpath解析详情页扩展信息
category = response.xpath('/html/body/div[5]/div[1]/div[1]/div[3]/div[1]/ul/li[2]/text()').get()
file_format = response.xpath('/html/body/div[5]/div[1]/div[1]/div[3]/div[1]/ul/li[3]/text()').get()
file_size = response.xpath('/html/body/div[5]/div[1]/div[1]/div[3]/div[1]/ul/li[4]/text()').get()
download_count = response.xpath('/html/body/div[5]/div[1]/div[1]/div[3]/div[1]/ul/li[5]/text()').get()
# 合并数据并输出
yield {
'song_id': response.url.split('/')[-1].replace('.html', ''),
'singer': base_data['singer'],
'song_name': base_data['song_name'],
'user_id': base_data['user_id'],
'user_nickname': base_data['user_nickname'],
'category': category,
'format': file_format,
'size': file_size,
'download_count': download_count
}
print(f"✅ {song_name} 详情页爬取完成,数据已输出")
3.2 运行命令
powershell
# 进入Scrapy项目根目录,执行以下命令
scrapy crawl 5sing
四、核心踩坑总结与实战经验
4.1 核心踩坑点
-
惯性思维误区:默认所有页面都能用Xpath解析,忽略了动态加载的JSON数据,导致首页数据提取失败;
-
字段错误:构造详情页URL时,误使用不存在的"songUrl"字段,引发KeyError;
-
调试困难:初期未添加日志打印,爬取失败后无法定位问题所在,效率低下;
-
数据存储差异:未提前分析首页与详情页的数据存储方式,导致解析方式不匹配,浪费开发时间。
4.2 实战优化经验
-
爬取前先分析页面源码:通过Page Source查看数据存储方式(是HTML静态渲染,还是JSON动态加载),再选择对应解析方式;
-
正则提取JSON需精准匹配:使用
r'"hot":(\[.*?\])'精准捕获目标JSON片段,避免匹配到无关内容,同时添加re.S适配换行符; -
多级请求数据传递:使用Scrapy的meta参数,将一级页面的基础数据传递到二级页面,实现数据合并;
-
添加日志打印:关键节点(请求成功、数据提取、详情页爬取)添加日志,便于调试和监控爬取进度;
-
规范字段命名:全部使用英文字段,符合Python开发规范,避免中文字段引发的编码问题。
4.3 延伸思考
本次爬取的5sing伴奏网,首页用JSON存储热榜数据、详情页用HTML存储详情数据,这种混合存储方式在很多网站中都很常见(首页追求加载速度,用JSON动态渲染;详情页追求稳定性,用静态HTML渲染)。
后续可进一步优化方向:
-
添加数据持久化:将爬取的数据保存到CSV、JSON文件或数据库(如SQLite);
-
完善反爬策略:添加请求延迟、随机User-Agent,避免频繁请求被封IP;
-
异常处理:添加try-except捕获解析失败、请求失败等异常,提升爬虫稳定性。
五、总结
本次Scrapy多级请求实战,核心突破了"首页JSON动态加载、详情页HTML静态渲染"的混合数据存储解析难题,从最初的Xpath解析失败,到通过排查源码发现JSON字段,再到切换为正则提取、完善二级请求和日志监控,全程踩坑但收获颇丰。
对于爬虫新手而言,最容易陷入"惯性解析"的误区,忽略页面数据存储的差异,本次实战也再次验证:爬取前的页面分析,远比盲目编写代码更重要。掌握Scrapy多级请求、正则提取JSON、Xpath解析HTML的核心技巧,能应对大多数网站的爬取需求,后续可结合实际场景,进一步优化爬虫的稳定性和效率。
如果本文对你有帮助,欢迎点赞、收藏、评论,关注我,持续分享Scrapy实战干货与爬取技巧!