前言
Python的标准库提供了两个模块:_thread和threading,_thread是低级模块,threading是高级模块,对_thread进行了封装。绝大多数情况下,我们只需要使用threading这个高级模块。
启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()开始执行。
虽然python的多线程受GIL限制,并不是真正的多线程,但是对于I/O密集型计算还是能明显提高效率,比如说爬虫。详细请见 https://www.zhihu.com/question/23474039
实例
下面用一个实例来验证多线程的效率。代码只涉及页面获取,并没有解析出来。
# -*-coding:utf-8 -*-
import requests, time
import threading
class MyThread(threading.Thread):
def __init__(self, func, args):
threading.Thread.__init__(self) # 调用父类的构造函数
self.args = args
self.func = func
def run(self): # 线程活动方法
self.func(self.args[0]) # 启动这个函数args是元组,取出url
def open_url(url):
response = requests.get(url).text
print(len(response))
return response
def nomal_method(urlList):
# 一般方式
n_start = time.time()
for each in urlList:
open_url(each)
n_end = time.time()
print('the normal way take %s s' % (n_end - n_start))
def threadWay(urlList):
# 多线程
t_start = time.time()
threadList = [MyThread(open_url,(url,)) for url in urlList] # 调用线程类创建新线程,返回线程列表
for t in threadList:
t.setDaemon(True) # 设置守护线程,父线程会等待子线程执行完后再退出
t.start() # 线程开启
for i in threadList:
i.join() # 等待线程终止,等子线程执行完后再执行父线程
t_end = time.time()
print('the thread way take %s s' % (t_end - t_start))
if __name__ == '__main__':
# 构造url列表
urlList = []
for p in range(1, 10):
urlList.append('https://so.gushiwen.org/authors/authorvsw.aspx?page='+str(p)+'&id=9cb3b7c0e4a0')
nomal_method(urlList)
threadWay(urlList)
分别用两种方式获取10个访问速度比较慢的网页,一般方式耗时1.8s,多线程耗时0.2s。
多线程简介
多线程举的例子,最对的可能就是消费者、生产者的例子了(不知道的可以去百度),下面介绍两种多线程的方式,即创建线程、传递函数(面向过程)和面向对象。
本人对多线程做了简单阐述,请点击跳转
面向过程的多线程爬虫模板
(因为面向对象的我在上面写过了。。。)
思路
得到urlList,然后根据每个url创建一个线程,然后for循环启动再for循环join
代码
# -*-coding:utf-8 -*-
import requests, time
import threading
def open_url(url):
response = requests.get(url).text
print(len(response))
return response
def doSpyder(urlList):
# 多线程
t_start = time.time()
threadList = [threading.Thread(target=open_url,args=(url,)) for url in urlList] # 调用线程类创建新线程,返回线程列表
for t in threadList:
t.setDaemon(True) # 设置守护线程,父线程会等待子线程执行完后再退出
t.start() # 线程开启
for i in threadList:
i.join() # 等待线程终止,等子线程执行完后再执行父线程
t_end = time.time()
print('the thread way take %s s' % (t_end - t_start))
if __name__ == '__main__':
# 构造url列表
urlList = []
for p in range(1, 10):
urlList.append('https://so.gushiwen.org/authors/authorvsw.aspx?page='+str(p)+'&id=9cb3b7c0e4a0')
doSpyder(urlList)
一个误区
注意:创建多线程的时候应该每个网页都创建一个线程,不能直接使用创建好的线程对列表进行访问爬取,否则会得到重复数据,即脏数据!!!(因为list是非线程安全的数据结构,就是同一秒可能有两个线程同时访问到了列表的第一个元素)
可以参看点击跳转的脏数据部分
注意啊,不要使用锁来处理,那样多线程可能就变单线程了,,,,