基於上兩篇文章的工作
【Python數據分析】Python3操作Excel-以豆瓣圖書Top250為例
【Python數據分析】Python3操作Excel(二) 一些問題的解決與優化
已經正確地實現豆瓣圖書Top250的抓取工作,並存入excel中,但是很不幸,由於采用的串行爬取方式,每次爬完250頁都需要花費7到8分鍾,顯然讓人受不了,所以必須在效率上有所提升才行。
仔細想想就可以發現,其實爬10頁(每頁25本),這10頁爬的先后關系是無所謂的,因為寫入的時候沒有依賴關系,各寫各的,所以用串行方式爬取是吃虧的。顯然可以用並發來加快速度,而且由於沒有同步互斥關系,所以連鎖都不用上。
既然考慮並發,那么就有多進程和多線程兩種方式,各自的優缺點比較可以見:這里
簡單來說,多進程穩定,因為一個進程掛掉其他進程不受影響,但是開銷大,建立太多進程會消耗系統大量資源,並且切換慢,因為要通過系統進程調度。
多線程作為“輕量級的進程”,是操作系統調度的基本單位,切換快速,只消耗極少的資源,但是缺點就是一個線程崩掉整個進程包括其他線程都會崩掉,所以穩定性欠佳。
這里雖然進程數/線程數很少(只有10個),即使采用多進程也不會有多大的開銷,但是為了更快地爬取,且爬取豆瓣這樣的大站,穩定性不會太差,所以還是采用多線程比較實惠。
多線程有兩個模塊,一個Thread模塊,一個threading模塊,但是前者現在用的很少了,后者更加方便實用。所以采用后者。
在程序中實用線程有兩種方法,一種是自己寫一個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:這個博客模板默認換行怎么辣么多,代碼里面都自動換行。。
(完)
