标题 python爬虫进阶–动态网页和正则表达式
介绍
上一篇使用最简单的方法爬取了唱吧一些歌曲,本篇介绍如何爬取更多歌曲,主要是以下两个问题。
- 如何爬取动态加载的网页数据?
- 如何解析出网页内嵌的script代码中的数据?
分析一:
- 打开我的唱吧主页,下拉,点击加载更多
- 点击加载更多发现url并没有发生变化,但网页确实请求到了更多数据
- 查了资料发现这是一种名叫ajax的技术,以下是百度百科的解释
- 新的请求在网页审查元素的XHR中可以看到:进入(chrome) F12 -> network -> xhr
此时点击加载更多
- 这里就是网页真实的请求数据,在preview栏可以看到响应的预览信息,在headers中可以看到真实的请求url
- 分析这个请求url,发现其中有一些get方式提交的参数如:pageNum=1 userid=43649980
盲猜 一个是页码,一个是用户id,继续点击加载更多,不难发现新的请求url pageNum=2
手动将pageNum改为0进行访问,发现竟然真的是第0页(默认加载的)的数据
OK,到了这里就有思路了,接下来开始写代码。
代码实现一
import requests
from bs4 import BeautifulSoup
import time
my_header = {
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.193 Safari/537.36'
}
url_head = 'http://changba.com/s'
user_id = '43649980' #用户id
song_list ={}
index = 0
for n in range(2): #爬取前2页
url = f'http://changba.com/member/personcenter/loadmore.php?ver=1&pageNum={n}&type=0&userid={user_id}&curuserid=-1'
res = requests.get(url, headers=my_header)
# 将response对象转为列表
json_song = res.json()
# 循环获取歌曲信息
for song_info in json_song:
# 歌曲名称
song_name = song_info['songname']
# 歌曲播放页链接
song_id = song_info['enworkid']
# 歌曲图片
song_img = song_info['mvCover']
由于请求得到的数据是json格式,因此需要先转换为可解析的格式
其中json_song = res.json()的意思是将json格式的数据转为列表格式以便进行解析
至此可以获取到的数据有:songname / mvCover / workid / enworkid等
可以拿这里的workid像上篇 url = “固定url前缀 + workid + .mp3” 这样去拼接,不过这样只能获取到符合这种规则的歌曲url ,而不符合的就没办法了,接下来就是第二个问题。
分析二
- 来到歌曲播放页面:分析url ,发现就是 changba.com/s/ + enworkid 的简单拼接
(其中 enworkid 是上面已经获取到的)那么我们可以方便的来到该页面,继续请求数据
- 分析该页面的源码
在audio标签中 有歌曲源文件的url ,拿到这个url 直接获取并按照二进制数据保存就是歌曲文件本件,然而现实并没有那么简单,requests.get请求到该页面源码,find查找audio标签,发现里面的src属性是空的,为什么呢?
- F12进入 network -> all 标签中,刷新网页 此时浏览器重新发起请求,可以看到获取到的响应数据有很多种类,大致就是有图片、html 、css 、 js等
如图,document 对应html文件,即网页源代码, png是一些图片资源,stylesheet 是css,用来设置网页的样式,script就是 js 代码;实际上html中通常也包含部分css代码和js代码。html+css+js 共同组成前端。浏览器在接收到响应的数据后,会执行js代码,对网页进行渲染,我们看到的网页通常是已经经过浏览器渲染,加载过js代码之后的网页。在网页上点右键,有显示网页源代码和检查两个选项,其中检查与F12是一样的,显示的都是经过浏览器渲染后的网页的代码,而显示网页源代码 跳转查看的是才是真正的未经渲染的网页源代码。那么就不难想到,网页源代码中的 audio标签中的src 开始确实是空值,js代码执行的时候将对应的数据填充上去了,所以我们直接获取到的网页源代码中解析不到 audio的src值。
说了这么多,该如何获取我们要的url呢。
查了资料,总结有以下两种方法:- 使用模拟操作浏览器的库如selenium, 它更像是在自动操作浏览器发起请求,获取数据,使用比较简单粗暴,缺点是严重拖慢爬虫速度。
- 使用python中类似PyExecJS、PyV8、Js2Py等可以执行js代码的库,直接执行js代码。
本项目中,使用了另一种取巧的方法(主要是因为菜,嫌弃上面方法1,又不会用方法2):
因为分析发现,我们的目标就藏在网页源代码内嵌的 js 代码中,只需要将其解析出来即可。
代码实现二
由于目标数据在js代码中,无法像解析普通标签一样将内容解析出来,这里使用正则表达式进行解析,很简单,直接上代码:
# 拼出播放页面的url
play_url = 'http://changba.com/s/' + song_id
# 继续爬取
play_res = requests.get(play_url, headers=my_header)
# 使用正则表达式,获取播放页中的url
result = re.findall(r'var a="(.*?)",', play_res.text)
# 判断是否获取成功
try:
res_source_song = requests.get(result[0])
except Exception as e:
print('失败2,not found')
continue
else:
if res_source_song.status_code == 200:
index += 1
with open(f'./song/{index}' + song_name + '.mp3', 'wb') as song_f:
song_f.write(res_source_song.content)
print(song_name + ' 下载成功')
# 注意我们是在学习,不是在攻击别人服务器,一定要加个延时
time.sleep(0.5)
re.findall(r'var a="(.*?)",', play_res.text)
这句代码的意思是使用正则表达式匹配 var a=" 与 ", 之间的所有字符串,通过ctrl + F 在网页源码中搜索可知,这个条件可以精确定位到目标字符串。
至此已经成功获取到想要的数据。
正则表达式
补充正则表达式的常用方法:
- 普通字符的搜索和字符串搜索完全一致,所以我们主要看特殊字符的使用
''' 普通字符的搜索和字符串搜索完全一致,所以我们主要看特殊字符的使用 说明位置的特殊字符 - ^表示行首: ^hello 匹配 hellohello 中的第一个 hello - $表示行尾 hello$ 匹配 hellohello 中的第二个 hello 说明数量的特殊字符 - ? 表示 0 个或者 1 个,例如 ab?c 匹配 ac 和 abc - + 表示 1 个或更多个,例如 ab+c 匹配 abc\abbc\ab...c - * 表示 0 个或更多个,例如 ab*c 匹配 ac\abc\ab...c - {n} 表示匹配 n 个, ab{3}c 只匹配 abbbc - {n,} 表示匹配最少 n 个,+ 对应 {1,},* 对应 {0,} - {n,m} 表示匹配 n 到 m 个,? 匹配 {0,1} 说明类型的特殊字符 - [a-zA-Z] 表示一个大小写字母,例如前面的例子匹配任何一个字母 - [^a-z] 表示除了小写的字母以外的所有字符 - \d \D \d 对应 [0-9], \D 对应[^0-9] - \s \S \s 对应 [\n\r\t] \S对应[^\n\r\t] - \w \W \w 对应 [0-9a-zA-Z_] \W 对应[^0-9a-zA-Z_] - . 表示任意一个字符 '''
- re 模块中的函数
# 尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match()就返回none。 re.match(pattern, string, flags=0) # re.search 扫描整个字符串并返回第一个成功的匹配。 re.search(pattern, string, flags=0) # re.sub用于替换字符串中的匹配项 re.sub(pattern, repl, string, count=0, flags=0) # 在字符串中找到正则表达式所匹配的所有子串,并返回一个列表,如果没有找到匹配的,则返回空列表。 findall(string[, pos[, endpos]]) # split 方法按照能够匹配的子串将字符串分割后返回列表,它的使用形式如下:
总结
以上是作为小白的我,近两天学习python爬虫,所学到的一些知识,通过小项目的方式写出来,其中也包含了一些自己的理解,学的比较浅,有不正确的地方欢迎指正,也欢迎有兴趣的朋友多多交流。