python爬虫用多线程还是多进程_多线程or多进程爬虫案例

前置说明

关于python多线程和多进程的说明,请参考如下:

这是我找到的两篇很棒的文章,里面详细说明的python多进程、多线程的原理以及用法,大家可以仔细看看

1158674-20200425094232395-963814720.png

多进程爬虫例子

用一个实例说明下如何使用多进程进行爬虫

目标网站:https://imgbin.com/,本次爬取的也是一个图片网站,里面是一些透明背景图

1158674-20200421225321961-449601620.png

1.首先看一下不添加多进程/多线程时的爬取速度,代码如下

#-*- coding:utf-8 -*-

importrequestsfrom requests.exceptions importRequestExceptionimportos, timefrom lxml importetreedefget_html(url):"""获取页面内容"""response= requests.get(url, timeout=15)#print(response.status_code)

try:if response.status_code == 200:#print(response.text)

returnresponse.textelse:returnNoneexceptRequestException:print("请求失败")#return None

defparse_html(html_text):"""解析页面内容,提取图片url"""html=etree.HTML(html_text)if len(html) >0:

img_src= html.xpath("//img[@class='photothumb lazy']/@data-original") #元素提取方法

#print(img_src)

returnimg_srcelse:print("解析页面元素失败")defget_image_content(url):"""请求图片url,返回二进制内容"""

try:

r= requests.get(url, timeout=15)if r.status_code == 200:returnr.contentreturnNoneexceptRequestException:returnNonedef main(depth=None):"""主函数,下载图片

:param depth: 爬取页码

:return:"""j= 1base_url= 'https://imgbin.com/free-png/naruto/' #定义初始url

for i in range(1, depth):

url= base_url + str(i) #根据页码遍历请求url

html = get_html(url) #解析每个页面的内容

#print(html)

ifhtml:

list_data= parse_html(html) #提取页面中的图片url

root_dir = os.path.dirname(os.path.abspath('.'))

save_path= root_dir + '/pics/' #定义保存路径

for t in list_data: #遍历每个图片url

try:

file_path= '{0}{1}.{2}'.format(save_path, str(j), 'jpg')if not os.path.exists(file_path): #判断是否存在文件,不存在则爬取

with open(file_path, 'wb') as f:

f.write(get_image_content(t))

f.close()print('第{}个文件保存成功'.format(j))else:print("第{}个文件已存在".format(j))

j= j + 1

exceptFileNotFoundError as e:print("遇到错误:", e)continue

exceptTypeError as f:print("遇到错误:", f)continue

else:print("无结果")if __name__ == '__main__':

start=time.time()

main(3)

end=time.time()print(end-start)

测试了一下,晚上10点多,在当时的网速下,爬取2页图片,大概用了403s,并且下载失败了几张;

1158674-20200422221702794-1603016734.png

2.使用多进程爬取

如果要进行多进程爬取的话,必须要有一个准备并行执行的函数,既然要多进程爬取图片,所以应该把下载图片的功能定义为主函数

而上面代码中的main()函数不适合作为主函数,它是用爬取页码作为参数的,而我们并行执行时并不是一次爬取多页,而是并行爬取多个图片(有点绕)

需要改造一下:

(1)定义一个函数,来提取所有页面的图片url,并存到一个列表中(下面代码中的第39行:get_all_image_url()函数)

(2)定义一个主函数,接收图片url,然后下载图片(下面代码中的第82行:main()函数)

代码如下

1 #-*- coding:utf-8 -*-

2 importrequests3 from requests.exceptions importRequestException4 from bs4 importBeautifulSoup5 importbs46 importos, time7 from hashlib importmd58 from lxml importetree9 from multiprocessing importPool, Lock, cpu_count10

13 defget_html(url):14 response = requests.get(url, timeout=15)15 #print(response.status_code)

16 try:17 if response.status_code == 200:18

19 #print(response.text)

20 returnresponse.text21 else:22 returnNone23 exceptRequestException:24 print("请求失败")25 #return None

26

27

