爬虫实战:爬虫之 web 自动化终极杀手 ( 上)

欢迎大家前往腾讯云技术社区,获取更多腾讯海量技术实践干货哦~

作者:陈象 

导语:

最近写了好几个简单的爬虫,踩了好几个深坑,在这里总结一下,给大家在编写爬虫时候能给点思路。本次爬虫内容有:静态页面的爬取。动态页面的爬取。web自动化终极爬虫。

分析:

数据获取(主要靠爬虫)

  • 静态网页爬取
  • 动态网页爬取

数据存储(python excel存储)

  • Python Excel操作,保存结果

数据获取实战:

百度音乐(静态网页)

分析步骤
1 . 打开百度音乐:http://music.baidu.com/

2 . 打开浏览器调试模式F12,选择Network+all模式

3 . 搜索框搜索歌曲(beat it),查看控制台

  • 过滤请求:ctrl + f 输入搜索关键字
  • 依照请求接口的特点,查看主请求
  • 分析请求(reques headers、Query string、response)

4 .通过以上分析:获取到有效信息:

  • 歌曲搜索请求接口为http://music.baidu.com/search?key=歌曲名
  • 获取请求方式(post、get)百度音乐搜索歌曲为get请求
  • 请求headers(伪装浏览器,避免被拒绝请求)
  • 请求返回结果(html or json)百度音乐的返回结果为html。

5 .通过有效信息来设计爬虫,获取数据

  • 伪装浏览器。需要导入requests库,比起urllib、urllib2等库更加方便,这里不做赘述。要点是添加请求头(User-Agent、Host等)
  • 发起get请求
  • 等待请求返回
  • 处理返回数据。因为百度音乐才用的是html作为返回数据。因此要祭出我们的BeautifulSoup(SoupBeautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库.它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式.Beautiful Soup会帮你节省数小时甚至数天的工作时间。这可是爬虫的福音,终于不用再写那么复杂的正则表达式了—-引用,详细教程可访问SoupBeautiful Soup 教程进行学习,本次使用的方法不多,会在后边介绍。)
  • 将解析到的有用数据进行保存。

代码实现

1 .View 提供准对参数url进行访问并返回结果的方法

def view(url):

     '''
     :param url: 待爬取的url链接
     :return:
     '''
     # 从url中获取host
     protocol, s1 = urllib.splittype(url)
     host, s2 = urllib.splithost(s1)

     # 伪装浏览器,避免被kill
     headers = {
         'Host': host,
         'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36',
         'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
         'Accept-Encoding': 'gzip, deflate',
         'Accept-Language': 'zh-CN,zh;q=0.8',
     }

     # 代理
     proxies = {
         "http": "dev-proxy.oa.com:8080",
         "https": "dev-proxy.oa.com:8080",
     }
     # 利用requests封装的get方法请求url,并添加请求头和代理,获取并返回请求结果。并进行异常处理
     try:
         res = requests.get(url, headers=headers, proxies=proxies)
         res.encoding = 'utf-8'

         if res.status_code == 200:
             # 请求成功
             # 获取网页内容,并返回
             content = res.text
             return content
         else:
             return None
     except requests.RequestException as e:
         # 异常处理
         print(e)
         return None

2 .search_baidu_song 提供对参数song_name进行歌曲搜索并获取搜索结果

def search_baidu_song(song_name):
     '''
     获取百度音乐搜索歌曲结果
     :param song_name: 待搜索歌曲名
     :return: 搜索结果
     '''

     def analyse():
         '''
         静态网页分析,利用BeautifulSoup,轻松获取想要的数据。需要对html有了解。
         :return:
         '''

         # 初始化BeautifulSoup对象,并指定解析器为 lxml。还有其他的解析器:html.parser、html5lib等
         # 详细教程可访问:http://cuiqingcai.com/1319.html《Python爬虫利器二之Beautiful Soup的用法》
         html = BeautifulSoup(content, "lxml")
         # beautifulsoupzui常用方法之一: find_all( name , attrs , recursive , text , **kwargs )
         # find_all() 方法搜索当前tag的所有tag子节点, 并判断是否符合过滤器的条件
         # tag标签名为'div'的并且标签类名为class_参数(可为 str、 list、 tuple),
         search_result_divs = html.find_all('div', class_=['song-item clearfix ', 'song-item clearfix yyr-song'])

         for div in search_result_divs:
             # find() 方法搜索当前tag的所有tag子节点, 并返回符合过滤器的条件的第一个结点对象
             song_name_str = div.find('span', class_='song-title')
             singer = div.find('span', class_='singer')
             album = div.find('span', class_='album-title')

             # 此部分需要对html页面进行分析,一层层剥开有用数据并提取出来
             if song_name_str:
                 # 获取结点对象内容,并清洗
                 song_name_str = song_name.text.strip()
             else:
                 song_name_str = ''
             if singer:
                 singer = singer.text.strip()
             else:
                 singer = ''
             if album:
                 album = album.find('a')
                 if album:
                     # 获取标签属性值
                     # 方法二:属性值 = album['属性名']
                     album = album.attrs.get('title')
                     if album and album != '':
                         album = album.strip()
                 else:
                     album = ''
             else:
                 album = ''
             # print song_name + " | " + singer + " | " + album
             songInfoList.append(SongInfo(song_name_str, singer, album))

     songInfoList = []
     url = urls.get('baidu_song')
     url1 = url.format(song_name=song_name, start_idx=0)
     content = self.view(url1)
     if not content:
         return []
     analyse(content)
     url2 = url.format(song_name=song_name, start_idx=20)
     content = self.view(url2)
     analyse(content)
     return songInfoList[0:30]

