python3爬虫第二弹-多线程爬取网站歌曲

一、简介

近日,迷惑墙报道了一个神奇的网站,网站域名为www.ustcnet.com,没错,就是ustcnet…访问网站之后发现,该网站的ustc实际上是指Unexceptionable Songs Taken from Cartoon,看到这么多好听的动漫歌曲并且可以直接下载,肥宅病顿时复发了😆😆😆,博主遂决定写一个爬虫来批量下载所有的歌曲,以作收藏。遗憾的是,该网站从2014年开始已经停更了,所以内容较少。

二、使用的环境

  • python3.9.0
  • re库(正则表达式)
  • requests库(请求特定网页)
  • threading库(多线程爬取,加快速度)
  • bs4库(主要使用BeautifulSoup解析网页结构)

三、网页解析

1、获取网页的最大页数

在网站主页最底部可以看到,会显示网站的最大页数以及当前页数,因此可以在源码中找到这些信息。点击鼠标右键查看网页源代码,ctrl+F搜索“/2",就能在网页源码中找到该位置。如图:
在这里插入图片描述
有了这一信息之后,就可以使用正则表达式在获取到的网页源码文本中搜索相关信息,从而找到最大页面,使用的表达式是:

1
pattern=re.compile(r'(?<=</b></font>/)\d+')

其中表达式(?<=exp)表示的是先定位到exp表达式的位置,然后从后面开始匹配,\d+表示匹配1到多个数字,满足贪婪匹配。然后通过该表达式对获取的网页源码进行匹配,就能够得到最大页数。

2、获取每一页的url形式

在主页上点击下一页,可以很容易地发现网站每一页网页的形式为:

1
'http://www.ustcnet.com/index.asp?page={}'.format(page_count)

同时有了最大页数,就可以批量生成网站每一页地url,然后对每页进行访问提取需要的信息。

3、获取每首歌曲的相关信息

通过网页调试工具发现,网页每一首歌曲的信息存放在一条tr标签中,同时可以根据标签的onmouseover属性来定位这一标签。
在这里插入图片描述
每个tr标签中包含一系列td标签,第2、3、4个td标签分别存储了歌曲的歌曲名、歌曲出处和歌手。
在这里插入图片描述
有了这些信息之后,就可以通过BeautifulSoup查找这些标签,从而获取这些信息,用于文件的命名。

4、获取下载的链接

通过网页调试工具发现,歌曲的下载链接存放在target属性为_blank的a标签中,因此获得了这一链接之后就可以批量生成每一首歌曲的下载链接。

在这里插入图片描述
这里还有一个小插曲,点进去这个链接之后不是直接下载,而是跳到了一个新的页面,如图,在这个页面中的a标签存放着歌曲下载的正确的url。

在这里插入图片描述

四、代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import re
import requests
import threading
from bs4 import BeautifulSoup

def GetHtmlText(url):
# 根据传入的url请求网站,并返回得到的数据
try:
user_agent = {'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36'}
rspon = requests.get(url, headers = user_agent)
rspon.encoding = 'GBK' #这里需要使用GBK编码,否则文字会出现乱码
rspon.raise_for_status()
except:
print('网页获取失败:', rspon.status_code)
return None
return rspon

def GetMaxPageCount():
# 获取主页信息,并且获取网站的最大页数
max_page_count = 0
url = 'http://www.ustcnet.com/'
html=GetHtmlText(url)
if html==None:
return max_page_count
#print(html.text)
pattern=re.compile(r'(?<=</b></font>/)\d+') #正则表达式匹配最大页数
match=pattern.search(html.text)
max_page_count=int(match.group(0))
return max_page_count

def SaveSongInUrl(song_url,song_name,song_path):
# 根据传入的url链接获取歌曲的二进制数据,并且根据传入的路径和文件名将文件写入到对应的路径中。
source = GetHtmlText(song_url)
if source == None:
return
file_name = '{}.wma'.format(song_name)
file = open(song_path+file_name, "wb") #以二进制写的方式打开文件。
file.write(source.content) #由于歌曲是以二进制存储的,这里需要写入二进制数据
file.close()

def GetOnePageSong(page_count, song_path):
# 从返回的网页数据中获取每张图片的相关信息以及图片下载的url,然后调用相关函数下载图片
url = 'http://www.ustcnet.com/index.asp?page={}'.format(page_count)
suop = BeautifulSoup(GetHtmlText(url).text, 'html.parser')
tag_container = suop.find_all('tr', {'onmouseover':'this.style.backgroundColor=\'#eeeeee\''}) #搜索存放每首歌曲信息的tr标签
thread_list=[] #存放所有下载线程的列表
for child in tag_container:
tag_song_inf=child.find_all('td') #获取歌名、出处和歌手信息
song_number=tag_song_inf[0].string
song_name=tag_song_inf[1]['title'] #第一个td标签存放歌名
song_from=tag_song_inf[2]['title'] #第二个td标签存放出处
song_sing=tag_song_inf[3].string #第三个td标签存放歌手
file_name=song_name+'_'+song_from+'_'+song_sing #将以上信息拼接成歌曲名
tag_download=child.find('a',{'target':'_blank'}) #查找存放下载链接的a标签
download_url='http://www.ustcnet.com/'+tag_download.attrs['href']
target_soup=BeautifulSoup(GetHtmlText(download_url).text, 'html.parser')
target_a=target_soup.find('a')
if target_a==None:
continue
target_url=target_a.attrs['href']
t=threading.Thread(target=SaveSongInUrl,args=(target_url,file_name,song_path)) #创建一个歌曲下载的线程
t.start()
thread_list.append(t)
print('page:{}\tnumber:{}\tthreading num:{}\tis downloading...'.format(page_count,song_number,len(thread_list)))
alive_thread_count=len(thread_list) #保存当前存活的线程数
while alive_thread_count>5: #最大运行线程数不大于5,否者循环直到当前线程数为5
for i in thread_list:
if not i.is_alive():
alive_thread_count-=1
thread_list.remove(i)
print('{} downloading threading remain...'.format(alive_thread_count))
#函数退出之前保证所有线程结束
alive_thread_count=len(thread_list)
while alive_thread_count>0:
for i in thread_list:
if not i.is_alive():
alive_thread_count-=1
thread_list.remove(i)
print('{} downloading threading remain...'.format(alive_thread_count))

def GetAllPageSong(song_path):
# 爬取所有页面的歌曲,并保存在输入的参数路径下
max_page_count = GetMaxPageCount()
for page_index in range(1, max_page_count+1):
GetOnePageSong(page_index, song_path)

def main():
song_path='E:/ustcnetsong/'
GetAllPageSong(song_path)

main()

运行发现有些歌曲下载时会出现404错误,访问网站发现是由于网站本身的原因。