zoukankan      html  css  js  c++  java
  • Python爬虫实战一之爬取QQ音乐

    一、前言


          前段时间尝试爬取了网易云音乐的歌曲,这次打算爬取QQ音乐的歌曲信息。网易云音乐歌曲列表是通过iframe展示的,可以借助Selenium获取到iframe的页面元素,

      而QQ音乐采用的是异步加载的方式,套路不一样,这是主流的页面加载方式,爬取有点难度,不过也是对自己的一个挑战。

    二、Python爬取QQ音乐单曲


    之前看的慕课网的一个视频, 很好地讲解了一般编写爬虫的步骤,我们也按这个来。

              爬虫步骤

    1.确定目标

    首先我们要明确目标,本次爬取的是QQ音乐歌手刘德华的单曲。

    (百度百科)->分析目标(策略:url格式(范围)、数据格式、网页编码)->编写代码->执行爬虫

    2.分析目标

     歌曲链接:https://y.qq.com/n/yqq/singer/003aQYLo2x8izP.html#tab=song&

    从左边的截图可以知道单曲采用分页的方式排列歌曲信息,每页显示30条,总共30页。点击页码或者最右边的">"会跳转到下一页,浏览器会向服务器发送ajax异步请求,从链接可以看到begin和num参数,分别代表起始歌曲下标(截图是第2页,起始下标是30)和一页返回30条,服务器响应返回json格式的歌曲信息(MusicJsonCallbacksinger_track({"code":0,"data":{"list":[{"Flisten_count1":......]})),如果只是单独想获取歌曲信息,可以直接拼接链接请求和解析返回的json格式的数据。这里不采用直接解析数据格式的方法,我采用的是Python Selenium方式,每获取和解析完一页的单曲信息,点击 ">" 跳转到下一页继续解析,直至解析并记录所有的单曲信息。最后请求每个单曲的链接,获取详细的单曲信息。

    右边的截图是网页的源码,所有歌曲信息都在类名为mod_songlist的div浮层里面,类名为songlist_list的无序列表ul下,每个子元素li展示一个单曲,类名为songlist__album下的a标签,包含单曲的链接,名称和时长等。

    3.编写代码

    1)下载网页内容,这里使用Python 的Urllib标准库,自己封装了一个download方法:

     1 def download(url, user_agent='wswp', num_retries=2):
     2     if url is None:
     3         return None
     4     print('Downloading:', url)
     5     headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'}
     6     request = urllib.request.Request(url, headers=headers)  # 设置用户代理wswp(Web Scraping with Python)
     7     try:
     8         html = urllib.request.urlopen(request).read().decode('utf-8')
     9     except urllib.error.URLError as e:
    10         print('Downloading Error:', e.reason)
    11         html = None
    12         if num_retries > 0:
    13             if hasattr(e, 'code') and 500 <= e.code < 600:
    14                 # retry when return code is 5xx HTTP erros
    15                 return download(url, num_retries-1)  # 请求失败,默认重试2次,
    16     return html


    2)解析网页内容,这里使用第三方插件BeautifulSoup,具体可以参考BeautifulSoup API 。

     1 def music_scrapter(html, page_num=0):
     2     try:
     3         soup = BeautifulSoup(html, 'html.parser')
     4         mod_songlist_div = soup.find_all('div', class_='mod_songlist')
     5         songlist_ul = mod_songlist_div[1].find('ul', class_='songlist__list')
     6         '''开始解析li歌曲信息'''
     7         lis = songlist_ul.find_all('li')
     8         for li in lis:
     9             a = li.find('div', class_='songlist__album').find('a')
    10             music_url = a['href']  # 单曲链接
    11             urls.add_new_url(music_url)  # 保存单曲链接
    12             # print('music_url:{0} '.format(music_url))
    13         print('total music link num:%s' % len(urls.new_urls))
    14         next_page(page_num+1)
    15     except TimeoutException as err:
    16         print('解析网页出错:', err.args)
    17         return next_page(page_num + 1)
    18     return None
     1 def get_music():
     2      try:
     3         while urls.has_new_url():
     4             # print('urls count:%s' % len(urls.new_urls))
     5             '''跳转到歌曲链接,获取歌曲详情'''
     6             new_music_url = urls.get_new_url()
     7             print('url leave count:%s' % str( len(urls.new_urls) - 1))
     8             html_data_info = download(new_music_url)
     9             # 下载网页失败,直接进入下一循环,避免程序中断
    10             if html_data_info is None:
    11                 continue
    12             soup_data_info = BeautifulSoup(html_data_info, 'html.parser')
    13             if soup_data_info.find('div', class_='none_txt') is not None:
    14                 print(new_music_url, '   对不起,由于版权原因,暂无法查看该专辑!')
    15                 continue
    16             mod_songlist_div = soup_data_info.find('div', class_='mod_songlist')
    17             songlist_ul = mod_songlist_div.find('ul', class_='songlist__list')
    18             lis = songlist_ul.find_all('li')
    19             del lis[0]  # 删除第一个li
    20             # print('len(lis):$s' % len(lis))
    21             for li in lis:
    22                 a_songname_txt = li.find('div', class_='songlist__songname').find('span', class_='songlist__songname_txt').find('a')
    23                 if 'https' not in a_songname_txt['href']:  #如果单曲链接不包含协议头,加上
    24                     song_url = 'https:' + a_songname_txt['href']
    25                 song_name = a_songname_txt['title']
    26                 singer_name = li.find('div', class_='songlist__artist').find('a').get_text()
    27                 song_time =li.find('div', class_='songlist__time').get_text()
    28                 music_info = {}
    29                 music_info['song_name'] = song_name
    30                 music_info['song_url'] = song_url
    31                 music_info['singer_name'] = singer_name
    32                 music_info['song_time'] = song_time
    33                 collect_data(music_info)
    34      except Exception as err:  # 如果解析异常,跳过
    35          print('Downloading or parse music information error continue:', err.args)
    
    

    4.执行爬虫

    爬虫跑起来了,一页一页地去爬取专辑的链接,并保存到集合中,最后通过get_music()方法获取单曲的名称,链接,歌手名称和时长并保存到Excel文件中。
    
    

    三、Python爬取QQ音乐单曲总结


    1.单曲采用的是分页方式,切换下一页是通过异步ajax请求从服务器获取json格式的数据并渲染到页面,浏览器地址栏链接是不变的,不能通过拼接链接来请求。一开始想过都通过Python Urllib库来模拟ajax请求,后来想想还是用Selenium。Selenium能够很好地模拟浏览器真实的操作,页面元素定位也很方便,模拟单击下一页,不断地切换单曲分页,再通过BeautifulSoup解析网页源码,获取单曲信息。

    2.url链接管理器,采用集合数据结构来保存单曲链接,为什么要使用集合?因为多个单曲可能来自同一专辑(专辑网址一样),这样可以减少请求次数。

     1 class UrlManager(object):
     2     def __init__(self):
     3         self.new_urls = set()  # 使用集合数据结构,过滤重复元素
     4         self.old_urls = set()  # 使用集合数据结构,过滤重复元素
     5     def add_new_url(self, url):
     6         if url is None:
     7             return
     8         if url not in self.new_urls and url not in self.old_urls:
     9             self.new_urls.add(url)
    10 
    11     def add_new_urls(self, urls):
    12         if urls is None or len(urls) == 0:
    13             return
    14         for url in urls:
    15             self.add_new_url(url)
    16 
    17     def has_new_url(self):
    18         return len(self.new_urls) != 0
    19 
    20     def get_new_url(self):
    21         new_url = self.new_urls.pop()
    22         self.old_urls.add(new_url)
    23         return new_url
     

    3.通过Python第三方插件openpyxl读写Excel十分方便,把单曲信息通过Excel文件可以很好地保存起来。

    1 def write_to_excel(self, content):
    2     try:
    3         for row in content:
    4             self.workSheet.append([row['song_name'], row['song_url'], row['singer_name'], row['song_time']])
    5         self.workBook.save(self.excelName)  # 保存单曲信息到Excel文件
    6     except Exception as arr:
    7         print('write to excel error', arr.args)
    
    

    四、后语


    最后还是要庆祝下,毕竟成功把QQ音乐的单曲信息爬取下来了。本次能够成功爬取单曲,Selenium功不可没,这次只是用到了selenium一些简单的功能,后续会更加深入学习Selenium,不仅在爬虫方面还有UI自动化。

    后续还需要优化的点:

    1.下载的链接比较多,一个一个下载起来比较慢,后面打算用多线程并发下载。

    2.下载速度过快,为了避免服务器禁用IP,后面还要对于同一域名访问过于频繁的问题,有个等待机制,每个请求之间有个等待间隔。

    3. 解析网页是一个重要的过程,可以采用正则表达式,BeautifulSoup和lxml,目前采用的是BeautifulSoup库, 在效率方面,BeautifulSoup没lxml效率高,后面会尝试采用lxml。

    我们一无所成的主要原因是想的太多,而做的太少!

  • 相关阅读:
    当Django模型迁移时,报No migrations to apply 问题时
    django--各个文件的含义
    django--创建项目
    1013. Battle Over Cities (25)
    1011. World Cup Betting (20)
    1009. Product of Polynomials (25)
    1007. Maximum Subsequence Sum (25)
    1006. Sign In and Sign Out (25)
    1008. Elevator (20)
    1004. Counting Leaves (30)
  • 原文地址:https://www.cnblogs.com/taotaoblogs/p/6872508.html
Copyright © 2011-2022 走看看