本系列为自己学习爬虫的相关笔记,如有误,欢迎大家指正
要学习提升爬虫速度用到的知识,必须先熟悉并发和并行、同步和异步的概
一、并发和并行,同步和异步
并发和并行
并发(concurrency)和并行(parallelism)是两个相似的概念。并发是指在一个时间段内发生若干事件的情况,并行是指在同一时刻发生若干事件的情况。
使用单核CPU和多核CPU来说就是:在使用单核CPU时,多个工作任务是以并发的方式运行的,因为只有一个CPU,所以各个任务会分别占用CPU的一段时间依次执行。如果在自己分得的时间段没有完成任务,就会切换到另一个任务,然后在下一次得到CPU使用权的时候再继续执行,直到完成。在这种情况下,因为各个任务的时间段很短、经常切换,所以给我们的感觉是“同时”进行。在使用多核CPU时,在各个核的任务能够同时运行,这是真正的同时运行,也就是并行。
同步和异步
同步就是并发或并行的各个任务不是独自运行的,任务之间有一定的交替顺序,可能在运行完一个任务得到结果后,另一个任务才会开始运行。就像接力赛跑一样,要拿到交接棒之后下一个选手才可以开始跑。
异步则是并发或并行的各个任务可以独立运行,一个任务的运行不受另一个任务影响,任务之间就像比赛的各个选手在不同的赛道比赛一样,跑步的速度不受其他赛道选手的影响。
二、多线程爬虫
多线程爬虫是以并发的方式执行的。也就是说,多个线程并不能真正的同时执行,而是通过进程的快速切换加快网络爬虫速度的。
在Python设计之初,为了数据安全所做的决定设置有GIL(Global Interpreter Lock,全局解释器锁)。在Python中,一个线程的执行过程包括获取GIL、执行代码直到挂起和释放GIL。在一个Python进程中,只有一个GIL,拿不到GIL的线程就不允许进入CPU执行。
正因为如此,在多核CPU上Python的多线程效率也不高。因为每次释放GIL锁,线程之间都会进行锁竞争,而切换线程会消耗资源。
虽然如此,但是因为网络爬虫是IO密集型,线程能够有效地提升效率,因为单线程下有IO操作会进行IO等待,所以会造成不必要的时间浪费,而开启多线程能在线程A等待时自动切换到线程B,可以不浪费CPU的资源,从而提升程序执行的效率。
Python的多线程对于IO密集型代码比较友好
使用单线程爬虫
import requests
import time
link_list = []
with open('most.txt') as file:
file_list = file.readlines()
for each in file_list:
link = each.split()[1]
link = link.replace('\n','')
link_list.append(link)
start = time.time()
for eachone in link_list:
try:
r = requests.get(eachone)
print(r.status_code,eachone)
except Exception as e:
print('Error:',e)
end = time.time()
print('串行时间:',end-start)
使用多线程
在python中使用多线程有两种方法
1.函数式
调用_thread模块中的start_new_thread()函数产生新线程
简单示例
import _thread
import time
# 为线程定义一个函数
def print_time(threadName,delay):
count = 0
while count < 3:
time.sleep(delay)
count += 1
print(threadName,time.ctime())
_thread.start_new_thread(print_time,('Thread-1',1))
_thread.start_new_thread(print_time,('Thread-2',2))
#time.sleep(5)
print('Main Finished')
这个代码没出现自己想要的结果,暂时没看出来问题在哪。
-
_thread中使用start_new_thread ()函数来产生新线程
def start_new_thread(function, args, kwargs=None):
-
function表示线程函数
-
args为传递给线程函数的参数,它必须是tuple类型
2.类包装式
调用Threading库创建线程,从threading.Thread继承
threading模块提供了Thread类来处理线程,包括以下方法。
- run():用以表示线程活动的方法。
- start():启动线程活动。
- join([time]):等待至线程中止。阻塞调用线程直至线程的join()方法被调用为止。
- isAlive():返回线程是否是活动的。
- getName():返回线程名。
- setName():设置线程名。
示例:
import threading
import time
class myThread(threading.Thread):
def __init__(self,name,delay):
threading.Thread.__init__(self)
self.name = name
self.delay = delay
def run(self):
print('Starting:'+self.name)
print_time(self.name,self.delay)
print('Exiting:'+self.name)
def print_time(threadName,delay):
counter = 0;
while counter<3:
time.sleep(delay)
print(threadName,time.ctime())
counter += 1
threads = []
# 创建新线程
thread1 = myThread('Thread-1',