Python的进程和线程(二)——IO密集型任务
一、什么是IO密集型任务?
主要的核心任务是进行IO操作,比如写文件,进行磁盘的读写等等。
上一篇博客,对计算密集型任务,多线程并没有体现它的好处,反而话费的时间更长。对IO密集型任务多线程会有明显的优势
二、举例:
任务:爬取韩寒的博客
1、获取urls,
2、根据文章的url,爬取内容,保存文件
3、将urls分给不同的进程/线程
4、多进程/多线程爬取
step1:爬取urls
思路:用requests库来爬取页面,用beautifulSoup库来获取目标的urls值
import requests from bs4 import BeautifulSoup def get_urls(url): ''' 获取目标URL :param url: 列表页面url :return: 目标url列表 ''' res=requests.get(url).text soup = BeautifulSoup(res, features="html.parser") #通过css selector解析页面,获取元素 artile_urls=soup.select(".atc_title > a") url_list=list(i.get("href") for i in artile_urls) return(url_list)
step2:根据文章的url,爬取内容,保存文件
import time,os import requests from bs4 import BeautifulSoup def get_content(urls,dirpath): ''' 获取文章内容 :param urls: 要获取文章的url列表 :param dirpath: 文章内容文件保存路径 :return: ''' for url in urls: # print("要抓取的url是%s" % url) res = requests.get(url).content.decode("utf-8") soup = BeautifulSoup(res, features="html.parser") paragraphs = soup.select("#sina_keyword_ad_area2 > p") content="" for i in paragraphs: content+=i.get_text() if not os.path.exists(dirpath): os.makedirs(dirpath) open(dirpath + r'/' + url[-26:], 'w').write(content)
step3:将urls分给不同的进程/线程
思路:
假设我们启动n个进程,一共要爬取的url列表是urls,列表的长度为url_len
我们先把列表整除n得到长度为L(不一定能够整除,所以最后一个进程为总数-前面n-1个进程的),则第1个进程要爬取的url列表是urls[0:L],第2个进程要爬取的url列表是url[L: 2L],依次类推。。。。,最后一个进程要爬取的url列表是url[i*n:url_len]
for n in [8, 4, 2, 1]: # 将urls分割到url_list url_list = [] url_split_len = url_len // n for i in range(n): if i == n - 1: url_list.append(urls[i * url_split_len:url_len]) else: url_list.append(urls[i * url_split_len:(i + 1) * url_split_len])
参照上一篇博客,多进程和多线程函数。
我们的目标函数是,get_content,这个函数需要2个参数,一个是url列表,一个是保存文件的路径
import time def thread_process_job(n, Thread_or_Process, url_list, job): """ n: 多线程或多进程数 Thread_Process: Thread/Process类 job: countdown任务 """ local_time = time.time() # 实例化多线程或多进程 threads_or_processes = [Thread_or_Process(target=job, args=(url_list[j],str(n)+Thread_or_Process.__name__)) for j in range(n)] for t in threads_or_processes: t.start() # 开始线程或进程,必须调用 for t in threads_or_processes: t.join() # 等待直到该线程或进程结束 print(n, Thread_or_Process.__name__, " run job need ", time.time() - local_time)
step4:多进程或者多线程爬取
我们爬取前6页的数据,代码如下
if __name__=="__main__": t = time.time() urls = [] for i in range(7): url='http://blog.sina.com.cn/s/articlelist_1191258123_0_' + str(i + 1) + '.html' page_urls=get_urls(url) urls.extend(page_urls) url_len = len(urls) print("total urls number is ", url_len) for n in [8, 4, 2, 1]: # 将urls分割到url_list url_list = [] url_split_len = url_len // n for i in range(n): if i == n - 1: url_list.append(urls[i * url_split_len:url_len]) else: url_list.append(urls[i * url_split_len:(i + 1) * url_split_len]) # 分割任务后创建线程 thread_process_job(n, Thread, url_list, get_content) thread_process_job(n, Process, url_list, get_content) print("All done in ", time.time() - t)
代码可能存在的问题:
代码在运行中,可能会存在的问题是,调用get_content函数时,可能一个进程正在创建文件夹,一个进程正好在判断文件不存在要创建,在创建的时候会报错文件已存在。