urilib是python的標准庫,當我們使用Python爬取網頁數據時,往往用的是urllib模塊,通過調用urllib模塊的urlopen(url)方法返回網頁對象,並使用read()方法獲得url的html內容,然后使用BeautifulSoup抓取某個標簽內容,結合正則表達式過濾。但是,用urllib.urlopen(url).read()獲取的只是網頁的靜態html內容,很多動態數據(比如網站訪問人數、當前在線人數、微博的點贊數等等)是不包含在靜態html里面的,例如我要抓取這個bbs網站中點擊打開鏈接 各個板塊的當前在線人數,靜態html網頁是不包含的(不信你查看頁面源代碼試試,只有簡單的一行)。像這些動態數據更多的是由JavaScript、JQuery、PHP等語言動態生成的,因此再用抓取靜態html內容的方式就不合適了。
本文將通過selenium webdriver模塊的使用,以獲取這些動態生成的內容,尤其是一些重要的動態數據。其實selenium模塊的功能不是僅僅限於抓取網頁,它是網絡自動化測試的常用模塊,在Ruby、Java里面都有廣泛使用,Python里面雖然使用相對較少,但也是一個非常簡潔高效容易上手的自動化測試模塊。通過利用selenium的子模塊webdriver的使用,解決抓取動態數據的問題,還可以對selenium有基本認識,為進一步學習自動化測試打下基礎。
一 環境配置
1、下載geckodriver.exe:下載地址:https://github.com/mozilla/geckodriver/releases請根據系統版本選擇下載;(如Windows 64位系統)
2、下載解壓后將getckodriver.exe復制到Firefox的安裝目錄下,
如(C:\Program Files\Mozilla Firefox),並在環境變量Path中添加路徑:C:\Program Files\Mozilla Firefox
3、安裝selenium
pip install selenium
4、beautifulsoup4的安裝,Beautiful Soup 是一個可以從HTML或XML文件中提取數據的Python庫.它能夠通過你喜歡的轉換器實現慣用的文檔導航,
pip install beautifulsoup4
pip install lxml
pip install html5lib
5、安裝faker
pip install faker
二 如何爬取落網某一期數據信息
落網的網址是http://www.luoo.net,我們利用火狐瀏覽器打開該網址,隨便選擇一期音樂,這里我點擊的是搖滾,
然后點擊F12,選擇第989期,進入該頁面,我們就以爬取這一頁的內容為例:
進入之后,我們可以看到該網頁主要包括以下內容:期刊編號,期刊標題,期刊封面,期刊描述,歌單。
通過下方開發工具中的查看器,可以獲取我們感興趣數據的標簽,選擇器等信息。以歌單為例:
程序代碼如下:
# -*- coding: utf-8 -*- """ Created on Thu May 24 16:35:36 2018 @author: zy """ import os from bs4 import BeautifulSoup import random from faker import Factory import queue import threading import urllib.request as urllib from selenium import webdriver import time #"gbk" codec can't encode character "\xXX" in position XX : https://www.cnblogs.com/feng18/p/5646925.html #即字符編碼是utf-8的字節,但是並不能轉換成utf-8編碼的字符串 ''' 爬取網頁信息的類 ''' def random_proxies(proxy_ips): ''' 從proxy_ips中隨機選取一個代理ip args: proxy_ips:list 每個元素都是一個代理ip ''' ip_index = random.randint(0, len(proxy_ips)-1) res = { 'http': proxy_ips[ip_index] } return res def fix_characters(s): ''' 替換掉s中的一些特殊字符 args: s:字符串 ''' for c in ['<', '>', ':', '"', '/', '\\', '|', '?', '*']: s = s.replace(c, '') return s def get_static_url_response_html(url): ''' 爬取靜態頁面數據:https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html#find reture: 返回html代碼 ''' fake = Factory.create() # 這里配置可用的代理IP,可以寫多個 proxy_ips = [ '183.129.151.130' ] headers = {'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Charset':'GB2312,utf-8;q=0.7,*;q=0.7', 'Accept-Language':'zh-cn,zh;q=0.5', 'Cache-Control':'max-age=0', 'Connection':'keep-alive', 'Host':'John', 'Keep-Alive':'115', 'Referer':url, 'User-Agent': fake.user_agent() #'User-Agent': 'User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36' } req = urllib.Request(url=url,origin_req_host=random_proxies(proxy_ips),headers = headers) #當該語句讀取的返回值是bytes類型時,要將其轉換成utf-8才能正常顯示在python程序中 response = urllib.urlopen(req).read() #需要進行類型轉換才能正常顯示在python中 response = response.decode('utf-8') return response def download(url,dstpath=None): ''' 利用urlretrieve()這個函數可以直接從互聯網上下載文件保存到本地路徑下 args: url:網頁文件或者圖片以及其他數據路徑,盡量不要下載網頁,因為下載的是靜態網頁 dstpath:保存全路況 ''' if dstpath is None: dstpath = './code.jpg' try: urllib.urlretrieve(url,dstpath) print('Download from {} finish!'.format(url)) except Exception as e: print('Download from {} fail!'.format(url)) def get_dynamic_url_response_html(url): ''' selenium是一個用於Web應用自動化程序測試的工具,測試直接運行在瀏覽器中,就像真正的用戶在操作一樣 selenium支持通過驅動真實瀏覽器(FirfoxDriver,IternetExplorerDriver,OperaDriver,ChromeDriver) selenium支持通過驅動無界面瀏覽器(HtmlUnit,PhantomJs) 1、下載geckodriver.exe:下載地址:https://github.com/mozilla/geckodriver/releases請根據系統版本選擇下載;(如Windows 64位系統) 2、下載解壓后將getckodriver.exe復制到Firefox的安裝目錄下, 如(C:\Program Files\Mozilla Firefox),並在環境變量Path中添加路徑:C:\Program Files\Mozilla Firefox return: 返回html代碼 獲取url頁面信息后關閉連接 ''' browser = webdriver.Firefox(executable_path = r'D:\ff\geckodriver.exe') #html請求 browser.get(url) html = browser.page_source time.sleep(2) #html = browser.page_source.decode('utf-8') #關閉瀏覽器 browser.quit() return html class UrlSpyder(threading.Thread): ''' 使用Threading模塊創建線程:http://www.runoob.com/python/python-multithreading.html 使用Threading模塊創建線程,直接從threading.Thread繼承,然后重寫__init__方法和run方法: 主要思路分成兩部分: 1.用來發起http請求分析出播放列表然后丟到隊列中 2.在隊列中逐條下載文件到本地(如果需要下載文件) 一般分析列表速度較快,下載速度比較慢,可以借助多線程同時進行下載 ''' def __init__(self, base_url, releate_urls, que=None): ''' args: base_url:網址 releate_urls:相對於網址的網頁路徑 list集合 que:隊列 Python的queue模塊中提供了同步的、線程安全的隊列類,包括FIFO(先入先出)隊列Queue,LIFO(后入先出)隊列LifoQueue, 和優先級隊列PriorityQueue。這些隊列都實現了鎖原語,能夠在多線程中直接使用。可以使用隊列來實現線程間的同步。 ''' threading.Thread.__init__(self) print('Start spider\n') print ('=' * 50) #保存字段信息 self.base_url = base_url if queue is None: self.queue = queue.Queue() else: self.queue = que self.releate_urls = releate_urls def run(self): ''' 把要執行的代碼寫到run函數里面 線程在創建后會直接運行run函數 ''' #遍歷每一個網頁 並開始爬取 for releate_url in self.releate_urls: self.spider(releate_url) print ('\nCrawl end\n\n') def spider(self, releate_url): ''' 爬取指定網頁數據,並提取我們所需要的網頁信息的函數 args: releate_url:網頁的相對路徑 ''' url = os.path.join(self.base_url,releate_url) print ('Crawling: ' + url + '\n') ''' 解析html 針對不同的網址,解析內容也不一樣 ''' #獲取指定網頁的html代碼 response = get_dynamic_url_response_html(url) #response = getUrlRespHtml(url) ) #使用BeautifulSoup解析這段代碼,能夠得到一個 BeautifulSoup 的對象,並能按照標准的縮進格式的結構輸出: #soup = BeautifulSoup(response, 'lxml') soup = BeautifulSoup(response, 'html.parser') #輸出網頁信息 #print(soup.prettify()) #with open('./read.html','w',encoding='utf-8') as f: #兩個內容是一樣的 只是一個是標准縮進格式,另一個不是 #f.write(str(soup.prettify())) #f.write(str(response)) ''' 根據爬取網站不同,下面代碼也會不一樣 解析該網站某一期的所有音樂信息 主要包括: 期刊信息 期刊封面 期刊描述 節目清單 ''' #<span class="vol-number rounded">989</span> num = soup.find('span',class_='vol-number').get_text() #<span class="vol-title">曙色初動</span> 解析標題 find每次只返回第一個元素 title = soup.find('span',class_='vol-title').get_text() ''' <div class="vol-cover-wrapper" id="volCoverWrapper"> <img src="http://img-cdn2.luoo.net/pics/vol/554f999074484.jpg!/fwfh/640x452" alt="曙色初動" class="vol-cover"> <a href="http://www.luoo.net/vol/index/739" class="nav-prev" title="后一期" style="display: inline; opacity: 0;"> </a><a href="http://www.luoo.net/vol/index/737" class="nav-next" title="前一期" style="display: inline; opacity: 0;"> </a> </div> 解析對應的圖片背景 ''' cover = soup.find('img', class_='vol-cover').get('src') ''' <div class="vol-desc"> 本期音樂為史詩氛圍類音樂專題。<br><br>史詩音樂的美好之處在於能夠讓人有無限多的宏偉想象。就像這一首首曲子,用歲月滄桑的厚重之聲,鏗鏘有力的撞擊人的內心深處,綻放出人世間的悲歡離合與決絕!<br><br>Cover From Meer Sadi </div> 解析描述文本 ''' desc = soup.find('div', class_='vol-desc').get_text() ''' <a href="javascript:;" rel="nofollow" class="trackname btn-play">01. Victory</a> <a href="javascript:;" rel="nofollow" class="trackname btn-play">02. Mythical Hero</a> 解析歌單 find_all返回一個列表 所有元素 ''' track_names = soup.find_all('a', attrs={'class': 'trackname'}) track_count = len(track_names) tracks = [] # 12期前的音樂編號1~9是1位(如:1~9),之后的都是2位 1~9會在左邊墊0(如:01~09) for track in track_names: _id = str(int(track.text[:2])) if (int(releate_url) < 12) else track.text[:2] _name = fix_characters(track.text[4:]) tracks.append({'id': _id, 'name': _name}) phases = { 'phase': num, # 期刊編號 'title': title, # 期刊標題 'cover': cover, # 期刊封面 'desc': desc, # 期刊描述 'track_count': track_count, # 節目數 'tracks': tracks # 節目清單(節目編號,節目名稱) } #追加到隊列 self.queue.put(phases) if __name__ == '__main__': luoo_site = 'http://www.luoo.net/vol/index/' spyder_queue = queue.Queue() ## 創建新線程 luoo = UrlSpyder(luoo_site, releate_urls=['1372','1370'], que=spyder_queue) luoo.setDaemon(True) #開啟線程 luoo.start() ''' 從隊列中取數據,並進行下載 ''' count = 1 while True: if spyder_queue.qsize() <= 0: time.sleep(10) pass else: phases = spyder_queue.get() print(phases) #下載圖片 download(phases['cover'],'%d.jpg'%(count)) count += 1
運行結果如下:
並爬取了如下兩種圖片:
注意:我們一般使用 urllib.urlretrieve()函數下載圖片,文件等,但是不要使用這個函數下載網頁,因為下載下來也是靜態網頁。如果要保存網頁html內容,我們使用selenium獲取網頁內容,然后寫入到指定文件:
with open(file_name, 'w',encoding='utf-8') as f: f.write(self.browser.page_source)
三 使用selenium模擬登陸
剛才我們爬取的網站是不需要登陸的,如果遇到一個需要先登錄,才能爬取數據怎么辦?幸運的是selenium提供了模擬鼠標點擊,和鍵盤輸入的功能,下面我們將會演示一個登陸微博的程序,並利用微博進行搜索我們需要的內容,並把數據保存下來。
下面我先列入完整代碼,一會一點點講解:
# -*- coding: utf-8 -*- """ Created on Thu May 24 16:35:36 2018 @author: zy """ import os import time from selenium import webdriver #from selenium.common.exceptions import NoSuchElementException from bs4 import BeautifulSoup class WeiboSpyder(): ''' python爬蟲——基於selenium用火狐模擬登陸爬搜索關鍵詞的微博:https://blog.csdn.net/u010454729/article/details/51225388 0.安裝火狐 1.安裝selenium,可通過pip安裝:pip install selenium 2.程序里面改三處:用戶名、密碼、搜過的關鍵詞search_text 3.需要手動輸入驗證碼,並且驗證碼大小寫敏感,若是輸錯了,等3秒再輸入。 4.爬下來了,用BeautifulSoup提取,並且將相同文本的放在一起,並且排序 時間:5秒一個,不可縮短,不然加載不出來下一頁這個按鈕,然后程序就掛了,若網速快些,延時可以短些。 在每個點擊事件之前或者之后都加time.sleep(2) ''' def __init__(self): #微博登錄用戶名,用戶命名 self.username_ = "你的用戶名" self.password_ = "你的密碼" self.href = 'http://s.weibo.com/' #獲取火狐瀏覽器對象 self.browser = webdriver.Firefox(executable_path = r'D:\ff\geckodriver.exe') #html請求 self.browser.get(self.href) time.sleep(2) #獲取網頁右上登陸元素 並點擊 login_btn = self.browser.find_element_by_xpath('//a[@node-type="loginBtn"]') login_btn.click() time.sleep(2) #獲取選擇賬號登錄元素 並點擊 name_login = self.browser.find_element_by_xpath('//a[@action-data="tabname=login"]') name_login.click() time.sleep(2) #獲取輸入用戶名,密碼元素 並輸入用戶名和密碼 username = self.browser.find_element_by_xpath('//input[@node-type="username"]') password = self.browser.find_element_by_xpath('//input[@node-type="password"]') username.clear() username.send_keys(self.username_) password.clear() password.send_keys(self.password_) #獲取提交登陸元素 並點擊 sub_btn = self.browser.find_element_by_xpath('//a[@suda-data="key=tblog_weibologin3&value=click_sign"]') sub_btn.click() time.sleep(5) ''' #下面是驗證碼部分,如果需要驗證碼的化 while True: try: verify_img = browser.find_element_by_xpath('//img[@node-type="verifycode_image"]') except NoSuchElementException: break if verify_img: # 輸入驗證碼 verify_code = browser.find_element_by_xpath('//input[@node-type="verifycode"]') verify_code_ = input('verify_code > ') verify_code.clear() verify_code.send_keys(verify_code_) # 提交登陸 sub_btn = browser.find_element_by_xpath('//a[@suda-data="key=tblog_weibologin3&value=click_sign"]') sub_btn.click() time.sleep(2) else: break ''' #獲取搜索欄元素 #self.search_form = self.browser.find_element_by_xpath('//input[@class="searchInp_form"]') def get_weibo_search(self,search_text,max_length = 20): ''' 在網頁中搜索指定信息,搜多到一頁信息后,保存,然后搜索下一頁信息,直至到max_length頁 默認在當前路徑下生成一個 名字為 search_text(去除下划線) 的文件夾,下面存放爬取得網頁 args: search_text:需要搜索的內容 max_length:最大爬取網頁個數 return: dst_dir:返回爬取網頁所在的文件夾 ''' #將關鍵詞送到搜索欄中,進行搜索 self.search_form = self.browser.find_element_by_xpath('//input[@class="searchInp_form"]') self.search_form.clear() self.search_form.send_keys(search_text) time.sleep(5) #獲取搜索按鈕元素 只有窗口最大化 才有搜索按鈕 self.search_btn = self.browser.find_element_by_xpath('//a[@class="searchBtn"]') #點擊搜索 self.search_btn.click() #進入循環之前,讓第一頁先加載完全。 time.sleep(2) print('Try download html for : {}'.format(search_text)) topics_name = search_text #將名字里面的空格換位_ 創建以搜索內容為名字的文件夾,保存爬取下來的網頁 topics_name = topics_name.replace(" ","_") os_path = os.getcwd() dst_dir = os.path.join(os_path,topics_name) if not os.path.isdir(dst_dir): os.mkdir(dst_dir) #捕獲異常,有的搜索可能沒有下一頁 遇到錯誤會跳過 try: count = 1 while count <= max_length: ''' #保存網頁 構建目標文件路徑 ''' file_name = os.path.join(dst_dir,'{}_{}.html'.format(topics_name, count)) #必須指定編碼格式 with open(file_name, 'w',encoding='utf-8') as f: f.write(self.browser.page_source) print('Page {} download finish!'.format(count)) time.sleep(3) #獲取下一頁元素 self.next_page = self.browser.find_element_by_css_selector('a.next') #next_page = browser.find_element_by_xpath('//a[@class="page next S_txt1 S_line1"]') #有的時候需要手動按F5刷新,不然等太久依然還是出不來,程序就會掛,略脆弱。 self.next_page.click() count += 1 #完成一輪之前,保存之前,先讓其加載完,再保存 如果報錯,可以通過調節時間長度去除錯誤 time.sleep(10) except Exception as e: print('Error:',e) return dst_dir def get_weibo_text(self,file_name): ''' 將html文件里面的<p></p>標簽的內容提取出來 args: text:返回一個list 每一個元素都是p標簽的內容 args: file_name:html文件路徑 ''' text = [] soup = BeautifulSoup(open(file_name,encoding='utf-8'),'lxml') #獲取tag為div class_="WB_cardwrap S_bg2 clearfix"的所有標簽 items = soup.find_all("div",class_="WB_cardwrap S_bg2 clearfix") if not items: text = [] #遍歷每一額標簽,提取為p的子標簽內容 for item in items: line = item.find("p").get_text() #print line text.append(line) return text def get_weibo_all_page(self,path): ''' 文件夾下所有文件內容提取出來,然后合並起來 args: path:list 每個元素都是一個文件路徑,加入我們在新浪搜索內容為火影忍者,則在當前路徑下會生成一個為火影忍者的文件夾 path就是這個文件夾的路徑 return: texts_all:返回合並之后的內容 是一個list ''' texts_all = [] file_names = os.listdir(path) #遍歷當前文件夾下每個文件 for file_name in file_names: #將html文件里面的<p></p>標簽的內容提取出來 texts = self.get_weibo_text(os.path.join(path,file_name)) #遍歷每一個元素 for text in texts: text = text.replace("\t","") text = text.strip("\n") text = text.strip(" ") #若是重了,不加入到里面 if text in texts_all: pass else: texts_all.append(text) return texts_all def get_results_weibo(self,weibos_name): ''' 合並若干個文件夾下提取出來的微博 args: weibos_name:list 每一個元素都是一個搜索項提取出來的文本文件 args: ''' texts = [] for file_name in weibos_name: with open(file_name,encoding='utf-8') as f: text = f.readlines() for line in text: line = line.strip("\n") if line not in texts: texts.append(line) return texts if __name__ == '__main__': print('開始搜索') search = WeiboSpyder() #在新浪搜索中需要搜索內容 searchs_text = ["火影忍者","蒼井空","波多野結衣"] #遍歷要搜索的每一行 for search_text in searchs_text: path = search.get_weibo_search(search_text,max_length=5) texts_all = search.get_weibo_all_page(path) #文本排序 texts_all_sorted = sorted(texts_all) weibo_text_name = path + "_weibos.txt" with open(weibo_text_name,"w",encoding='utf-8') as f: #一行一行的寫入 for text in texts_all_sorted: f.write(text + "\n") #將幾個_weibos.txt文件合並到一起 print("Together:") file_names_weibos = [i for i in os.listdir(os.getcwd()) if i.endswith("_weibos.txt")] texts = search.get_results_weibo(file_names_weibos) with open("results.txt","w",encoding='utf-8') as f: for text in sorted(texts): f.write(text+"\n") print("Together finish!")
微博的官網是http://s.weibo.com/,同樣我們點擊F12,打開網頁:我們先獲取右上角登陸按鈕的標簽信息:
我們怎樣可以實現點擊這個登陸的事件呢,在上面代碼可以看到我定義了一個WeiboSpyder的類,其中在構造函數中負責網頁的登陸功能。
我們首先通過向指定網址發送html請求,進行加載網頁:
#獲取火狐瀏覽器對象 self.browser = webdriver.Firefox(executable_path = r'D:\ff\geckodriver.exe') #html請求 self.browser.get(self.href) time.sleep(2)
然后通過find_element_by_xpath()函數獲取登陸標簽,獲取該標簽之后,我們觸發點擊事件,為了等到網頁加載出來,我們在點擊時候,睡眠2s。
#獲取選擇賬號登錄元素 並點擊 name_login = self.browser.find_element_by_xpath('//a[@action-data="tabname=login"]') name_login.click() time.sleep(2)
同理,后面我們獲取選擇賬號登陸標簽,然后獲取登陸名,登錄密碼元素,然后利用send_keys()方法輸入登陸信息,最后獲取登陸標簽,開始登陸:
#獲取選擇賬號登錄元素 並點擊 name_login = self.browser.find_element_by_xpath('//a[@action-data="tabname=login"]') name_login.click() time.sleep(2) #獲取輸入用戶名,密碼元素 並輸入用戶名和密碼 username = self.browser.find_element_by_xpath('//input[@node-type="username"]') password = self.browser.find_element_by_xpath('//input[@node-type="password"]') username.clear() username.send_keys(self.username_) password.clear() password.send_keys(self.password_) #獲取提交登陸元素 並點擊 sub_btn = self.browser.find_element_by_xpath('//a[@suda-data="key=tblog_weibologin3&value=click_sign"]') sub_btn.click() time.sleep(5)
由於這里不需要驗證碼登陸,我們就跳過驗證碼了,在下一個案例中我會教大家如何過驗證碼。
完成登陸后,我們在主程序中開始利用微博的搜索引擎搜索指定內容:
if __name__ == '__main__': print('開始搜索') search = WeiboSpyder() #在新浪搜索中需要搜索內容 searchs_text = ["火影忍者","蒼井空","波多野結衣"] #遍歷要搜索的每一行 for search_text in searchs_text: path = search.get_weibo_search(search_text,max_length=5) texts_all = search.get_weibo_all_page(path) #文本排序 texts_all_sorted = sorted(texts_all) weibo_text_name = path + "_weibos.txt" with open(weibo_text_name,"w",encoding='utf-8') as f: #一行一行的寫入 for text in texts_all_sorted: f.write(text + "\n") #將幾個_weibos.txt文件合並到一起 print("Together:") file_names_weibos = [i for i in os.listdir(os.getcwd()) if i.endswith("_weibos.txt")] texts = search.get_results_weibo(file_names_weibos) with open("results.txt","w",encoding='utf-8') as f: for text in sorted(texts): f.write(text+"\n") print("Together finish!")
我在這里搜索的內容主要包括:
searchs_text = ["火影忍者","蒼井空","波多野結衣"]
然后遍歷每一項,開始調用get_weibo_search()方法:
def get_weibo_search(self,search_text,max_length = 20): ''' 在網頁中搜索指定信息,搜多到一頁信息后,保存,然后搜索下一頁信息,直至到max_length頁 默認在當前路徑下生成一個 名字為 search_text(去除下划線) 的文件夾,下面存放爬取得網頁 args: search_text:需要搜索的內容 max_length:最大爬取網頁個數 return: dst_dir:返回爬取網頁所在的文件夾 ''' #將關鍵詞送到搜索欄中,進行搜索 self.search_form = self.browser.find_element_by_xpath('//input[@class="searchInp_form"]') self.search_form.clear() self.search_form.send_keys(search_text) time.sleep(5) #獲取搜索按鈕元素 只有窗口最大化 才有搜索按鈕 self.search_btn = self.browser.find_element_by_xpath('//a[@class="searchBtn"]') #點擊搜索 self.search_btn.click() #進入循環之前,讓第一頁先加載完全。 time.sleep(2) print('Try download html for : {}'.format(search_text)) topics_name = search_text #將名字里面的空格換位_ 創建以搜索內容為名字的文件夾,保存爬取下來的網頁 topics_name = topics_name.replace(" ","_") os_path = os.getcwd() dst_dir = os.path.join(os_path,topics_name) if not os.path.isdir(dst_dir): os.mkdir(dst_dir) #捕獲異常,有的搜索可能沒有下一頁 遇到錯誤會跳過 try: count = 1 while count <= max_length: ''' #保存網頁 構建目標文件路徑 ''' file_name = os.path.join(dst_dir,'{}_{}.html'.format(topics_name, count)) #必須指定編碼格式 with open(file_name, 'w',encoding='utf-8') as f: f.write(self.browser.page_source) print('Page {} download finish!'.format(count)) time.sleep(3) #獲取下一頁元素 self.next_page = self.browser.find_element_by_css_selector('a.next') #next_page = browser.find_element_by_xpath('//a[@class="page next S_txt1 S_line1"]') #有的時候需要手動按F5刷新,不然等太久依然還是出不來,程序就會掛,略脆弱。 self.next_page.click() count += 1 #完成一輪之前,保存之前,先讓其加載完,再保存 如果報錯,可以通過調節時間長度去除錯誤 time.sleep(10) except Exception as e: print('Error:',e) return dst_dir
這個函數主要就做了以下事情:
- 將關鍵詞送到搜索欄中,進行搜索
- 創建一個文件夾,把剛才搜索的網頁保存下來
- 獲取下一頁元素,並觸發點擊事件,然后再把新的網頁保存下來
- 重復上一步,直至保存了max_length頁,或者觸發的異常,停止搜索
- 返回文件夾路徑
程序運行結果如下:
如果我們運行出現類似以下的錯誤:
這主要是因為我們頁面沒有加載完,所以才會找不到指定元素,我們可以通過延長睡眠時間解決該問題。
並且我們會生成三個文件夾:
為什么搜索蒼井空和波多野結衣只有一條數據,主要是因為被屏蔽了:
后面的代碼get_weibo_all_page()函數以及get_weibo_text(),get_results_weibo()函數就是利用BeautifulSoup對剛才獲取的幾個網頁內容進行提取,這里就不過多介紹了,最終還會生成幾個文件:
results.txt是對另外三個文件進行合並,內容如下:
四 爬取需要驗證碼的網站
其實爬取需要驗證碼網站和上一個案例是類似的,只不過需要我們輸入驗證碼,如何過驗證碼主要有兩種方式,一種是利用手動輸入,還有一種是采用自動識別技術識別出驗證碼,驗證碼的識別需要對圖像處理具有一定的基礎,或者我們利用人工神經網絡識別,但是這里為了簡單,我們可以采用
tesseract.exe這個軟件來識別,不過識別准確率很低,這個軟件使用之前需要准備工作
- 下載tesseract.exe https://www.polarxiong.com/archives/python-pytesser-tesseract.html,安裝在D:\Tesseract-OCR
- 配置環境變量 TESSDATE PREFIX = D:\Tesseract-OCR
然后我們可以安裝相對應的python庫,來調用該軟件:
pip install pytesser3
不過我使用的時候總是報錯,因此就把該庫下載了,並修改了原來庫的代碼,保存在pytesser.py
# -*- coding: utf-8 -*- """ Created on Sat May 26 09:19:09 2018 @author: lenovo """ import subprocess import os from PIL import Image ''' 使用之前需要准備工作 1.下載tesseract.exe https://www.polarxiong.com/archives/python-pytesser-tesseract.html 安裝在D:\Tesseract-OCR 2.配置環境變量 TESSDATE PREFIX = D:\Tesseract-OCR ''' pwd = os.getcwd() #tesseract.exe在命令行調用的命令 或者tesseract.exe文件的全路徑 tesseract_exe_name = 'D:\\Tesseract-OCR\\tesseract' #把圖像保存成tesseract兼容格式bmp scratch_image_name = pwd + os.sep + "temp.bmp" #識別后txt文件保存的路徑 scratch_text_name_root = pwd + os.sep + "temp" #識別之后是否清楚生成的臨時文件 cleanup_scratch_flag = True def image_to_scratch(im, scratch_image_name): ''' Saves image in memory to scratch file. .bmp format will be read correctly by Tesseract 保存圖像到路徑scratch_image_name 即把圖片轉換成Tesseract兼容格式 bmp ''' im.save(scratch_image_name, dpi=(200,200)) def retrieve_text(scratch_text_name_root): ''' 從識別的txt文件中讀取文本 ''' with open(scratch_text_name_root + '.txt','r',encoding='utf-8') as f: text = f.read() return text def perform_cleanup(scratch_image_name, scratch_text_name_root): ''' 清除臨時文件 ''' for name in (scratch_image_name, scratch_text_name_root + '.txt', "tesseract.log"): try: os.remove(name) except OSError: pass def call_tesseract(input_filename, output_filename): ''' Calls external tesseract.exe on input file (restrictions on types), outputting output_filename +"txt" 調用 tesseract.exe識別圖片 ''' print(tesseract_exe_name,input_filename,output_filename) args = [tesseract_exe_name, input_filename, output_filename] proc = subprocess.Popen(args) retcode = proc.wait() if retcode!=0: print('Open process error') def image_to_string(im, cleanup = cleanup_scratch_flag): """Converts im to file, applies tesseract, and fetches resulting text. If cleanup=True, delete scratch files after operation.""" try: #保存圖片為scratch_image_name image_to_scratch(im, scratch_image_name) #調用tesseract生成識別文本 call_tesseract(scratch_image_name, scratch_text_name_root) # 提取文本內容 text = retrieve_text(scratch_text_name_root) finally: if cleanup: perform_cleanup(scratch_image_name, scratch_text_name_root) return text def image_file_to_string(filename, cleanup = cleanup_scratch_flag, graceful_errors=True): """Applies tesseract to filename; or, if image is incompatible and graceful_errors=True, converts to compatible format and then applies tesseract. Fetches resulting text. If cleanup=True, delete scratch files after operation.""" try: try: call_tesseract(filename, scratch_text_name_root) text = retrieve_text(scratch_text_name_root) except: im = Image.open(filename) text = image_to_string(im, cleanup) finally: if cleanup: perform_cleanup(scratch_image_name, scratch_text_name_root) return text
下面我以爬取超星網站的評論數據為例:網址http://passport2.chaoxing.com/login?fid=2218&refer=http://i.mooc.chaoxing.com
按下F12,打開該網址,我們可以看到有一處需要輸入驗證碼:
同樣我定義了一個chaoxingSpy類,其中有兩個函數是用來處理驗證碼的:
def read_code(self,file=None,auto=True): ''' 識別驗證碼 args: file:驗證碼圖片所在本機路徑,如果為None,則自動從網頁中截取驗證碼圖片保存到'./code.jpg' auto:表示是否自動識別 為True:自動識別 False:手動識別 自動識別准確的不高 ''' ''' 對驗證碼進行區域截圖,並保存 ''' if file is None: #裁切后的驗證碼路徑 file = './code.jpg' #對網頁進行全截圖 self.browser.get_screenshot_as_file('./all.jpg') img = Image.open('./all.jpg') #設置要裁剪的區域 box = (490,340,570,370) #裁切 region = img.crop(box) #RGBA to RGB region= region.convert('RGB') region.save(file) print('圖片裁切成功!') else: img = Image.open(file) if auto: verify_code = self.image_file_to_string(file) else: #顯示圖片 region.show() #手動輸入驗證碼 獲取驗證碼輸入元素 verify_code = input('verify_code > ') return verify_code def image_file_to_string(self,file): ''' 圖像增強,自動識別簡單驗證碼 args: file:驗證碼文件的路徑 return: code:識別的驗證碼 ''' img = Image.open(file) #圖像加強,二值化 imgry = img.convert('L') #對比度增強 sharpness = ImageEnhance.Contrast(imgry) sharp_img = sharpness.enhance(2.0) #保存 sharp_img.save(file) code = pytesser.image_file_to_string(file) #去除非數字 new_code = '' for i in code: if i in '0123456789': new_code += i print('識別的驗證碼為:',new_code) return new_code
當read_code()函數的auto參數傳入False時,表示手動輸入驗證碼,即在程序運行的時候,我們利用input()函數讀取我們控制台輸入的驗證碼,然后返回驗證碼的值。當傳入True的時候,我們通過調用tesseract.exe識別驗證碼,為了提高准確率,我們事先對圖像進行了處理,默認下我們截取當前網頁的驗證碼圖片,然后通過對圖片二值化,對比度增強處理然后在進行自動識別。
完整代碼如下:
# -*- coding: utf-8 -*- """ Created on Fri May 25 18:04:13 2018 @author: zy """ # -*- coding: utf-8 -*- """ Created on Thu May 24 16:35:36 2018 @author: zy """ import time from selenium import webdriver from selenium.common.exceptions import NoSuchElementException import urllib.request as urllib from PIL import Image,ImageEnhance import pytesser ''' webdriver使用說明:https://wenku.baidu.com/view/d64005c6af45b307e9719715.html ''' class chaoxingSpy(): def __init__(self): ''' 登錄系統 ''' #登錄用戶名,用戶命名 self.username_ = "你的用戶名" self.password_ = "你的密碼" url = 'http://passport2.chaoxing.com/login?fid=2218&refer=http://i.mooc.chaoxing.com' #獲取火狐瀏覽器對象 self.browser = webdriver.Firefox(executable_path = r'D:\ff\geckodriver.exe') #html請求 self.browser.get(url) #存放登錄前后的cookies self.cookies = {'before':'','after':''} self.cookies['before'] = self.browser.get_cookies() print('登陸前:\n') for cookie in self.cookies['before']: print('%s -> %s' % (cookie['name'], cookie['value'])) #登錄系統,循環嘗試登錄,登錄成功,就會拋異常,跳出循環繼續執行 while True: try: #獲取登錄用戶名元素 username = self.browser.find_element_by_id('unameId') #獲取登錄密碼元素 password = self.browser.find_element_by_id('passwordId') #輸入用戶名和密碼 username.clear() username.send_keys(self.username_) password.clear() password.send_keys(self.password_) #判斷是否需要輸入驗證碼 try: #獲取驗證碼圖片 verify_img = self.browser.find_element_by_id('numVerCode') #獲取驗證碼url 就是驗證碼產生的網頁 #code_src= verify_img.get_attribute('src') except NoSuchElementException: print('不需要輸入驗證碼進行驗證!') if verify_img: #獲取驗證碼 verify_code_ = self.read_code(auto=False) verify_code = self.browser.find_element_by_id('numcode') verify_code.clear() verify_code.send_keys(verify_code_) #提交登錄 login_btn = self.browser.find_element_by_xpath('//input[@class="zl_btn_right"]') login_btn.click() time.sleep(3) #判斷是否已經登錄進系統 if url == self.browser.current_url: #刷新網頁 self.browser.refresh() except: #cookies = self.browser.get_cookies() print('登錄成功!') #存放登錄前后的cookies self.cookies['after'] = self.browser.get_cookies() print('登陸后:\n') for cookie in self.cookies['after']: print('%s -> %s' % (cookie['name'], cookie['value'])) break #利用登陸后的cookies 跳轉到需要爬取的頁面 url = 'http://mooc1.chaoxing.com/mycourse/teachercourse?moocId=93196686&clazzid=3216311&edit=true' self.browser.get(url) time.sleep(2) #點擊超鏈接打開一個新的窗口 moretipic_a = self.browser.find_element_by_xpath('//a[@class="moreTopic"]') moretipic_a.click() time.sleep(2) #0是第一個打開的窗口 1是最后一個打開的窗口 轉到最后一個窗口 超鏈接的點擊最好都要加這句代碼 #https://www.cnblogs.com/nullbaby/articles/7205247.html #https://www.cnblogs.com/studyddup0212/p/9030455.html self.browser.switch_to_window(self.browser.window_handles[1]) time.sleep(2) def get_text(self): ''' 獲取所有的文本 ''' texts = [] count = 1 #寫文件 with open('./out_topics.txt', 'w',encoding='utf-8') as f: ''' 爬取文件夾外部數據 ''' txts = self.get_url_txt() for txt in txts: txt = '%s. %s'%(count,txt) texts.append(txt) f.write(txt) count += 1 print('外部數據寫入成功!') ''' 遍歷每一個文件夾的數據 ''' with open('./in_topics.txt', 'w',encoding='utf-8') as f: folder_urls = self.spyder_folder() for url in folder_urls: txts = self.get_url_txt(url) for txt in txts: txt = '%s. %s'%(count,txt) texts.append(txt) f.write(txt) count += 1 print('文件夾數據寫入成功!') def get_url_txt(self,url=None): ''' 遍歷一個網頁中的所有數據 args: url:網頁網址 如果是None,就是從當前網頁讀取數據 return: texts:獲取所有數據 list類型 每個元素對應一行數據 ''' texts = [] if url: #html請求 self.browser.get(url) time.sleep(2) count = 1 while True: try: #如果有查看更多,獲取查看更多的超鏈接 循環次數越多,等待時間越長 getmoretopic_a = self.browser.find_element_by_id('getMoreTopic') getmoretopic_a.click() time.sleep(5) print(count,' 點擊查看更多成功!') count += 1 if count> 40: self.browser.switch_to_window(self.browser.window_handles[1]) time.sleep(20) break except: self.browser.switch_to_window(self.browser.window_handles[1]) time.sleep(5) break #獲取所有話題 divlist = self.browser.find_elements_by_class_name('content1118') #遍歷每一個話題 for div in divlist: name = div.find_element_by_class_name('name').text date = div.find_element_by_class_name('gray').text text = div.find_element_by_class_name('stuFont').find_element_by_tag_name('span').text row = '%s %s : %s\n'%(date,name,text) texts.append(row) #print(row) print('Download from {0} finish!'.format(self.browser.current_url)) return texts def spyder_folder(self): ''' 爬取所有的文件夾路徑 args: folderList:list每一個元素對應一個文件夾的網址 ''' ul = self.browser.find_element_by_xpath('//ul[@class="folderList"]') folderList = [] alist = ul.find_elements_by_tag_name('a') for a in alist: folderList.append(a.get_attribute('href')) return folderList def read_code(self,file=None,auto=True): ''' 識別驗證碼 args: file:驗證碼圖片所在本機路徑,如果為None,則自動從網頁中截取驗證碼圖片保存到'./code.jpg' auto:表示是否自動識別 為True:自動識別 False:手動識別 自動識別准確的不高 ''' ''' 對驗證碼進行區域截圖,並保存 ''' if file is None: #裁切后的驗證碼路徑 file = './code.jpg' #對網頁進行全截圖 self.browser.get_screenshot_as_file('./all.jpg') img = Image.open('./all.jpg') #設置要裁剪的區域 box = (490,340,570,370) #裁切 region = img.crop(box) #RGBA to RGB region= region.convert('RGB') region.save(file) print('圖片裁切成功!') else: img = Image.open(file) if auto: verify_code = self.image_file_to_string(file) else: #顯示圖片 region.show() #手動輸入驗證碼 獲取驗證碼輸入元素 verify_code = input('verify_code > ') return verify_code def image_file_to_string(self,file): ''' 圖像增強,自動識別簡單驗證碼 args: file:驗證碼文件的路徑 return: code:識別的驗證碼 ''' img = Image.open(file) #圖像加強,二值化 imgry = img.convert('L') #對比度增強 sharpness = ImageEnhance.Contrast(imgry) sharp_img = sharpness.enhance(2.0) #保存 sharp_img.save(file) code = pytesser.image_file_to_string(file) #去除非數字 new_code = '' for i in code: if i in '0123456789': new_code += i print('識別的驗證碼為:',new_code) return new_code def download(self,url,dstpath=None): ''' 利用urlretrieve()這個函數可以直接從互聯網上下載文件保存到本地路徑下 args: url:網頁文件或者圖片以及其他數據路徑 dstpath:保存全路況 ''' if dstpath is None: dstpath = './code.jpg' try: urllib.urlretrieve(url,dstpath) print('Download from {} finish!'.format(url)) except Exception as e: print('Download from {} fail!'.format(url)) if __name__ == '__main__': #加載頁面 spy = chaoxingSpy() spy.get_text()
我們主要是爬取下面網頁中的評論人時間,評論人姓名,評論人內容:
注意:在程序運行時如點擊下面的查看更多話題,會創建一個新的窗口:
而我們需要獲取的數據保存在討論列表頁面的話,這時候我們需要切換窗口,代碼如下:
#點擊超鏈接打開一個新的窗口 moretipic_a = self.browser.find_element_by_xpath('//a[@class="moreTopic"]') moretipic_a.click() time.sleep(2) #0是第一個打開的窗口 1是最后一個打開的窗口 轉到最后一個窗口 超鏈接的點擊最好都要加這句代碼 #https://www.cnblogs.com/nullbaby/articles/7205247.html #https://www.cnblogs.com/studyddup0212/p/9030455.html self.browser.switch_to_window(self.browser.window_handles[1]) time.sleep(2)
程序運行后,會生成另個文件out_topics.txt和in_topics.txt,下面我們顯示其中一個文件的的內容:
為了避免我們每次都要進行登錄,我們可以利用cookies繞過登錄,詳細內容可以搜索其他博客。
參考文獻
[2]python爬蟲——基於selenium用火狐模擬登陸爬搜索關鍵詞的微博
[4]selenium - switch_to.window() - 多窗口切換
[5]Python 爬蟲入門(四)—— 驗證碼上篇(主要講述驗證碼驗證流程,不含破解驗證碼)