最近寫了好幾個簡單的爬蟲,踩了好幾個深坑,在這里總結一下,給大家在編寫爬蟲時候能給點思路。本次爬蟲內容有:靜態頁面的爬取。動態頁面的爬取。web自動化終極爬蟲。
數據獲取(主要靠爬蟲)
數據存儲(python excel存儲)
分析步驟
1 . 打開百度音樂:http://music.baidu.com/
2 . 打開瀏覽器調試模式F12,選擇Network+all模式
3 . 搜索框搜索歌曲(beat it),查看控制臺
4 .通過以上分析:獲取到有效信息:
5 .通過有效信息來設計爬蟲,獲取數據
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樹卻能找到想要的數據,這時候我們是遇到了動態網頁,數據是在動態加載進去的。無法獲取網頁數據。
目前解決方案有兩種:
- 通過查看訪問動態數據接口來獲取數據。
- 通過web自動化工具來獲取網頁源代碼以獲取數據。
(目前網易云簡單通過訪問url已經不能獲取到數據了,我們可以采用web自動化工具selenium和PhantomJS來實現網頁源代碼的獲取)
過濾請求為XHR,發現請求name怎么都一樣,這時候我們翻看這些name,查看到Request URL里找到關鍵字search的請求,這個請求是一個POST請求。這個應該就是獲取搜索數據的接口,通過查看response或者preview來查看請求返回結果。正是我們想要的。
我們先不要高興的太早了,目前我們還沒有搞清楚Form Data是怎么構成的。params + encSecKey到底是怎么生成的。在看過網絡上有關抓取網易評論的爬蟲《如何爬網易云音樂的評論數?》,得知這個網易針對api做了加密處理。由于個人道行太淺參悟不透這里的加密參數順序和內容。因此這個方案就此作罷。實在不甘心,只好換方案二。
既然方案一暫時走不通,也不能影響我們的工作進度,換個思路繼續走,想到使用web自動化測試工具selenium可以實現模擬人工操縱瀏覽器。這樣導出網頁數據應該不是問題,想到立馬動手。
2 .安裝PhantomJS
PhantomJS是一個基于webkit的JavaScript API。它使用QtWebKit作為它核心瀏覽器的功能,使用webkit來編譯解釋執行JavaScript代碼。任何你可以在基于webkit瀏覽器做的事情,它都能做到。它不僅是個隱形的瀏覽器,提供了諸如CSS選擇器、支持Web標準、DOM操作、JSON、HTML5、Canvas、SVG等,同時也提供了處理文件I/O的操作,從而使你可以向操作系統讀寫文件等。PhantomJS的用處可謂非常廣泛,諸如網絡監測、網頁截屏、無需瀏覽器的 Web 測試、頁面訪問自動化等。
目前官方支持三種操作系統,包括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
方案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:
在獲取了對象之后添加對該對象的清除方法(username.clear()、password.clear())
實施結果
方案1失敗。原因不明了,多半是webdriver對PhantomJS兼容性不好。
方案2:
更換瀏覽器,本次選擇使用chrome瀏覽器進行自動化操作。
安裝chrome自動化控制插件。
# 初始化瀏覽器driverdriver = webdriver.Chrome()
本以為這樣就可以獲取到數據了。燃鵝,還是沒有獲取到,又報錯了(如下圖)
到這里:就應該查看請求了,找到token是什么。并嘗試添加token到請求頭中。
查看cookies
可是在我們登錄后的cookies列表中卻沒有這個cookie!
預測這個cookie應該是在web播放器加載時種下的。驗證一下:
由上表可知。該token在加載播放器的時候種下的。
到這里問題,解決一大半了。