由於需要在公司的內網進行神經網路建模試驗(https://www.cnblogs.com/NosenLiu/articles/9463886.html),為了更方便的在內網環境下快速的查閱資料,構建深度學習模型,我決定使用爬蟲來對深度學習框架keras的使用手冊進行爬取。 keras中文文檔的地址是 ...
由於需要在公司的內網進行神經網路建模試驗(https://www.cnblogs.com/NosenLiu/articles/9463886.html),為了更方便的在內網環境下快速的查閱資料,構建深度學習模型,我決定使用爬蟲來對深度學習框架keras的使用手冊進行爬取。
keras中文文檔的地址是 http://keras-cn.readthedocs.io/en/latest/ ,是基於英文原版使用手冊https://keras.io/,由國內眾多學者進行翻譯所得,方便大家在學習和工作中快速的進行查閱。
在編寫爬蟲之前,我們需要對網站的源碼進行分析,以確定抓取策略。
首先,網頁分為左右兩個部分,並且網站的大部分有效地址基本上都是集中在頁面左側的索引中,以<li class="toctree-l1 "></li>標簽進行包圍。
根據網站的這個特征,我們可以不使用傳統的 URL管理器+網頁下載器+解析器 的傳統遞歸爬取模式,化繁為簡,一次性的獲取索引中所有的待爬取url。
其次,該頁面的url不同於我們平時所瀏覽的.html或.jsp文件,通過瀏覽器的查看元素操作,可以知道該url所對應的是一個事件。應該是類似於一個action指令,伺服器根據這個傳入參數,來動態的返回頁面。
為了正確的獲取動態頁面的內容,我們設計使用基於selenium+phantomJS的python語言爬蟲來完成全站爬取任務。
selenium 是一個用於Web應用程式測試的工具。Selenium測試直接運行在瀏覽器中,就像真正的用戶在操作一樣。支持的瀏覽器包括IE(7, 8, 9, 10, 11),Mozilla Firefox,Safari,Google Chrome,Opera等[1]。
phantomJS是一個基於 WebKit(WebKit是一個開源的瀏覽器引擎,Chrome,Safari就是用的這個瀏覽器引擎) 的伺服器端 JavaScript API,python可以使用selenium執行javascript,selenium可以讓瀏覽器自動載入頁面,獲取需要的數據。
關於selenium與phantomJS的用法在網上有很多講解,本文不再贅述,僅針對該全站爬取任務進行闡述。
動態頁面與靜態頁面的分析
不同於單個網頁的下載,全站爬取的難點在於如何在爬取之後保存網頁之間的正確調用關係(即點擊超鏈接能夠正確的進行頁面跳轉)。在目標網站 keras中文文檔中,伺服器通過傳遞進來的action,使用servlet進行應答,返回對應的頁面(筆者web開發的功底不牢固,只能描述大概流程,伺服器運行具體細節難以描述清楚 =。=# )。而將這些動態頁面的信息以靜態方式進行存儲後,只有把它們放在正確的相對路徑下,才能夠在流量器中正常使用,因此在下載頁面的時候,需要完成以下兩個工作:
工作1.獲取頁面所在的相對路徑,並且給頁面命名。通過對頁面的源代碼進行瀏覽,我們可以發現,每個頁面的url就是它以/latest/為根目錄的相對路徑。
圖1 網站主頁面上的序貫模型url (相對路徑)
圖2 序貫模型頁面的真實url (絕對路徑)
根據這個特征,我們可以設計相對的函數,來獲取所有待爬取頁面的真實url。此外,為了能夠對頁面進行正確的保存,需要給文件進行命名,這裡將所有頁面名稱定位info.html。例如,序貫模型的頁面在本地就存儲在 ./latest/getting_started/sequential_model/info.html 文件中。
工作2.將頁面存儲到本地時,將其中的超鏈接地址改為目標靜態頁面的相對路徑。例如,對於主頁 http://keras-cn.readthedocs.io/en/latest/,它的序貫模型索引的url如下:
而對於我們所爬取下來的靜態主頁 ./latest/info.html 來說,它的序貫模型索引的url如下:
我們需要精確的指向該頁面在本地目錄中所保存的地址。
註意:我們只修改以<li class="toctree-l1 "></li>標簽進行環繞的超鏈接<a>,其他類似href=”#keras-cn”的鏈接只是JavaScript的一個位置移動操作,並不會對新的頁面進行載入(這一點我花了好久時間才看懂,之前一直以為需要對 #keras-cn新建一個路徑,再對其頁面進行靜態保存……)。
做完了上述工作,就可以對網頁進行爬取了,但此時,爬取出的網頁大概是這個樣子:
這是因為我們此時並沒有下載網頁的樣式文件.css與.js文件,導致一片白板。繼續觀察網頁源碼,發現該網站下所有的頁面,其.css文件與.js文件路徑都在頁面的<head>標簽內進行規定,且均指向/lastest/css/文件夾與/latest/js/文件夾。因此我們只要在存儲網站主頁的時候,對.css與.js文檔存儲一次即可。
整個網站爬取的流程如下:
①使用selenium+phantomJS打開根頁面,獲取頁面左側索引的全部url,將其存儲在url_list中。
②調用頁面保存函數,對根頁面進行保存。
③下載<head>標簽內的 .css 與 .js 文件。
④迴圈遍歷url_list中的頁面地址,使用selenimu的webdriver進行打開,調用頁面保存函數對頁面內容進行保存。
註意事項:
1.獲取索引URL時,由於href給出的是相對路徑,需要將相對url拼接為絕對url再存入url_list。
2.存取網頁時,根據<head>中的<meta charset="utf-8">,需要將頁面使用utf-8編碼進行保存。具體語法如下:
1 with open(save_path+page_name,'wb') as f_in: 2 f_in.write(driver.page_source.encode('utf-8'))
3.每爬取一個頁面,暫停一段時間,這既是互聯網上的禮節禮貌問題,也降低了自己被反爬措施限制的風險。
time.sleep(3) # 勿頻繁訪問,以防網站封禁
在我第一次爬取tensorflow手冊時,沒有設置訪問延遲,被網站鎖了一個星期不能訪問,都是血淚教訓~。
4.通過代碼下載的.css和.js文件有可能不全,我通過右鍵網頁→頁面另存為,獲取了完整的js和css文件,將其移動到對應的/latest/css/和/latest/js/路徑下即可。
具體實現:
①獲取絕對url函數:
1 def get_abs_url(url,href): 2 if '../' in href: 3 count = 0 4 while('../' in href): 5 count += 1 6 href = href[3:] 7 for i in range(count): 8 if url[-1]=='/': # 去除掉url最後一個 '/' 9 url = url[:-1] 10 rare = url.split('/')[-1] 11 url = url.split(rare)[0] 12 if href[-1]=='/': 13 return url+href[:-1] 14 else: 15 return url+href 16 elif './' in href: 17 href = href[2:]
使用該函數,對不同類型的相對路徑進行解析,獲取能正確訪問對應頁面的絕對url。
②保存數據函數(主要用於保存css文件和js文件)
1 def save_data(driver, path): # 這個path是指/latest/路徑之後的path。 頁面的話要加上 路徑/info.html 2 if path[-4:]=='.ico': 3 with open('./latest/'+path,'wb') as f_in: 4 f_in.write(driver.page_source) 5 elif path[-4:]=='.css' or path[-3:]=='.js': 6 with open('./latest/'+path,'wb') as f_in: 7 f_in.write(driver.page_source.encode('utf-8')) 8 else: 9 with open('./latest/'+path+'/info.html','wb') as f_in: 10 f_in.write(driver.page_source.encode('utf-8'))
③保存頁面函數,根據路徑和頁面內容,來對頁面進行保存。並且對頁面中的url地址進行修改,以便正確的調用靜態頁面。
1 def save_page(driver,save_path): 2 with open(save_path+page_name,'wb') as f_in: 3 f_in.write(driver.page_source.encode('utf-8')) 4 temp_file_lines = [] 5 # 下麵這一步把頁面中的 'layers/pooling_layer/' 改為 './layers/pooling_layer/info.html' 以方便靜態調用 6 with open(save_path+page_name,'r', encoding="utf-8") as f_in: 7 f_lines = f_in.readlines() 8 for i in range(len(f_lines)): 9 if 'toctree-l1' in f_lines[i] and "href=\".\"" not in f_lines[i+1]: 10 temp_loc = f_lines[i+1].split('"')[3] 11 new_loc = './'+temp_loc+page_name 12 f_lines[i+1] = f_lines[i+1].split(temp_loc)[0] + new_loc + f_lines[i+1].split(temp_loc)[1] 13 temp_file_lines.append(f_lines[i].encode('utf-8')) 14 with open(save_path+page_name,'wb') as f_in: 15 f_in.writelines(temp_file_lines)
④文件路徑獲取函數
1 def get_save_path(url): # 將url變為相對的文件保存路徑。 2 if url[-1]!='/': 3 url = url+'/' 4 rare = url.split(root_url)[1] 5 path = root_dir+rare 6 return path
通過該函數獲取靜態頁面存儲路徑(相對路徑)。
另外還有一些邏輯直接寫在了main函數里,如通過BeautifulSoup解析url地址:
1 driver = webdriver.PhantomJS() 2 driver.get(root_url) 3 li_list = BeautifulSoup(driver.page_source,'html.parser').find_all('li',class_='toctree-l1')
通過<head>標簽解析.css與.js文件地址:
1 # TODO 在head標簽中尋找 .css 及 .js 2 link_list = BeautifulSoup(driver.page_source,'html.parser').find('head').find_all('link') 3 script_list = BeautifulSoup(driver.page_source,'html.parser').find('head').find_all('script') 4 css_list = [] # 存儲css文件 5 for link in link_list: 6 href = link['href'] 7 if 'https://' in href: 8 css_list.append(href) 9 else: 10 css_list.append(get_abs_url(root_url,href)) 11 js_list = [] # 存儲 js 文件 12 for js in script_list: 13 try: 14 href = js['src'] 15 except: 16 continue 17 if 'https://' in href: 18 js_list.append(href) 19 else: 20 js_list.append(get_abs_url(root_url,href))
具體的代碼可從我的GitHub上進行下載。
https://github.com/NosenLiu/crawler_keras
其中的main.py便是程式代碼,python3實現。
[1] https://blog.csdn.net/qq_29186489/article/details/78661008 selenium用法詳解