就这样我们获取到了百度网页歌曲搜索结果的数据。然后就是保存数据,这个我们最后再谈谈。

网易云音乐 (动态网页)

在我们以上一种静态网页获取数据方式来获取网易云音乐的数据的时候,可能会遇到这样的问题:网页查看源代码并没有可用的数据,仅仅只有网页的骨架。数据完全找不到,可是打开开发者工具查看DOM树却能找到想要的数据,这时候我们是遇到了动态网页,数据是在动态加载进去的。无法获取网页数据。

目前解决方案有两种:

  1. 通过查看访问动态数据接口来获取数据。
  2. 通过web自动化工具来获取网页源代码以获取数据。
    (目前网易云简单通过访问url已经不能获取到数据了,我们可以采用web自动化工具selenium和PhantomJS来实现网页源代码的获取)

方案一实现(通过查看访问动态数据接口来获取数据):

  1. 打开网易云音乐:http://music.163.com/
  2. 打开浏览器调试模式F12,选择Network+all模式
  3. 搜索框搜索歌曲(beat it),查看控制台

过滤请求为XHR,发现请求name怎么都一样,这时候我们翻看这些name,查看到Request URL里找到关键字search的请求,这个请求是一个POST请求。这个应该就是获取搜索数据的接口,通过查看response或者preview来查看请求返回结果。正是我们想要的。

我们先不要高兴的太早了,目前我们还没有搞清楚Form Data是怎么构成的。params + encSecKey到底是怎么生成的。在看过网络上有关抓取网易评论的爬虫《如何爬网易云音乐的评论数?》,得知这个网易针对api做了加密处理。由于个人道行太浅参悟不透这里的加密参数顺序和内容。因此这个方案就此作罢。实在不甘心,只好换方案二。

方案二实现:

既然方案一暂时走不通,也不能影响我们的工作进度,换个思路继续走,想到使用web自动化测试工具selenium可以实现模拟人工操纵浏览器。这样导出网页数据应该不是问题,想到立马动手。

环境配置

  1. 安装selenium
    推荐使用python包管理工具自动: pip install -y selenium
    其他方式可参考:selenium + python自动化测试环境搭建

2 .安装PhantomJS

PhantomJS是一个基于webkit的JavaScript API。它使用QtWebKit作为它核心浏览器的功能,使用webkit来编译解释执行JavaScript代码。任何你可以在基于webkit浏览器做的事情,它都能做到。它不仅是个隐形的浏览器,提供了诸如CSS选择器、支持Web标准、DOM操作、JSON、HTML5、Canvas、SVG等,同时也提供了处理文件I/O的操作,从而使你可以向操作系统读写文件等。PhantomJS的用处可谓非常广泛,诸如网络监测、网页截屏、无需浏览器的 Web 测试、页面访问自动化等。

下载PhantomJS

目前官方支持三种操作系统,包括windows\Mac OS\Linux这三大主流的环境。你可以根据你的运行环境选择要下载的包

1.安装PhantomJS

下载完成后解压文件,可将phantomjs.exe放在pythond的目录下(C:\Python27\phantomjs.exe)。这样后续加载不需要指定目录。也可以放在特定目录,使用的时候指定phantomjs.exe路径即可。双击打开phantomjs.exe验证安装是否成功。如果出现下图,即安装成功了。

2.代码步骤实现:

  • 初始化浏览器获取网页数据
def dynamic_view(url):
     '''
     使用自动化工具获取网页数据
     :param url: 待获取网页url
     :return: 页面数据
     '''

     # 初始化浏览器driver
     driver = webdriver.PhantomJS()
     # 浏览器driver访问url
     driver.get(url)
     # 坑:不同frame间的转换(网易云在数据展示中会将数据动态添加到'g_iframe'这个框架中,如果不切换,会报"元素不存在"错误。)
     driver.switch_to.frame("g_iframe")
     # 隐式等待5秒,可以自己调节
     driver.implicitly_wait(5)
     # 设置10秒页面超时返回,类似于requests.get()的timeout选项,driver.get()没有timeout选项
     driver.set_page_load_timeout(10)
     # 获取网页资源(获取到的是网页所有数据)
     html = driver.page_source
     # 坑:退出浏览器driver,必须手动退出driver。
     driver.quit()
     # 返回网页资源
     return html

 

  • 解析网页获取数据,同百度音乐(省略)
def search_163_song(song_name):
     pass