28 defparse_html(html_text):29 html =etree.HTML(html_text)30

31 if len(html) >0:32 img_src = html.xpath("//img[@class='photothumb lazy']/@data-original") #元素提取方法

33 #print(img_src)

34 returnimg_src35

36 else:37 print("解析页面元素失败")38

39 defget_all_image_url(page_number):40 """

41 获取所有图片的下载url42 :param page_number: 爬取页码43 :return: 所有图片url的集合44 """

45

46 base_url = 'https://imgbin.com/free-png/naruto/'

47 image_urls =[]48

49 x = 1 #定义一个标识,用于给每个图片url编号,从1递增

50 for i in range(1, page_number):51 url = base_url + str(i) #根据页码遍历请求url

52 try:53 html = get_html(url) #解析每个页面的内容

54 ifhtml:55 data = parse_html(html) #提取页面中的图片url

56 #print(data)

57 #time.sleep(3)

58 ifdata:59 for j indata:60 image_urls.append({61 'name': x,62 'value': j63 })64 x += 1 #每提取一个图片url,标识x增加1

65 exceptRequestException as f:66 print("遇到错误:", f)67 continue

68 #print(image_urls)

69 returnimage_urls70

71 defget_image_content(url):72 """请求图片url,返回二进制内容"""

73 #print("正在下载", url)

74 try:75 r = requests.get(url, timeout=15)76 if r.status_code == 200:77 returnr.content78 returnNone79 exceptRequestException:80 returnNone81

82 defmain(url, name):83 """

84 主函数:实现下载图片功能85 :param url: 图片url86 :param name: 图片名称87 :return:88 """

89 save_path = os.path.dirname(os.path.abspath('.')) + '/pics/'

90 try:91 file_path = '{0}/{1}.jpg'.format(save_path, name)92 if not os.path.exists(file_path): #判断是否存在文件,不存在则爬取

93 with open(file_path, 'wb') as f:94 f.write(get_image_content(url))95 f.close()96

97 print('第{}个文件保存成功'.format(name))98 else:99 print("第{}个文件已存在".format(name))100

101 exceptFileNotFoundError as f:102 print("第{}个文件下载时遇到错误,url为:{}:".format(name, url))103 print("报错:", f)104 raise

105

106 exceptTypeError as e:107 print("第{}个文件下载时遇到错误,url为:{}:".format(name, url))108 print("报错:", e)109

110

111 if __name__ == '__main__':112 start =time.time()113 urls = get_all_image_url(3) # 获取所有图片url列表,爬取2页内容114 #print(urls)

115 #print(cpu_count()) # 查看电脑是几核的

116

117 pool = Pool(6) #我的电脑是6核的,所以开启6个线程试试

118

119 for t inurls: # 遍历列表中的每个图片下载url120 #print(i)

121 pool.apply_async(main, args=(t["value"], t["name"])) #使用apply_async函数实现多进程(并行请求url,下载图片)

122

123 pool.close()124 pool.join()125

126 end =time.time()127 print(end-start)

开启了6个进程,晚上10点多,同样爬取2页内容,大概用了30s,速度提升还是挺明显的

1158674-20200423204247338-1714104415.png

多线程爬虫例子

看了开头分享的两篇文章后,应该了解到如下2点:

1、python解释器有GIL全局锁,导致多线程不能利用多核,多线程并发并不能在python中实现;

2、任务类型分为计算密集型和IO密集型,对于IO密集型任务,大部分时间都在等待IO操作完成,在等待时间中CPU是不需要工作的,即使提供多核CPU也利用不上

网络爬虫属于IO密集型任务,发送网络请求等待响应、把爬取图片保存到本地,很多时间都消耗在等待中,如果启动多线程会明显提高效率

改造一下上面的代码,由多进程爬虫改为多线程爬虫,如下

1 #-*- coding:utf-8 -*-

2 importrequests3 from requests.exceptions importRequestException4 importos, time5 from lxml importetree6 importthreading7

8

9 defget_html(url):10 response = requests.get(url, timeout=15)11 #print(response.status_code)

