python爬虫——多线程+协程(threading+gevent)
上一篇博客中我介绍了如何将爬虫改造为多进程爬虫,但是这种方法对爬虫效率的提升不是非常明显,而且占用电脑cpu较高,不是非常适用于爬虫。这篇博客中,我将介绍在爬虫中广泛运用的多线程+协程的解决方案,亲测可提高效率至少十倍以上。
本文既然提到了线程和协程,我觉得有必要在此对进程、线程、协程做一个简单的对比,了解这三个程之间的区别。
以下摘自这篇文章:http://www.cnblogs.com/guokaixin/p/6041237.html
1、进程
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。
2、线程
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。
3、协程
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
简单来说,协程相比线程来说有一个优势,就是在协程间切换时不需要很大的资源开销。在使用时可以开多个进程,然后每个进程开多个线程,每个线程开多个协程来综合使用。
from bs4 import BeautifulSoup
import datetime
import sys
import threading
reload(sys)
sys.setdefaultencoding('utf-8')
import gevent.monkey
gevent.monkey.patch_all()
import socket
socket.setdefaulttimeout(10)
path = sys.path[0] + '/data/'
1
2
3
4
5
6
7
8
9
10
11
多线程可以使用的包一般有两个:Thread和threading,threading更强大和常用一点,可以利用threading.Thread来自定义多线程类。gevent为python下的协程包。
本篇实例场景与上一篇相同,依旧为爬取外文数据库,可参考 http://blog.csdn.net/qq_23926575/article/details/76375042
def main():
"""将任务切割,开启多线程"""
listf = open(path + 'urllist.txt', 'r')
urllist = listf.readlines()
length = len(urllist)
print length
queList = []
threadNum = 6 #线程数量
#将urllist按照线程数目进行切割
for i in range(threadNum):
que = []#Queue.Queue()
left = i * (length//threadNum)
if (i+1)*(length//threadNum)<length:
right = (i+1) * (length//threadNum)
else:
right = length
for url in urllist[left:right]:
que.append(url.strip())
queList.append(que)
threadList = []
for i in range(threadNum):
threadList.append(threadDownload(queList[i]))
for thread in threadList:
thread.start() #启动线程
for thread in threadList:
thread.join() #这句是必须的,否则线程还没开始运行就结束了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
其中threadDownload是自定义的线程类,传入参数为url列表。在这个线程类中开启多个协程。
class threadDownload(threading.Thread):
"""使用threading.Thread初始化自定义类"""
def __init__(self, que):
threading.Thread.__init__(self)
self.que = que
def run(self):
length = len(self.que)
coroutineNum = 20 #协程数量
for i in range(coroutineNum):
jobs = []
left = i * (length//coroutineNum)
if (i+1)*(length//coroutineNum)<length:
right = (i+1) * (length//coroutineNum)
else:
right = length
for url in self.que[left:right]:
jobs.append(gevent.spawn(getThesis, url))
gevent.joinall(jobs)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
在上述代码中我开启了6个线程,并且在每个线程中开启了20个协程,因为需要抓取的数据量较大,故对数据进行了切割。其实也可以使用quene队列的方式来实现,多个协程共用同一个队列数据,但是管理起来稍微麻烦一点。
我在运行的过程中发现有这样几个问题,运行速度很快,是多进程的十几倍,但是抓很多数据时会抓取失败,报各种错误,最常见的是too many open files和new connection failed之类的错误,应该是每个协程都获得了一个文件的句柄,所以你可能只打开了几个文件,但是系统会认为你开启了很多,网上找了解决方案(有一个是修改ulimit,即系统设定的最大开启文件数量,在ubuntu下输入ulimit -n得到的1024是系统默认的,可以通过ulimit - n 5000修改为5000,但是也没能解决问题),但是都没有很好的方法能避免这类问题,希望懂的高手能够告知一下。
多线程+协程的方法效率高,但是很不稳定,会出现很多错误,所以在编写代码的过程中,需要做一些错误的处理,使程序更加robust,在抓取一些链接出问题的时候能够不挂掉继续抓取其他页面。建议将出错的url保存到一个文件内,最后再对这些url进行抓取。
我的urllist文件中其实有20万条数据,但是全部一次性运行会挂掉,所以我是每次读取4万条记录,然后6个线程,每个线程分别20个协程进行抓取,大概1小时搞定。
以上。有问题欢迎评论交流,如有错误也欢迎指出。
---------------------
作者:MoonBreeze_Ma
来源:CSDN
原文:https://blog.csdn.net/qq_23926575/article/details/76375337
版权声明:本文为博主原创文章,转载请附上博文链接!
python爬虫——多线程+协程(threading+gevent)的更多相关文章
- python 多进程/多线程/协程 同步异步
这篇主要是对概念的理解: 1.异步和多线程区别:二者不是一个同等关系,异步是最终目的,多线程只是我们实现异步的一种手段.异步是当一个调用请求发送给被调用者,而调用者不用等待其结果的返回而可以做其它的事 ...
- Python 多进程 多线程 协程 I/O多路复用
引言 在学习Python多进程.多线程之前,先脑补一下如下场景: 说有这么一道题:小红烧水需要10分钟,拖地需要5分钟,洗菜需要5分钟,如果一样一样去干,就是简单的加法,全部做完,需要20分钟:但是, ...
- python开发concurent.furtrue模块:concurent.furtrue的多进程与多线程&协程
一,concurent.furtrue进程池和线程池 1.1 concurent.furtrue 开启进程,多进程&线程,多线程 # concurrent.futures创建并行的任务 # 进 ...
- Python程序中的协程操作-gevent模块
目录 一.安装 二.Gevent模块介绍 2.1 用法介绍 2.2 例:遇到io主动切换 2.3 查看threading.current_thread().getName() 三.Gevent之同步与 ...
- Python并发编程协程(Coroutine)之Gevent
Gevent官网文档地址:http://www.gevent.org/contents.html 基本概念 我们通常所说的协程Coroutine其实是corporate routine的缩写,直接翻译 ...
- python网络编程-协程(协程说明,greenlet,gevent)
一:什么是协程 协程(Coroutine):,又称微线程.协程是一种用户态的轻量级线程.是由用户自己控制,CPU根本不知道协程存在. 协程拥有自己的寄存器上下文和栈. 协程调度切换时,将寄存器上下文和 ...
- 【python】-- 协程介绍及基本示例、协程遇到IO操作自动切换、协程(gevent)并发爬网页
协程介绍及基本示例 协程,又称微线程,纤程.英文名Coroutine.一句话说明什么是协程:协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到其他 ...
- python 协程 greenlet gevent
一.并发的本质 切换+保存状态 cpu正在运行一个任务,会在两种情况下切走去执行其他的任务(切换由操作系统强制控制),一种情况是该任务发生了阻塞,另外一种情况是该任务计算的时间过长时间片到了 二.协程 ...
- Python 协程(gevent)
协程,又叫微线程,协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈.因此: 协程能保留上 ...
随机推荐
- MySQL从删库到跑路(二)——MySQL字符集与乱码解析
作者:天山老妖S 链接:http://blog.51cto.com/9291927 一.字符集与编码 1.字符集简介 字符(Character)是各种文字和符号的总称,包括各国家文字.标点符号.图形符 ...
- TED #06# Questioning the universe
Stephen Hawking: Questioning the universe 1. 第一段: There is nothing bigger or older than the universe ...
- java第四天
p32~p36: 学习javadoc 1.第一步,打开一个一定规模的java项目 2.第二步,搭建测试环境 IntelliJ IDEA ——> Tools ——> Generate Jav ...
- selenium+java破解滑动验证码
2019-04-16更新 修复极验页面改版,这次采用极验官方的demo地址:https://www.geetest.com/demo/slide-bind.html 截止2019-04-16,极验和腾 ...
- Hmtl5 <input>中placeholder属性(新属性)
Hmtl5 <input>中placeholder属性(新属性) 一.定义和用法 placeholder 属性提供可描述输入字段预期值的提示信息(hint). 该提示会在输入字段为空时显示 ...
- Sybase中的load table的用法
Sybase中的load table的用法 用sybase iq作为数据仓库的存储,确实有其独特的特点和优势,但是在数据ETL的时候,如果用insert.update方式,速度太慢,大概是2秒钟左右一 ...
- Linux内核分析08
进程的切换和系统的一般执行过程 一,进程切换的关键代码switch_to分析 进程调度的时机 中断处理过程(包括时钟中断.I/O中断.系统调用和异常)中,直接调用schedule(),或者返回用户态时 ...
- linux第七章读书笔记
Vim编辑器 Vim 仅仅通过键盘来在插入和执行命令等多种模式之间切换.这使得Vim可以不用进行菜单或者鼠标操作,并且最小化组合键的操作,对文字录入员或者程序员可以大大增强速度和效率. CHAPTER ...
- shell 字符串加入变量
your_name='runoob' str="Hello, I know you are \"$your_name\"! \n" echo $str
- html中用变量作为django字典的键值
若字典为dic={'name': Barbie, 'age': 20},则在html中dic.name为Barbie,dic.age为20. 但若字典为dic={'Barbie': 1, 'Roger ...