同样是通过BeautifulSoup对网页资源进行对象化,在通过对对象的筛选获取得到数据。没想到网易云音乐的数据也能这样拿到。能做到这里已经可以对付大部分网站了。

选用PhantomJS看中其不需要可视化页面,在内存占用上比较省。可是也是出现问题,各位看官请继续往下看。眼看着就要完成了。

3. spotify

  • 使用搜索功能,需要登录账户(比较不好申请,申请好几次都没有成功)
  • 登录成功后,天不遂愿啊,打开spotify搜索页面,竟然没办法查看网页原代码。单独通过请求搜索url: https://open.spotify.com/search/recent 也没办发获取网页数据,会报出权限问题。后边依次解决。

解决方案:

  1. 通过使用web自动化获取数据。
  2. 通过请求动态数据接口来获取数据

方案实施:

方案1:

采用web自动化工具获取数据:配置如同网易云配置,模仿用户操作浏览器进行网页打开,用户登录,进入搜索页面,获取页面数据

def spotify_view(url):
     '''
     使用自动化工具获取网页数据
     :param url: 待获取网页url
     :return: 页面数据
     '''
     spotify_name = 'manaxiaomeimei'
     spotify_pass = 'dajiagongyong'
     spotify_login = 'https://accounts.spotify.com/en/login'
     # 初始化浏览器driver
     driver = webdriver.PhantomJS()

     # 模拟用户登录()
     # 浏览器driver访问登录url
     driver.get(spotify_login)
     # 休息一下等待网页加载。(还有另一种方式:driver.implicitly_wait(3))
     time.sleep(3)

     # 获取页面元素对象方法(本次使用如下):
     #   find_element_by_id : 通过标签id获取元素对象 可在页面中获取到唯一一个元素,因为在html规范中。一个DOM树中标签id不能重复
     #   find_element_by_class_name : 通过标签类名获取元素对象,可能会重复(有坑)
     #   find_element_by_xpath : 通过标签xpath获取元素对象,类同id,可获取唯一一个元素。
     # 获取页面元素对象--用户名
     username = driver.find_element_by_id('login-username')
     # username.clear()
     # 坑:获取页面元素对象--密码
     #   在通过类名获取标签元素中,遇到了无法定位复合样式,这时候可采用仅选取最后一个使用的样式作为参数,即可(稳定性不好不建议使用。尽量使用by_id)
     # password = driver.find_element_by_class_name('form-control input-with-feedback ng-dirty ng-valid-parse ng-touched ng-empty ng-invalid ng-invalid-required')
     password = driver.find_element_by_class_name('ng-invalid-required')
     # password.clear()
     # 获取页面元素对象--登录按钮
     login_button = driver.find_element_by_xpath('/html/body/div[2]/div/form/div[3]/div[2]/button')
     # 通过WebDriver API调用模拟键盘的输入用户名
     username.send_keys(spotify_name)
     # 通过WebDriver API调用模拟键盘的输入密码
     password.send_keys(spotify_pass)
     # 通过WebDriver API调用模拟鼠标的点击操作,进行登录
     login_button.click()
     # 休息一下等待网页加载
     driver.implicitly_wait(3)

     # 搜索打开歌曲url
     driver.get(url)
     time.sleep(5)
     # 搜索获取网页代码
     html = driver.page_source
     return html

 

点击运行之后,一切都风平浪静。突然代码报错了(如下图)。查完资料也做了代码的修改。

网络提供方案

  1. 添加对待输入元素的clear(),清除原有的字符。
  2. 更换浏览器

方案实施:

方案1:

在获取了对象之后添加对该对象的清除方法(username.clear()、password.clear())

实施结果

方案1失败。原因不明了,多半是webdriver对PhantomJS兼容性不好。

方案2:

更换浏览器,本次选择使用chrome浏览器进行自动化操作。

安装chrome自动化控制插件。

  • 下载ChromeDriver插件。
  • 将WebDriver复制到python的安装目录
  • 双击验证安装
  • 修改代码:
  • # 初始化浏览器driver
    driver = webdriver.Chrome()
  • 再次执行代码
  • 实施结果
    成功打开可视化chrome页面并登录成功。

本以为这样就可以获取到数据了。燃鹅,还是没有获取到,又报错了(如下图)

到这里:就应该查看请求了,找到token是什么。并尝试添加token到请求头中。

查看cookies

可是在我们登录后的cookies列表中却没有这个cookie!

预测这个cookie应该是在web播放器加载时种下的。验证一下:

由上表可知。该token在加载播放器的时候种下的。

到这里问题,解决一大半了。

相关阅读

爬虫实战 : 爬虫之 web 自动化终极杀手(下)

反-反爬虫:用几行代码写出和人类一样的动态爬虫

数据科学工具 Jupyter Notebook教程 in Python


此文已由作者授权腾讯云技术社区发布,转载请注明文章出处

原文链接:https://cloud.tencent.com/community/article/759574

 

posted @ 2017-09-21 16:56  腾讯云开发者  阅读(10350)  评论(3编辑  收藏  举报