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)
协程,又叫微线程,协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈.因此: 协程能保留上 ...
随机推荐
- Win10取消开机密码方法
1.开始菜单输入命令“netplwiz” 2.进入到用户账户页面,选择所需账户,把“要使用本计算机,用户必须输入用户名和密码”单选框取消勾选,点击应用 3.输入密码进行 这个时候会提示输入两次该账户的 ...
- qml学习笔记(二):可视化元素基类Item详解(上半场anchors等等)
原博主博客地址:http://blog.csdn.net/qq21497936本文章博客地址:http://blog.csdn.net/qq21497936/article/details/78516 ...
- TED #03# 10 ways to have a better conversation
Teach you how to talk and how to listen Many of you have already heard a lot of advice on this, thin ...
- VC++ 在两个程序中 传送字符串等常量值的方法:使用了 WM_COPYDATA 消息(转载)
转载:http://www.cnblogs.com/renyuan/p/5037536.html VC++ 在两个程序中 传递字符串等常量值的方法:使用了 WM_COPYDATA 消息的 消息作用: ...
- 利用MyBatis生成器自动生成实体类、DAO接口和Mapping映射文件
1. mybatis-generator-core-1.3.5.jar 下载地址:https://github.com/mybatis/generator/releases 2. msyql-conn ...
- ubuntu16.04解决tensorflow提示未编译使用SSE3、SSE4.1、SSE4.2、AVX、AVX2、FMA的问题【转】
本文转载自:https://blog.csdn.net/Nicholas_Wong/article/details/70215127 rticle/details/70215127 在我的机器上出现的 ...
- 分布式之zk的应用场景
分布式应用系统中,经常会用到zk,比如dubbo注册中心,kafka分布式集群等都用到zk这一工具.除了这些用来做分布式集群外,zk还有那西应用场景事我们可以使用到该工具的呢?所以接下来就是我们要了解 ...
- 安全之路:Web渗透技术及实战案例解析(第2版)
安全之路:Web渗透技术及实战案例解析(第2版)
- 【Python】使用codecs模块进行文件操作及消除文件中的BOM
前言 此前遇到过UTF8格式的文件有无BOM的导致的问题,最近在做自动化测试,读写配置文件时又遇到类似的问题,和此前一样,又是折腾了挺久之后,通过工具比较才知道原因. 两次在一个问题上面栽更头,就在想 ...
- npm安装vue详细教程(图片详解)
npm安装vue详细教程(图片详解) 一.总结 一句话总结:整个安装流程照着教程来,注意系统环境变量的配置,注意一下npm的本地仓库和缓存位置 教程 系统环境变量 仓库 缓存 1.什么情况下最适合用n ...