基于上两篇文章的工做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:这个博客模板默认换行怎么辣么多,代码里面都自动换行。。
(完)