12 try:13 if response.status_code == 200:14

15 #print(response.text)

16 returnresponse.text17 else:18 returnNone19 exceptRequestException:20 print("请求失败")21 #return None

22

23

24 defparse_html(html_text):25 html =etree.HTML(html_text)26

27 if len(html) >0:28 img_src = html.xpath("//img[@class='photothumb lazy']/@data-original") #元素提取方法

29 #print(img_src)

30 returnimg_src31

32 else:33 print("解析页面元素失败")34

35 defget_all_image_url(page_number):36 """

37 获取所有图片的下载url38 :param page_number: 爬取页码39 :return: 所有图片url的集合40 """

41

42 base_url = 'https://imgbin.com/free-png/naruto/'

43 image_urls =[]44

45 x = 1 #定义一个标识,用于给每个图片url编号,从1递增

46 for i in range(1, page_number):47 url = base_url + str(i) #根据页码遍历请求url

48 try:49 html = get_html(url) #解析每个页面的内容

50 ifhtml:51 data = parse_html(html) #提取页面中的图片url

52 #print(data)

53 #time.sleep(3)

54 ifdata:55 for j indata:56 image_urls.append({57 'name': x,58 'value': j59 })60 x += 1 #每提取一个图片url,标识x增加1

61 exceptRequestException as f:62 print("遇到错误:", f)63 continue

64 #print(image_urls)

65 returnimage_urls66

67 defget_image_content(url):68 """请求图片url,返回二进制内容"""

69 #print("正在下载", url)

70 try:71 r = requests.get(url, timeout=15)72 if r.status_code == 200:73 returnr.content74 returnNone75 exceptRequestException:76 returnNone77

78 defmain(url, image_name):79 """

80 主函数:实现下载图片功能81 :param url: 图片url82 :param image_name: 图片名称83 :return:84 """

85 print('当前子线程: {}'.format(threading.current_thread().name))86 save_path = os.path.dirname(os.path.abspath('.')) + '/pics/'

87 try:88 file_path = '{0}/{1}.jpg'.format(save_path, image_name)89 if not os.path.exists(file_path): #判断是否存在文件,不存在则爬取

90 with open(file_path, 'wb') as f:91 f.write(get_image_content(url))92 f.close()93

94 print('第{}个文件保存成功'.format(image_name))95 else:96 print("第{}个文件已存在".format(image_name))97

98 exceptFileNotFoundError as f:99 print("第{}个文件下载时遇到错误,url为:{}:".format(image_name, url))100 print("报错:", f)101 raise

102

103 exceptTypeError as e:104 print("第{}个文件下载时遇到错误,url为:{}:".format(image_name, url))105 print("报错:", e)106

107

108 if __name__ == '__main__':109 start =time.time()110 print('这是主线程:{}'.format(threading.current_thread().name))111

112 urls = get_all_image_url(3) #获取所有图片url列表

113 thread_list = [] #定义一个列表,向里面追加线程

114

115 for t inurls:116 #print(i)

117 m = threading.Thread(target=main, args=(t["value"], t["name"])) #调用threading.Thread方法,传入主函数及其参数,启动多线程

118

119 thread_list.append(m)120

121 for m inthread_list:122 m.start() #调用start()方法,开始执行

123

124 for m inthread_list:125 m.join() #子线程调用join()方法,使主线程等待子线程运行完毕之后才退出

126

127

128 end =time.time()129 print(end-start)

同样爬取2页,因为有100张图片,所以一共启动了100个子线程,耗时大约6.5s

1158674-20200424171205845-251897263.png

如果打开文件夹来看的话,图片是一下子都出现的

1158674-20200424171955605-1704984421.gif

通过对比,可以看到对于网络爬虫这种IO密集型任务,多线程的效率其实是比多进程高的(6.5s VS 29.9s)

小结:本篇通过一个图片爬虫实例来说了一下如何使用python的多线程与多进程,对比单线程爬虫效率有明显提高,更多细节请自行查看,网上有很多优质资料,这里就不细说了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值