基於上兩篇文章的工做html
【Python數據分析】Python3操做Excel-以豆瓣圖書Top250爲例python
【Python數據分析】Python3操做Excel(二) 一些問題的解決與優化多線程
已經正確地實現豆瓣圖書Top250的抓取工做,並存入excel中,可是很不幸,因爲採用的串行爬取方式,每次爬完250頁都須要花費7到8分鐘,顯然讓人受不了,因此必須在效率上有所提高才行。併發
仔細想一想就能夠發現,其實爬10頁(每頁25本),這10頁爬的前後關係是無所謂的,由於寫入的時候沒有依賴關係,各寫各的,因此用串行方式爬取是吃虧的。顯然能夠用併發來加快速度,並且因爲沒有同步互斥關係,因此連鎖都不用上。app
既然考慮併發,那麼就有多進程和多線程兩種方式,各自的優缺點比較能夠見:這裏 函數
簡單來講,多進程穩定,由於一個進程掛掉其餘進程不受影響,可是開銷大,創建太多進程會消耗系統大量資源,而且切換慢,由於要經過系統進程調度。post
多線程做爲「輕量級的進程」,是操做系統調度的基本單位,切換快速,只消耗極少的資源,可是缺點就是一個線程崩掉整個進程包括其餘線程都會崩掉,因此穩定性欠佳。優化
這裏雖然進程數/線程數不多(只有10個),即便採用多進程也不會有多大的開銷,可是爲了更快地爬取,且爬取豆瓣這樣的大站,穩定性不會太差,因此仍是採用多線程比較實惠。url
多線程有兩個模塊,一個Thread模塊,一個threading模塊,可是前者如今用的不多了,後者更加方便實用。因此採用後者。spa
在程序中實用線程有兩種方法,一種是本身寫一個class,並重寫此class中的__init__方法和run()方法,建立一個這個class的對象並調用start()時run()方法自動調用。另外一種是在threading.Thread構造函數中傳入要用線程運行的函數及其參數。我採用的是後者。
多線程主代碼以下:
thread = [] for i in range(0,250,25): geturl = url + "/start=" + str(i) #要獲取的頁面地址 print("Now to get " + geturl) t = threading.Thread(target=crawler, args=(s,i,url,header,image_dir,worksheet,txtfile)) thread.append(t) for i in range(0,10): thread[i].start() for i in range(0,10): thread[i].join()
之前的爬取和存儲函數寫到了crawler中,具備7個參數。將10頁的url都放入thread列表中,而後逐個啓動,啓動後調用join()等待每個線程結束,若是不等待的話,會發現有的已經運行到下面關閉文件了,那麼別的沒運行完的就寫不了了。
更改和簡化後的所有代碼以下:
# -*- coding:utf-8 -*- import requests import re import xlsxwriter from bs4 import BeautifulSoup from datetime import datetime import codecs import threading #下載圖片 def download_img(imageurl,image_dir,imageName = "xxx.jpg"): rsp = requests.get(imageurl, stream=True) image = rsp.content path = image_dir + imageName +'.jpg' with open(path,'wb') as file: file.write(image) def crawler(s,i,url,header,image_dir,worksheet,txtfile): postData = {"start":i} #post數據 res = s.post(url,data = postData,headers = header) #post soup = BeautifulSoup(res.content.decode(),"html.parser") #BeautifulSoup解析 table = soup.findAll('table',{"width":"100%"}) #找到全部圖書信息的table sz = len(table) #sz = 25,每頁列出25篇文章 for j in range(1,sz+1): #j = 1~25 sp = BeautifulSoup(str(table[j-1]),"html.parser") #解析每本圖書的信息 imageurl = sp.img['src'] #找圖片連接 bookurl = sp.a['href'] #找圖書連接 bookName = sp.div.a['title'] nickname = sp.div.span #找別名 if(nickname): #若是有別名則存儲別名不然存’無‘ nickname = nickname.string.strip() else: nickname = "" notion = str(sp.find('p',{"class":"pl"}).string) #抓取出版信息,注意裏面的.string還不是真的str類型 rating = str(sp.find('span',{"class":"rating_nums"}).string) #抓取平分數據 nums = sp.find('span',{"class":"pl"}).string #抓取評分人數 nums = nums.replace('(','').replace(')','').replace('\n','').strip() nums = re.findall('(\d+)人評價',nums)[0] download_img(imageurl,bookName) #下載圖片 book = requests.get(bookurl) #打開該圖書的網頁 sp3 = BeautifulSoup(book.content,"html.parser") #解析 taglist = sp3.find_all('a',{"class":" tag"}) #找標籤信息 tag = "" lis = [] for tagurl in taglist: sp4 = BeautifulSoup(str(tagurl),"html.parser") #解析每一個標籤 lis.append(str(sp4.a.string)) tag = ','.join(lis) #加逗號 the_img = "I:\\douban\\image\\"+bookName+".jpg" writelist=[i+j,bookName,nickname,rating,nums,the_img,bookurl,notion,tag] for k in range(0,9): if k == 5: worksheet.insert_image(i+j,k,the_img) else: worksheet.write(i+j,k,writelist[k]) txtfile.write(str(writelist[k])) txtfile.write('\t') txtfile.write(u'\r\n') def main(): now = datetime.now() #開始計時 print(now) txtfile = codecs.open("top2501.txt",'w','utf-8') url = "http://book.douban.com/top250?" header = { "User-Agent": "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.13 Safari/537.36", "Referer": "http://book.douban.com/" } image_dir = "I:\\douban\\image\\" #創建Excel workbookx = xlsxwriter.Workbook('I:\\douban\\booktop250.xlsx') worksheet = workbookx.add_worksheet() format = workbookx.add_format() #format.set_align('justify') format.set_align('center') #format.set_align('vjustify') format.set_align('vcenter') format.set_text_wrap() worksheet.set_row(0,12,format) for i in range(1,251): worksheet.set_row(i,70) worksheet.set_column('A:A',3,format) worksheet.set_column('B:C',17,format) worksheet.set_column('D:D',4,format) worksheet.set_column('E:E',7,format) worksheet.set_column('F:F',10,format) worksheet.set_column('G:G',19,format) worksheet.set_column('H:I',40,format) item = ['書名','別稱','評分','評價人數','封面','圖書連接','出版信息','標籤'] for i in range(1,9): worksheet.write(0,i,item[i-1]) s = requests.Session() #創建會話 s.get(url,headers=header) thread = [] for i in range(0,250,25): geturl = url + "/start=" + str(i) #要獲取的頁面地址 print("Now to get " + geturl) t = threading.Thread(target=crawler, args=(s,i,url,header,image_dir,worksheet,txtfile)) thread.append(t) for i in range(0,10): thread[i].start() for i in range(0,10): thread[i].join() end = datetime.now() #結束計時 print(end) print("程序耗時: " + str(end-now)) txtfile.close() workbookx.close() if __name__ == '__main__': main()
雖然仍是寫的有點亂。。 而後運行:
2016-03-29 08:48:37.006681 Now to get http://book.douban.com/top250?/start=0 Now to get http://book.douban.com/top250?/start=25 Now to get http://book.douban.com/top250?/start=50 Now to get http://book.douban.com/top250?/start=75 Now to get http://book.douban.com/top250?/start=100 Now to get http://book.douban.com/top250?/start=125 Now to get http://book.douban.com/top250?/start=150 Now to get http://book.douban.com/top250?/start=175 Now to get http://book.douban.com/top250?/start=200 Now to get http://book.douban.com/top250?/start=225 2016-03-29 08:49:44.003378 程序耗時: 0:01:06.996697
只花費了1分6秒,與前面的7分24秒相比,加速比達到6.7!這就是多線程的優點,理論上應該達到將近10倍的,可是因爲線程建立和切換也是有開銷的,因此達到7~8倍就不錯了。而後我又運行了幾回,穩定性還行,沒有崩過。 ps:這個博客模板默認換行怎麼辣麼多,代碼裏面都自動換行。。
(完)