爬虫里的多线程基本使用

python学习网 2021-04-01 22:31:15

最近拜读瑞安·米切尔的书关于并行抓取问题有很通俗介绍:

  “网页抓去的速度很快,起码通常比雇佣几十个实习生手动网上复制数据要快很多。当然随着技术的不断进步和享乐适应,人们还是在某个时刻觉得‘不够快’,于是把目光转向分布式计算。

 和其他领域不同的是,网页抓取不能单纯依靠‘给问题增加更多进程’来提升速度,虽然运行一个process很快,但是两个进程未必能让速度提升一倍,而当运行三个乃更多时,可能你的所有请求都会被远程服务器封杀,因为他认为你是在恶意攻击。”

然而,某些场景里使用网页并行抓取或者并行线程(thread)/进程仍然有些好处:

 

1.从多个数据源(多个远程服务器),而不只是在一个数据源收集数据。

2.在已经收集到的数据上执行更加复杂/执行时间更长的操作(例如图像分析或者OCR处理)。

3.从大型web服务器收集数据,如果你已经付费,或者创建多个连接是使用协议允许的行为。

 

一.pythom3.x 使用的是_thread,而thread模块已经废弃

1.1下面用_thread实现一个小例子

 

 1 import time
 2 import  _thread
 3 def print. time(threadName, delay, iterations):
 4 
 5     start = int(time . time())
 6 
 7     for i in range(0 ,iterations):
 8 
 9         time .sleep(delay)
10 
11         seconds_ elapsed = str(int(time.time()) - start)
12 
13         print ("[] []". format(seconds_ elapsed, threadName))
14     
15 try:
16     _thread.start_new_thread(print_time, ('Fizz', 3, 33))
17     _thread.start_new_thread(print_time, ('Buzz',5,20))
18     _thread.start_new_thread(print_time,('Counter',1,100))
19 except:
20 
21     print ('Error:unable to start_thread')
22 
23 while 1:
24 
25 pass                

上面的例子开启了三个线程,分别3,5,1秒打印不同次数的名字

 

1.2 threading 模块

 

 _thread是python相当底层的模块,虽然可以有详细的管理操作,但是苦于没有高级函数,使用起来不太方便。

threadign模块是一个高级接口,更加便捷使用线程,与此同时也体现了_thread模块的特性。

在threading与os模块里,分别调用了current_thread()和getpid() 该函数来获取当前的线程/进程号,能有更好的执行效果。

 1 import os
 2 import threading
 3 from threading import Thread
 4 
 5 import time
 6 
 7 userlist = ["黎明", "张学友", "刘德华"]
 8 
 9 
10 def work(n):
11     print(f"开始给{n}打电话,进程号:{os.getpid()},线程号是:{threading.current_thread()}")  # 这里加f 是字符串格式化,作用类似于format
12     time.sleep(3)
13     print(f"给{n}打电话结束。进程号:{os.getpid()}线程号是:{threading.current_thread()}")
14 
15 
16 if __name__ == '__main__':
17     # 单线程
18     # for d in userlist:
19     #     work(d)
20     plist = []
21     # # 循环创建多进程部门,类似增加
22     # for d in userlist:
23     #     # 进程对象
24     #     p = Process(target=work, args=(d,))
25     #     # 生成进程
26     #     p.start()
27     #     # 生成的进程加入列表
28     #     plist.append(p)
29     #
30     # # 阻塞终止进程的执行
31     # [i.join() for i in plist]
32 
33     # 多线程类似部门增加人手
34     for d in userlist: 
35         # 利用for来生成线程对象,然后再为它们传入你想要传入的数据(可以不一样)
36         p = Thread(target=work, args=(d,))
37         # 生成线程
38         p.start()
39         # 生成的线程加入列表
40         plist.append(p)
41 
42     # 阻塞终止线程的执行
43     [i.join() for i in plist]

你也可以单个的创建,threading的优势是创建的线程其他线程无法访问的线程局部数据(local thread data),这样的优势对于爬虫尤其明显,它们抓取不同的网站,那么每个线程都可以

专注于自己要抓取的目标。

import threading

def  crawler(url):
       data = threading.local()
       data.visited = []
       #爬去目标网站

threading.Thread(target=crawler, args=('http://www.douban.com')).start()

这样就解决了线程之间的共享对象导致竞争条件问题。目标很明显:不需要共享就不要共享(使用local data),为了安全的共享,就需要用到Queue模块(后面会有示例)

threading的保姆职责甚至可以达到高度定制:isAlive函数的默认行为查看是否有线程仍处于活跃状态。当一个线程崩溃或者重启后才会返回True:

threading.Thread(target=crawler)

t.start()

while True:
         time.sleep(1)
         if not t.isAlive:

t = Thread(target=crawler, args=(d,))
t.start()

这样可以达到简单的监控方法。

 

1.3 还有创建线程池的方式。

明天补上,这回宿舍兄弟叫我唠嗑了,嘿嘿。

2021-04-01

19:36:13

 

阅读(2211) 评论(0)