
多线程
使用Python编撰多线程爬虫抓取百度贴吧邮箱与手机号
采集交流 • 优采云 发表了文章 • 0 个评论 • 454 次浏览 • 2020-07-05 08:00
不知道你们春节都是如何过的,反正栏主是在家睡了三天,醒来的时侯登QQ发觉有人找我要一份帖吧爬虫的源代码,想起之前练手的时侯写过一个抓取百度贴吧发贴记录中的邮箱与手机号的爬虫,于是开源分享给你们学习与参考。
本爬虫主要是对百度贴吧中各类贴子的内容进行抓取,并且剖析贴子内容将其中的手机号和邮箱地址抓取下来。主要流程在代码注释中有详尽解释。
代码在Windows7 64bit,python 2.7 64bit(安装mysqldb扩充)以及centos 6.5,python 2.7(带mysqldb扩充)环境下测试通过
工欲善其事必先利其器,大家可以从截图看出我的环境是Windows 7 + PyCharm。我的Python环境是Python 2.7 64bit。这是比较适宜菜鸟使用的开发环境。然后我再建议你们安装一个easy_install,听名子就晓得这是一个安装器,它是拿来安装一些扩充包的,比如说在python中假如我们要操作mysql数据库的话,python原生是不支持的,我们必须安装mysqldb包来让python可以操作mysql数据库,如果有easy_install的话我们只须要一行命令就可以快速安装号mysqldb扩充包,他如同php中的composer,centos中的yum,Ubuntu中的apt-get一样便捷。
相关工具可在我的github中找到:cw1997/python-tools,其中easy_install的安装只须要在python命令行下运行哪个py脚本之后稍等片刻即可邮箱爬虫软件,他会手动加入Windows的环境变量,在Windows命令行下假如输入easy_install有回显说明安装成功。
至于电脑硬件其实是越快越好,内存至少8G起步,因为爬虫本身须要大量储存和解析中间数据,尤其是多线程爬虫,在遇到抓取带有分页的列表和详情页,并且抓取数据量很大的情况下使用queue队列分配抓取任务会特别占显存。包括有的时候我们抓取的数据是使用json,如果使用mongodb等nosql数据库储存,也会太占显存。
网络联接建议使用有线网,因为市面上一些劣质的无线路由器和普通的民用无线网卡在线程开的比较大的情况下会出现间歇性断网或则数据遗失,掉包等情况,这个我亲有感受。
至于操作系统和python其实肯定是选择64位。如果你使用的是32位的操作系统,那么难以使用大显存。如果你使用的是32位的python,可能在小规模抓取数据的时侯觉得不出有哪些问题,但是当数据量变大的时侯,比如说某个列表,队列,字典上面储存了大量数据,导致python的显存占用超过2g的时侯会报显存溢出错误。原因在我以前segmentfault上提过的问题中依云的回答有解释(java – python只要占用显存达到1.9G以后httplib模块就开始报内存溢出错误 – SegmentFault)
如果你打算使用mysql储存数据,建议使用mysql5.5之后的版本,因为mysql5.5版本支持json数据类型,这样的话可以抛弃mongodb了。(有人说mysql会比mongodb稳定一点,这个我不确定。)
至于现今python都早已出了3.x版本了,为什么我这儿还使用的是python2.7?我个人选择2.7版本的缘由是自己当年很早曾经买的python核心编程这本书是第二版的,仍然以2.7为示例版本。并且目前网上一直有大量的教程资料是以2.7为版本讲解,2.7在个别方面与3.x还是有很大差异,如果我们没有学过2.7,可能对于一些细微的句型差异不是太懂会导致我们理解上出现误差,或者看不懂demo代码。而且如今还是有部份依赖包只兼容2.7版本。我的建议是假如你是打算急着学python之后去公司工作,并且公司没有老代码须要维护,那么可以考虑直接上手3.x,如果你有比较充沛的时间,并且没有太系统的大牛带,只能借助网上零零散散的博客文章来学习,那么还是先学2.7在学3.x,毕竟学会了2.7以后3.x上手也很快。
其实对于任何软件项目而言,我们但凡想知道编撰这个项目须要哪些知识点,我们都可以观察一下这个项目的主要入口文件都导出了什么包。
现在来看一下我们这个项目,作为一个刚接触python的人,可能有一些包几乎都没有用过,那么我们在本小节就来简单的谈谈这种包起哪些作用,要把握她们分别会涉及到哪些知识点,这些知识点的关键词是哪些。这篇文章并不会耗费长篇大论来从基础讲起,因此我们要学会善用百度,搜索那些知识点的关键词来自学。下面就来一一剖析一下这种知识点。
我们的爬虫抓取数据本质上就是不停的发起http请求,获取http响应,将其存入我们的笔记本中。了解http协议有助于我们在抓取数据的时侯对一些才能加速抓取速率的参数才能精准的控制,比如说keep-alive等。
我们平常编撰的程序都是单线程程序,我们写的代码都在主线程上面运行,这个主线程又运行在python进程中。关于线程和进程的解释可以参考阮一峰的博客:进程与线程的一个简单解释 – 阮一峰的网路日志
在python中实现多线程是通过一个名子称作threading的模块来实现。之前还有thread模块,但是threading对于线程的控制更强,因此我们后来都改用threading来实现多线程编程了。
关于threading多线程的一些用法,我认为这篇文章不错:[python] 专题八.多线程编程之thread和threading 大家可以参考参考。
简单来说,使用threading模块编撰多线程程序,就是先自己定义一个类,然后这个类要承继threading.Thread,并且把每位线程要做的工作代码讲到一个类的run方式中,当然若果线程本身在创建的时侯假如要做一些初始化工作,那么就要在他的__init__方法中编撰好初始化工作所要执行的代码,这个方式如同php,java中的构造方式一样。
这里还要额外讲的一点就是线程安全这个概念。通常情况下我们单线程情况下每位时刻只有一个线程在对资源(文件,变量)操作,所以不可能会出现冲突。但是当多线程的情况下,可能会出现同一个时刻两个线程在操作同一个资源,导致资源受损,所以我们须要一种机制来解决这些冲突带来的破坏,通常有加锁等操作,比如说mysql数据库的innodb表引擎有行级锁等,文件操作有读取锁等等,这些都是她们的程序底层帮我们完成了。所以我们一般只要晓得这些操作,或者这些程序对于线程安全问题做了处理,然后就可以在多线程编程中去使用它们了。而这些考虑到线程安全问题的程序通常就叫做“线程安全版本”,比如说php就有TS版本,这个TS就是Thread Safety线程安全的意思。下面我们要提到的Queue模块就是一种线程安全的队列数据结构,所以我们可以放心的在多线程编程中使用它。
***我们就要来讲讲至关重要的线程阻塞这个概念了。当我们详尽学习完threading模块以后,大概就晓得怎样创建和启动线程了。但是假如我们把线程创建好了,然后调用了start方式,那么我们会发觉似乎整个程序立刻就结束了,这是如何回事呢?其实这是因为我们在主线程中只有负责启动子线程的代码,也就意味着主线程只有启动子线程的功能,至于子线程执行的这些代码,他们本质上只是写在类上面的一个方式,并没在主线程上面真正去执行他,所以主线程启动完子线程以后他的本职工作就早已全部完成了,已经光荣离场了。既然主线程都离场了,那么python进程就跟随结束了,那么其他线程也就没有显存空间继续执行了。所以我们应当是要使主线程大婶等到所有的子线程鄙人全部执行完毕再光荣离场,那么在线程对象中有哪些方式才能把主线程卡住呢?thread.sleep嘛?这确实是个办法,但是到底应当使主线程sleep多久呢?我们并不能确切晓得执行完一个任务要多久时间,肯定不能用这个办法。所以我们这个时侯应当上网查询一下有哪些办法才能使子线程“卡住”主线程呢?“卡住”这个词似乎很粗俗了,其实说专业一点,应该称作“阻塞”,所以我们可以查询“python 子线程阻塞主线程”,如果我们会正确使用搜索引擎的话,应该会查到一个方式称作join(),没错,这个join()方法就是子线程用于阻塞主线程的方式,当子线程还未执行完毕的时侯,主线程运行到富含join()方法的这一行都会卡在那里,直到所有线程都执行完毕才能执行join()方法前面的代码。
假设有一个这样的场景,我们须要抓取一个人的博客,我们晓得这个人的博客有两个页面,一个list.php页面显示的是此博客的所有文章链接,还有一个view.php页面显示的是一篇文章的具体内容。
如果我们要把这个人的博客上面所有文章内容抓取出来,编写单线程爬虫的思路是:先用正则表达式把这个list.php页面的所有链接a标签的href属性抓取出来,存入一个名子称作article_list的链表(在python中不叫链表,叫做list,中文名列表),然后再用一个for循环遍历这个article_list链表,用各类抓取网页内容的函数把内容抓取出来之后存入数据库。
如果我们要编撰一个多线程爬虫来完成这个任务的话,就假定我们的程序用10个线程把,那么我们就要想办法把之前抓取的article_list平均分成10份,分别把每一份分配给其中一个子线程。
但是问题来了,如果我们的article_list链表宽度不是10的倍数,也就是文章数量并不是10的整数倍,那么***一个线程都会比别的线程少分配到一些任务,那么它将会更快的结束。
如果仅仅是抓取这些只有几千字的博客文章这看似没哪些问题,但是假如我们一个任务(不一定是抓取网页的任务,有可能是物理估算,或者图形渲染等等历时任务)的运行时间太长,那么这将导致极大地资源和时间浪费。我们多线程的目的就是尽可能的借助一切估算资源而且估算时间,所以我们要想办法使任务才能愈发科学合理的分配。
并且我还要考虑一种情况,就是文章数量很大的情况下,我们要既能快速抓取到文章内容,又能尽早的看见我们早已抓取到的内容,这种需求在好多CMS采集站上常常会彰显下来。
比如说我们如今要抓取的目标博客,有几千万篇文章,通常这些情况下博客还会做分页处理,那么我们若果根据前面的传统思路先抓取完list.php的所有页面至少就要几个小时甚至几天,老板假如希望你还能早日显示出抓取内容,并且尽早将早已抓取到的内容诠释到我们的CMS采集站上,那么我们就要实现一边抓取list.php而且把早已抓取到的数据丢入一个article_list链表,一边用另一个线程从article_list链表中提取早已抓取到的文章URL地址,然后这个线程再去对应的URL地址中用正则表达式取到博客文章内容。如何实现这个功能呢?
我们就须要同时开启两类线程,一类线程专门负责抓取list.php中的url之后丢入article_list链表,另外一类线程专门负责从article_list中提取出url之后从对应的view.php页面中抓取出对应的博客内容。
但是我们是否还记得上面提及过线程安全这个概念?前一类线程一边往article_list字段中写入数据,另外那一类的线程从article_list中读取数据但是删掉早已读取完毕的数据。但是python中list并不是线程安全版本的数据结构,因此这样操作会导致不可预想的错误。所以我们可以尝试使用一个愈发便捷且线程安全的数据结构,这就是我们的子标题中所提及的Queue队列数据结构。
同样Queue也有一个join()方法,这个join()方法虽然和上一个小节所提到的threading中join()方法差不多,只不过在Queue中,join()的阻塞条件是当队列不为空空的时侯才阻塞,否则继续执行join()后面的代码。在这个爬虫中我便使用了这些技巧来阻塞主线程而不是直接通过线程的join方法来阻塞主线程,这样的用处是可以不用写一个死循环来判定当前任务队列中是否还有未执行完的任务,让程序运行愈发高效,也使代码愈发柔美。
还有一个细节就是在python2.7中队列模块的名子是Queue,而在python3.x中早已更名为queue,就是首字母大小写的区别,大家假如是复制网上的代码,要记得这个小区别。
如果你们学过c语言的话,对这个模块应当会太熟悉,他就是一个负责从命令行中的命令上面提取出附送参数的模块。比如说我们一般在命令行中操作mysql数据库,就是输入mysql -h127.0.0.1 -uroot -p,其中mysql前面的“-h127.0.0.1 -uroot -p”就是可以获取的参数部份。
我们平常在编撰爬虫的时侯,有一些参数是须要用户自己自动输入的,比如说mysql的主机IP,用户名密码等等。为了使我们的程序愈加友好通用,有一些配置项是不需要硬编码在代码上面,而是在执行他的时侯我们动态传入,结合getopt模块我们就可以实现这个功能。
哈希本质上就是一类物理算法的集合,这种物理算法有个特点就是你给定一个参数,他就能输出另外一个结果,虽然这个结果太短,但是他可以近似觉得是***的。比如说我们平常听过的md5,sha-1等等,他们都属于哈希算法。他们可以把一些文件,文字经过一系列的物理运算然后弄成短短不到一百位的一段数字中文混和的字符串。
python中的hashlib模块就为我们封装好了这种物理运算函数,我们只须要简单的调用它就可以完成哈希运算。
为什么在我这个爬虫中用到了这个包呢?因为在一些插口恳求中,服务器须要带上一些校验码,保证插口恳求的数据没有被篡改或则遗失,这些校验码通常都是hash算法,所以我们须要用到这个模块来完成这些运算。
很多时侯我们抓取到的数据不是html,而是一些json数据,json本质上只是一段富含通配符对的字符串,如果我们须要提取出其中特定的字符串,那么我们须要json这个模块来将这个json字符串转换为dict类型便捷我们操作。
有的时侯我们抓取到了一些网页内容,但是我们须要将网页中的一些特定格式的内容提取下来,比如说电子邮箱的格式通常都是上面几位英语数字字母加一个@符号加的域名,而要象计算机语言描述这些格式,我们可以使用一种称作正则表达式的表达式来抒发出这些格式,并且使计算机手动从一大段字符串上将符合这些特定格式的文字匹配下来。
这个模块主要用于处理一些系统方面的事情,在这个爬虫中我用他来解决输出编码问题。
稍微学过一点法语的人都还能猜下来这个模块用于处理时间,在这个爬虫中我用它来获取当前时间戳,然后通过在主线程末尾用当前时间戳除以程序开始运行时的时间戳,得到程序的运行时间。
如图所示,开50个线程抓取100页(每页30个贴子,相当于抓取了3000个贴子)贴吧贴子内容而且从中提取出手机邮箱这个步骤共历时330秒。
这两个模块都是用于处理一些http请求,以及url低格方面的事情。我的爬虫http请求部份的核心代码就是使用这个模块完成的。
这是一个第三方模块,用于在python中操作mysql数据库。
这里我们要注意一个细节问题:mysqldb模块并不是线程安全版本,意味着我们不能在多线程中共享同一个mysql联接句柄。所以你们可以在我的代码中听到,我在每位线程的构造函数中都传入了一个新的mysql联接句柄。因此每位子线程只会用自己独立的mysql联接句柄。
这也是一个第三方模块,网上还能找到相关代码,这个模块主要用于向命令行中输出彩色字符串。比如说我们一般爬虫出现错误,要输出黄色的字体会比较醒目,就要使用到这个模块。
如果你们在网路质量不是挺好的环境下使用该爬虫,会发觉有的时侯会报如图所示的异常,这是我为了偷懒并没有写各类异常处理的逻辑。
通常情况下我们假如要编撰高度自动化的爬虫,那么就须要意料到我们的爬虫可能会遇见的所有异常情况邮箱爬虫软件,针对这种异常情况做处理。
比如说如图所示的错误,我们就应当把当时正在处理的任务重新伸入任务队列,否则我们还会出现遗漏信息的情况。这也是爬虫编撰的一个复杂点。
其实多线程爬虫的编撰也不复杂,多看示例代码,多自己动手尝试,多去社区,论坛交流,很多精典的书上对多线程编程也有特别详尽的解释。这篇文章本质上主要还是一篇科普文章,内容讲解的都不是太深入,大家还须要课外自己多结合网上各类资料自己学习。
【编辑推荐】
Python程序员都该用的一个库Python正则表达式re模块简明笔记这种方式推动Python开发者提升效率Python并发编程之线程池/进程池Python黑魔法之描述符 查看全部
本爬虫主要是对百度贴吧中各类贴子的内容进行抓取,并且剖析贴子内容将其中的手机号和邮箱地址抓取下来。主要流程在代码注释中有详尽解释。
代码在Windows7 64bit,python 2.7 64bit(安装mysqldb扩充)以及centos 6.5,python 2.7(带mysqldb扩充)环境下测试通过
工欲善其事必先利其器,大家可以从截图看出我的环境是Windows 7 + PyCharm。我的Python环境是Python 2.7 64bit。这是比较适宜菜鸟使用的开发环境。然后我再建议你们安装一个easy_install,听名子就晓得这是一个安装器,它是拿来安装一些扩充包的,比如说在python中假如我们要操作mysql数据库的话,python原生是不支持的,我们必须安装mysqldb包来让python可以操作mysql数据库,如果有easy_install的话我们只须要一行命令就可以快速安装号mysqldb扩充包,他如同php中的composer,centos中的yum,Ubuntu中的apt-get一样便捷。
相关工具可在我的github中找到:cw1997/python-tools,其中easy_install的安装只须要在python命令行下运行哪个py脚本之后稍等片刻即可邮箱爬虫软件,他会手动加入Windows的环境变量,在Windows命令行下假如输入easy_install有回显说明安装成功。
至于电脑硬件其实是越快越好,内存至少8G起步,因为爬虫本身须要大量储存和解析中间数据,尤其是多线程爬虫,在遇到抓取带有分页的列表和详情页,并且抓取数据量很大的情况下使用queue队列分配抓取任务会特别占显存。包括有的时候我们抓取的数据是使用json,如果使用mongodb等nosql数据库储存,也会太占显存。
网络联接建议使用有线网,因为市面上一些劣质的无线路由器和普通的民用无线网卡在线程开的比较大的情况下会出现间歇性断网或则数据遗失,掉包等情况,这个我亲有感受。
至于操作系统和python其实肯定是选择64位。如果你使用的是32位的操作系统,那么难以使用大显存。如果你使用的是32位的python,可能在小规模抓取数据的时侯觉得不出有哪些问题,但是当数据量变大的时侯,比如说某个列表,队列,字典上面储存了大量数据,导致python的显存占用超过2g的时侯会报显存溢出错误。原因在我以前segmentfault上提过的问题中依云的回答有解释(java – python只要占用显存达到1.9G以后httplib模块就开始报内存溢出错误 – SegmentFault)
如果你打算使用mysql储存数据,建议使用mysql5.5之后的版本,因为mysql5.5版本支持json数据类型,这样的话可以抛弃mongodb了。(有人说mysql会比mongodb稳定一点,这个我不确定。)
至于现今python都早已出了3.x版本了,为什么我这儿还使用的是python2.7?我个人选择2.7版本的缘由是自己当年很早曾经买的python核心编程这本书是第二版的,仍然以2.7为示例版本。并且目前网上一直有大量的教程资料是以2.7为版本讲解,2.7在个别方面与3.x还是有很大差异,如果我们没有学过2.7,可能对于一些细微的句型差异不是太懂会导致我们理解上出现误差,或者看不懂demo代码。而且如今还是有部份依赖包只兼容2.7版本。我的建议是假如你是打算急着学python之后去公司工作,并且公司没有老代码须要维护,那么可以考虑直接上手3.x,如果你有比较充沛的时间,并且没有太系统的大牛带,只能借助网上零零散散的博客文章来学习,那么还是先学2.7在学3.x,毕竟学会了2.7以后3.x上手也很快。
其实对于任何软件项目而言,我们但凡想知道编撰这个项目须要哪些知识点,我们都可以观察一下这个项目的主要入口文件都导出了什么包。
现在来看一下我们这个项目,作为一个刚接触python的人,可能有一些包几乎都没有用过,那么我们在本小节就来简单的谈谈这种包起哪些作用,要把握她们分别会涉及到哪些知识点,这些知识点的关键词是哪些。这篇文章并不会耗费长篇大论来从基础讲起,因此我们要学会善用百度,搜索那些知识点的关键词来自学。下面就来一一剖析一下这种知识点。
我们的爬虫抓取数据本质上就是不停的发起http请求,获取http响应,将其存入我们的笔记本中。了解http协议有助于我们在抓取数据的时侯对一些才能加速抓取速率的参数才能精准的控制,比如说keep-alive等。
我们平常编撰的程序都是单线程程序,我们写的代码都在主线程上面运行,这个主线程又运行在python进程中。关于线程和进程的解释可以参考阮一峰的博客:进程与线程的一个简单解释 – 阮一峰的网路日志
在python中实现多线程是通过一个名子称作threading的模块来实现。之前还有thread模块,但是threading对于线程的控制更强,因此我们后来都改用threading来实现多线程编程了。
关于threading多线程的一些用法,我认为这篇文章不错:[python] 专题八.多线程编程之thread和threading 大家可以参考参考。
简单来说,使用threading模块编撰多线程程序,就是先自己定义一个类,然后这个类要承继threading.Thread,并且把每位线程要做的工作代码讲到一个类的run方式中,当然若果线程本身在创建的时侯假如要做一些初始化工作,那么就要在他的__init__方法中编撰好初始化工作所要执行的代码,这个方式如同php,java中的构造方式一样。
这里还要额外讲的一点就是线程安全这个概念。通常情况下我们单线程情况下每位时刻只有一个线程在对资源(文件,变量)操作,所以不可能会出现冲突。但是当多线程的情况下,可能会出现同一个时刻两个线程在操作同一个资源,导致资源受损,所以我们须要一种机制来解决这些冲突带来的破坏,通常有加锁等操作,比如说mysql数据库的innodb表引擎有行级锁等,文件操作有读取锁等等,这些都是她们的程序底层帮我们完成了。所以我们一般只要晓得这些操作,或者这些程序对于线程安全问题做了处理,然后就可以在多线程编程中去使用它们了。而这些考虑到线程安全问题的程序通常就叫做“线程安全版本”,比如说php就有TS版本,这个TS就是Thread Safety线程安全的意思。下面我们要提到的Queue模块就是一种线程安全的队列数据结构,所以我们可以放心的在多线程编程中使用它。
***我们就要来讲讲至关重要的线程阻塞这个概念了。当我们详尽学习完threading模块以后,大概就晓得怎样创建和启动线程了。但是假如我们把线程创建好了,然后调用了start方式,那么我们会发觉似乎整个程序立刻就结束了,这是如何回事呢?其实这是因为我们在主线程中只有负责启动子线程的代码,也就意味着主线程只有启动子线程的功能,至于子线程执行的这些代码,他们本质上只是写在类上面的一个方式,并没在主线程上面真正去执行他,所以主线程启动完子线程以后他的本职工作就早已全部完成了,已经光荣离场了。既然主线程都离场了,那么python进程就跟随结束了,那么其他线程也就没有显存空间继续执行了。所以我们应当是要使主线程大婶等到所有的子线程鄙人全部执行完毕再光荣离场,那么在线程对象中有哪些方式才能把主线程卡住呢?thread.sleep嘛?这确实是个办法,但是到底应当使主线程sleep多久呢?我们并不能确切晓得执行完一个任务要多久时间,肯定不能用这个办法。所以我们这个时侯应当上网查询一下有哪些办法才能使子线程“卡住”主线程呢?“卡住”这个词似乎很粗俗了,其实说专业一点,应该称作“阻塞”,所以我们可以查询“python 子线程阻塞主线程”,如果我们会正确使用搜索引擎的话,应该会查到一个方式称作join(),没错,这个join()方法就是子线程用于阻塞主线程的方式,当子线程还未执行完毕的时侯,主线程运行到富含join()方法的这一行都会卡在那里,直到所有线程都执行完毕才能执行join()方法前面的代码。
假设有一个这样的场景,我们须要抓取一个人的博客,我们晓得这个人的博客有两个页面,一个list.php页面显示的是此博客的所有文章链接,还有一个view.php页面显示的是一篇文章的具体内容。
如果我们要把这个人的博客上面所有文章内容抓取出来,编写单线程爬虫的思路是:先用正则表达式把这个list.php页面的所有链接a标签的href属性抓取出来,存入一个名子称作article_list的链表(在python中不叫链表,叫做list,中文名列表),然后再用一个for循环遍历这个article_list链表,用各类抓取网页内容的函数把内容抓取出来之后存入数据库。
如果我们要编撰一个多线程爬虫来完成这个任务的话,就假定我们的程序用10个线程把,那么我们就要想办法把之前抓取的article_list平均分成10份,分别把每一份分配给其中一个子线程。
但是问题来了,如果我们的article_list链表宽度不是10的倍数,也就是文章数量并不是10的整数倍,那么***一个线程都会比别的线程少分配到一些任务,那么它将会更快的结束。
如果仅仅是抓取这些只有几千字的博客文章这看似没哪些问题,但是假如我们一个任务(不一定是抓取网页的任务,有可能是物理估算,或者图形渲染等等历时任务)的运行时间太长,那么这将导致极大地资源和时间浪费。我们多线程的目的就是尽可能的借助一切估算资源而且估算时间,所以我们要想办法使任务才能愈发科学合理的分配。
并且我还要考虑一种情况,就是文章数量很大的情况下,我们要既能快速抓取到文章内容,又能尽早的看见我们早已抓取到的内容,这种需求在好多CMS采集站上常常会彰显下来。
比如说我们如今要抓取的目标博客,有几千万篇文章,通常这些情况下博客还会做分页处理,那么我们若果根据前面的传统思路先抓取完list.php的所有页面至少就要几个小时甚至几天,老板假如希望你还能早日显示出抓取内容,并且尽早将早已抓取到的内容诠释到我们的CMS采集站上,那么我们就要实现一边抓取list.php而且把早已抓取到的数据丢入一个article_list链表,一边用另一个线程从article_list链表中提取早已抓取到的文章URL地址,然后这个线程再去对应的URL地址中用正则表达式取到博客文章内容。如何实现这个功能呢?
我们就须要同时开启两类线程,一类线程专门负责抓取list.php中的url之后丢入article_list链表,另外一类线程专门负责从article_list中提取出url之后从对应的view.php页面中抓取出对应的博客内容。
但是我们是否还记得上面提及过线程安全这个概念?前一类线程一边往article_list字段中写入数据,另外那一类的线程从article_list中读取数据但是删掉早已读取完毕的数据。但是python中list并不是线程安全版本的数据结构,因此这样操作会导致不可预想的错误。所以我们可以尝试使用一个愈发便捷且线程安全的数据结构,这就是我们的子标题中所提及的Queue队列数据结构。
同样Queue也有一个join()方法,这个join()方法虽然和上一个小节所提到的threading中join()方法差不多,只不过在Queue中,join()的阻塞条件是当队列不为空空的时侯才阻塞,否则继续执行join()后面的代码。在这个爬虫中我便使用了这些技巧来阻塞主线程而不是直接通过线程的join方法来阻塞主线程,这样的用处是可以不用写一个死循环来判定当前任务队列中是否还有未执行完的任务,让程序运行愈发高效,也使代码愈发柔美。
还有一个细节就是在python2.7中队列模块的名子是Queue,而在python3.x中早已更名为queue,就是首字母大小写的区别,大家假如是复制网上的代码,要记得这个小区别。
如果你们学过c语言的话,对这个模块应当会太熟悉,他就是一个负责从命令行中的命令上面提取出附送参数的模块。比如说我们一般在命令行中操作mysql数据库,就是输入mysql -h127.0.0.1 -uroot -p,其中mysql前面的“-h127.0.0.1 -uroot -p”就是可以获取的参数部份。
我们平常在编撰爬虫的时侯,有一些参数是须要用户自己自动输入的,比如说mysql的主机IP,用户名密码等等。为了使我们的程序愈加友好通用,有一些配置项是不需要硬编码在代码上面,而是在执行他的时侯我们动态传入,结合getopt模块我们就可以实现这个功能。
哈希本质上就是一类物理算法的集合,这种物理算法有个特点就是你给定一个参数,他就能输出另外一个结果,虽然这个结果太短,但是他可以近似觉得是***的。比如说我们平常听过的md5,sha-1等等,他们都属于哈希算法。他们可以把一些文件,文字经过一系列的物理运算然后弄成短短不到一百位的一段数字中文混和的字符串。
python中的hashlib模块就为我们封装好了这种物理运算函数,我们只须要简单的调用它就可以完成哈希运算。
为什么在我这个爬虫中用到了这个包呢?因为在一些插口恳求中,服务器须要带上一些校验码,保证插口恳求的数据没有被篡改或则遗失,这些校验码通常都是hash算法,所以我们须要用到这个模块来完成这些运算。
很多时侯我们抓取到的数据不是html,而是一些json数据,json本质上只是一段富含通配符对的字符串,如果我们须要提取出其中特定的字符串,那么我们须要json这个模块来将这个json字符串转换为dict类型便捷我们操作。
有的时侯我们抓取到了一些网页内容,但是我们须要将网页中的一些特定格式的内容提取下来,比如说电子邮箱的格式通常都是上面几位英语数字字母加一个@符号加的域名,而要象计算机语言描述这些格式,我们可以使用一种称作正则表达式的表达式来抒发出这些格式,并且使计算机手动从一大段字符串上将符合这些特定格式的文字匹配下来。
这个模块主要用于处理一些系统方面的事情,在这个爬虫中我用他来解决输出编码问题。
稍微学过一点法语的人都还能猜下来这个模块用于处理时间,在这个爬虫中我用它来获取当前时间戳,然后通过在主线程末尾用当前时间戳除以程序开始运行时的时间戳,得到程序的运行时间。
如图所示,开50个线程抓取100页(每页30个贴子,相当于抓取了3000个贴子)贴吧贴子内容而且从中提取出手机邮箱这个步骤共历时330秒。
这两个模块都是用于处理一些http请求,以及url低格方面的事情。我的爬虫http请求部份的核心代码就是使用这个模块完成的。
这是一个第三方模块,用于在python中操作mysql数据库。
这里我们要注意一个细节问题:mysqldb模块并不是线程安全版本,意味着我们不能在多线程中共享同一个mysql联接句柄。所以你们可以在我的代码中听到,我在每位线程的构造函数中都传入了一个新的mysql联接句柄。因此每位子线程只会用自己独立的mysql联接句柄。
这也是一个第三方模块,网上还能找到相关代码,这个模块主要用于向命令行中输出彩色字符串。比如说我们一般爬虫出现错误,要输出黄色的字体会比较醒目,就要使用到这个模块。
如果你们在网路质量不是挺好的环境下使用该爬虫,会发觉有的时侯会报如图所示的异常,这是我为了偷懒并没有写各类异常处理的逻辑。
通常情况下我们假如要编撰高度自动化的爬虫,那么就须要意料到我们的爬虫可能会遇见的所有异常情况邮箱爬虫软件,针对这种异常情况做处理。
比如说如图所示的错误,我们就应当把当时正在处理的任务重新伸入任务队列,否则我们还会出现遗漏信息的情况。这也是爬虫编撰的一个复杂点。
其实多线程爬虫的编撰也不复杂,多看示例代码,多自己动手尝试,多去社区,论坛交流,很多精典的书上对多线程编程也有特别详尽的解释。这篇文章本质上主要还是一篇科普文章,内容讲解的都不是太深入,大家还须要课外自己多结合网上各类资料自己学习。
【编辑推荐】
Python程序员都该用的一个库Python正则表达式re模块简明笔记这种方式推动Python开发者提升效率Python并发编程之线程池/进程池Python黑魔法之描述符 查看全部
不知道你们春节都是如何过的,反正栏主是在家睡了三天,醒来的时侯登QQ发觉有人找我要一份帖吧爬虫的源代码,想起之前练手的时侯写过一个抓取百度贴吧发贴记录中的邮箱与手机号的爬虫,于是开源分享给你们学习与参考。
本爬虫主要是对百度贴吧中各类贴子的内容进行抓取,并且剖析贴子内容将其中的手机号和邮箱地址抓取下来。主要流程在代码注释中有详尽解释。
代码在Windows7 64bit,python 2.7 64bit(安装mysqldb扩充)以及centos 6.5,python 2.7(带mysqldb扩充)环境下测试通过


工欲善其事必先利其器,大家可以从截图看出我的环境是Windows 7 + PyCharm。我的Python环境是Python 2.7 64bit。这是比较适宜菜鸟使用的开发环境。然后我再建议你们安装一个easy_install,听名子就晓得这是一个安装器,它是拿来安装一些扩充包的,比如说在python中假如我们要操作mysql数据库的话,python原生是不支持的,我们必须安装mysqldb包来让python可以操作mysql数据库,如果有easy_install的话我们只须要一行命令就可以快速安装号mysqldb扩充包,他如同php中的composer,centos中的yum,Ubuntu中的apt-get一样便捷。
相关工具可在我的github中找到:cw1997/python-tools,其中easy_install的安装只须要在python命令行下运行哪个py脚本之后稍等片刻即可邮箱爬虫软件,他会手动加入Windows的环境变量,在Windows命令行下假如输入easy_install有回显说明安装成功。
至于电脑硬件其实是越快越好,内存至少8G起步,因为爬虫本身须要大量储存和解析中间数据,尤其是多线程爬虫,在遇到抓取带有分页的列表和详情页,并且抓取数据量很大的情况下使用queue队列分配抓取任务会特别占显存。包括有的时候我们抓取的数据是使用json,如果使用mongodb等nosql数据库储存,也会太占显存。
网络联接建议使用有线网,因为市面上一些劣质的无线路由器和普通的民用无线网卡在线程开的比较大的情况下会出现间歇性断网或则数据遗失,掉包等情况,这个我亲有感受。
至于操作系统和python其实肯定是选择64位。如果你使用的是32位的操作系统,那么难以使用大显存。如果你使用的是32位的python,可能在小规模抓取数据的时侯觉得不出有哪些问题,但是当数据量变大的时侯,比如说某个列表,队列,字典上面储存了大量数据,导致python的显存占用超过2g的时侯会报显存溢出错误。原因在我以前segmentfault上提过的问题中依云的回答有解释(java – python只要占用显存达到1.9G以后httplib模块就开始报内存溢出错误 – SegmentFault)
如果你打算使用mysql储存数据,建议使用mysql5.5之后的版本,因为mysql5.5版本支持json数据类型,这样的话可以抛弃mongodb了。(有人说mysql会比mongodb稳定一点,这个我不确定。)
至于现今python都早已出了3.x版本了,为什么我这儿还使用的是python2.7?我个人选择2.7版本的缘由是自己当年很早曾经买的python核心编程这本书是第二版的,仍然以2.7为示例版本。并且目前网上一直有大量的教程资料是以2.7为版本讲解,2.7在个别方面与3.x还是有很大差异,如果我们没有学过2.7,可能对于一些细微的句型差异不是太懂会导致我们理解上出现误差,或者看不懂demo代码。而且如今还是有部份依赖包只兼容2.7版本。我的建议是假如你是打算急着学python之后去公司工作,并且公司没有老代码须要维护,那么可以考虑直接上手3.x,如果你有比较充沛的时间,并且没有太系统的大牛带,只能借助网上零零散散的博客文章来学习,那么还是先学2.7在学3.x,毕竟学会了2.7以后3.x上手也很快。
其实对于任何软件项目而言,我们但凡想知道编撰这个项目须要哪些知识点,我们都可以观察一下这个项目的主要入口文件都导出了什么包。

现在来看一下我们这个项目,作为一个刚接触python的人,可能有一些包几乎都没有用过,那么我们在本小节就来简单的谈谈这种包起哪些作用,要把握她们分别会涉及到哪些知识点,这些知识点的关键词是哪些。这篇文章并不会耗费长篇大论来从基础讲起,因此我们要学会善用百度,搜索那些知识点的关键词来自学。下面就来一一剖析一下这种知识点。
我们的爬虫抓取数据本质上就是不停的发起http请求,获取http响应,将其存入我们的笔记本中。了解http协议有助于我们在抓取数据的时侯对一些才能加速抓取速率的参数才能精准的控制,比如说keep-alive等。
我们平常编撰的程序都是单线程程序,我们写的代码都在主线程上面运行,这个主线程又运行在python进程中。关于线程和进程的解释可以参考阮一峰的博客:进程与线程的一个简单解释 – 阮一峰的网路日志
在python中实现多线程是通过一个名子称作threading的模块来实现。之前还有thread模块,但是threading对于线程的控制更强,因此我们后来都改用threading来实现多线程编程了。
关于threading多线程的一些用法,我认为这篇文章不错:[python] 专题八.多线程编程之thread和threading 大家可以参考参考。
简单来说,使用threading模块编撰多线程程序,就是先自己定义一个类,然后这个类要承继threading.Thread,并且把每位线程要做的工作代码讲到一个类的run方式中,当然若果线程本身在创建的时侯假如要做一些初始化工作,那么就要在他的__init__方法中编撰好初始化工作所要执行的代码,这个方式如同php,java中的构造方式一样。
这里还要额外讲的一点就是线程安全这个概念。通常情况下我们单线程情况下每位时刻只有一个线程在对资源(文件,变量)操作,所以不可能会出现冲突。但是当多线程的情况下,可能会出现同一个时刻两个线程在操作同一个资源,导致资源受损,所以我们须要一种机制来解决这些冲突带来的破坏,通常有加锁等操作,比如说mysql数据库的innodb表引擎有行级锁等,文件操作有读取锁等等,这些都是她们的程序底层帮我们完成了。所以我们一般只要晓得这些操作,或者这些程序对于线程安全问题做了处理,然后就可以在多线程编程中去使用它们了。而这些考虑到线程安全问题的程序通常就叫做“线程安全版本”,比如说php就有TS版本,这个TS就是Thread Safety线程安全的意思。下面我们要提到的Queue模块就是一种线程安全的队列数据结构,所以我们可以放心的在多线程编程中使用它。
***我们就要来讲讲至关重要的线程阻塞这个概念了。当我们详尽学习完threading模块以后,大概就晓得怎样创建和启动线程了。但是假如我们把线程创建好了,然后调用了start方式,那么我们会发觉似乎整个程序立刻就结束了,这是如何回事呢?其实这是因为我们在主线程中只有负责启动子线程的代码,也就意味着主线程只有启动子线程的功能,至于子线程执行的这些代码,他们本质上只是写在类上面的一个方式,并没在主线程上面真正去执行他,所以主线程启动完子线程以后他的本职工作就早已全部完成了,已经光荣离场了。既然主线程都离场了,那么python进程就跟随结束了,那么其他线程也就没有显存空间继续执行了。所以我们应当是要使主线程大婶等到所有的子线程鄙人全部执行完毕再光荣离场,那么在线程对象中有哪些方式才能把主线程卡住呢?thread.sleep嘛?这确实是个办法,但是到底应当使主线程sleep多久呢?我们并不能确切晓得执行完一个任务要多久时间,肯定不能用这个办法。所以我们这个时侯应当上网查询一下有哪些办法才能使子线程“卡住”主线程呢?“卡住”这个词似乎很粗俗了,其实说专业一点,应该称作“阻塞”,所以我们可以查询“python 子线程阻塞主线程”,如果我们会正确使用搜索引擎的话,应该会查到一个方式称作join(),没错,这个join()方法就是子线程用于阻塞主线程的方式,当子线程还未执行完毕的时侯,主线程运行到富含join()方法的这一行都会卡在那里,直到所有线程都执行完毕才能执行join()方法前面的代码。
假设有一个这样的场景,我们须要抓取一个人的博客,我们晓得这个人的博客有两个页面,一个list.php页面显示的是此博客的所有文章链接,还有一个view.php页面显示的是一篇文章的具体内容。
如果我们要把这个人的博客上面所有文章内容抓取出来,编写单线程爬虫的思路是:先用正则表达式把这个list.php页面的所有链接a标签的href属性抓取出来,存入一个名子称作article_list的链表(在python中不叫链表,叫做list,中文名列表),然后再用一个for循环遍历这个article_list链表,用各类抓取网页内容的函数把内容抓取出来之后存入数据库。
如果我们要编撰一个多线程爬虫来完成这个任务的话,就假定我们的程序用10个线程把,那么我们就要想办法把之前抓取的article_list平均分成10份,分别把每一份分配给其中一个子线程。
但是问题来了,如果我们的article_list链表宽度不是10的倍数,也就是文章数量并不是10的整数倍,那么***一个线程都会比别的线程少分配到一些任务,那么它将会更快的结束。
如果仅仅是抓取这些只有几千字的博客文章这看似没哪些问题,但是假如我们一个任务(不一定是抓取网页的任务,有可能是物理估算,或者图形渲染等等历时任务)的运行时间太长,那么这将导致极大地资源和时间浪费。我们多线程的目的就是尽可能的借助一切估算资源而且估算时间,所以我们要想办法使任务才能愈发科学合理的分配。
并且我还要考虑一种情况,就是文章数量很大的情况下,我们要既能快速抓取到文章内容,又能尽早的看见我们早已抓取到的内容,这种需求在好多CMS采集站上常常会彰显下来。
比如说我们如今要抓取的目标博客,有几千万篇文章,通常这些情况下博客还会做分页处理,那么我们若果根据前面的传统思路先抓取完list.php的所有页面至少就要几个小时甚至几天,老板假如希望你还能早日显示出抓取内容,并且尽早将早已抓取到的内容诠释到我们的CMS采集站上,那么我们就要实现一边抓取list.php而且把早已抓取到的数据丢入一个article_list链表,一边用另一个线程从article_list链表中提取早已抓取到的文章URL地址,然后这个线程再去对应的URL地址中用正则表达式取到博客文章内容。如何实现这个功能呢?
我们就须要同时开启两类线程,一类线程专门负责抓取list.php中的url之后丢入article_list链表,另外一类线程专门负责从article_list中提取出url之后从对应的view.php页面中抓取出对应的博客内容。
但是我们是否还记得上面提及过线程安全这个概念?前一类线程一边往article_list字段中写入数据,另外那一类的线程从article_list中读取数据但是删掉早已读取完毕的数据。但是python中list并不是线程安全版本的数据结构,因此这样操作会导致不可预想的错误。所以我们可以尝试使用一个愈发便捷且线程安全的数据结构,这就是我们的子标题中所提及的Queue队列数据结构。
同样Queue也有一个join()方法,这个join()方法虽然和上一个小节所提到的threading中join()方法差不多,只不过在Queue中,join()的阻塞条件是当队列不为空空的时侯才阻塞,否则继续执行join()后面的代码。在这个爬虫中我便使用了这些技巧来阻塞主线程而不是直接通过线程的join方法来阻塞主线程,这样的用处是可以不用写一个死循环来判定当前任务队列中是否还有未执行完的任务,让程序运行愈发高效,也使代码愈发柔美。
还有一个细节就是在python2.7中队列模块的名子是Queue,而在python3.x中早已更名为queue,就是首字母大小写的区别,大家假如是复制网上的代码,要记得这个小区别。
如果你们学过c语言的话,对这个模块应当会太熟悉,他就是一个负责从命令行中的命令上面提取出附送参数的模块。比如说我们一般在命令行中操作mysql数据库,就是输入mysql -h127.0.0.1 -uroot -p,其中mysql前面的“-h127.0.0.1 -uroot -p”就是可以获取的参数部份。
我们平常在编撰爬虫的时侯,有一些参数是须要用户自己自动输入的,比如说mysql的主机IP,用户名密码等等。为了使我们的程序愈加友好通用,有一些配置项是不需要硬编码在代码上面,而是在执行他的时侯我们动态传入,结合getopt模块我们就可以实现这个功能。
哈希本质上就是一类物理算法的集合,这种物理算法有个特点就是你给定一个参数,他就能输出另外一个结果,虽然这个结果太短,但是他可以近似觉得是***的。比如说我们平常听过的md5,sha-1等等,他们都属于哈希算法。他们可以把一些文件,文字经过一系列的物理运算然后弄成短短不到一百位的一段数字中文混和的字符串。
python中的hashlib模块就为我们封装好了这种物理运算函数,我们只须要简单的调用它就可以完成哈希运算。
为什么在我这个爬虫中用到了这个包呢?因为在一些插口恳求中,服务器须要带上一些校验码,保证插口恳求的数据没有被篡改或则遗失,这些校验码通常都是hash算法,所以我们须要用到这个模块来完成这些运算。
很多时侯我们抓取到的数据不是html,而是一些json数据,json本质上只是一段富含通配符对的字符串,如果我们须要提取出其中特定的字符串,那么我们须要json这个模块来将这个json字符串转换为dict类型便捷我们操作。
有的时侯我们抓取到了一些网页内容,但是我们须要将网页中的一些特定格式的内容提取下来,比如说电子邮箱的格式通常都是上面几位英语数字字母加一个@符号加的域名,而要象计算机语言描述这些格式,我们可以使用一种称作正则表达式的表达式来抒发出这些格式,并且使计算机手动从一大段字符串上将符合这些特定格式的文字匹配下来。
这个模块主要用于处理一些系统方面的事情,在这个爬虫中我用他来解决输出编码问题。
稍微学过一点法语的人都还能猜下来这个模块用于处理时间,在这个爬虫中我用它来获取当前时间戳,然后通过在主线程末尾用当前时间戳除以程序开始运行时的时间戳,得到程序的运行时间。

如图所示,开50个线程抓取100页(每页30个贴子,相当于抓取了3000个贴子)贴吧贴子内容而且从中提取出手机邮箱这个步骤共历时330秒。
这两个模块都是用于处理一些http请求,以及url低格方面的事情。我的爬虫http请求部份的核心代码就是使用这个模块完成的。
这是一个第三方模块,用于在python中操作mysql数据库。
这里我们要注意一个细节问题:mysqldb模块并不是线程安全版本,意味着我们不能在多线程中共享同一个mysql联接句柄。所以你们可以在我的代码中听到,我在每位线程的构造函数中都传入了一个新的mysql联接句柄。因此每位子线程只会用自己独立的mysql联接句柄。
这也是一个第三方模块,网上还能找到相关代码,这个模块主要用于向命令行中输出彩色字符串。比如说我们一般爬虫出现错误,要输出黄色的字体会比较醒目,就要使用到这个模块。

如果你们在网路质量不是挺好的环境下使用该爬虫,会发觉有的时侯会报如图所示的异常,这是我为了偷懒并没有写各类异常处理的逻辑。
通常情况下我们假如要编撰高度自动化的爬虫,那么就须要意料到我们的爬虫可能会遇见的所有异常情况邮箱爬虫软件,针对这种异常情况做处理。
比如说如图所示的错误,我们就应当把当时正在处理的任务重新伸入任务队列,否则我们还会出现遗漏信息的情况。这也是爬虫编撰的一个复杂点。
其实多线程爬虫的编撰也不复杂,多看示例代码,多自己动手尝试,多去社区,论坛交流,很多精典的书上对多线程编程也有特别详尽的解释。这篇文章本质上主要还是一篇科普文章,内容讲解的都不是太深入,大家还须要课外自己多结合网上各类资料自己学习。
【编辑推荐】
Python程序员都该用的一个库Python正则表达式re模块简明笔记这种方式推动Python开发者提升效率Python并发编程之线程池/进程池Python黑魔法之描述符
高拓展性的Java多线程爬虫框架reptile(个人开源项目)
采集交流 • 优采云 发表了文章 • 0 个评论 • 292 次浏览 • 2020-05-07 08:03
Reptile是一个具有高拓展性的可支持单机与集群布署Java多线程爬虫框架,该框架可简化爬虫的开发流程。该框架各个组件高内聚松耦合的特点使用户可以对不同组件进行订制来满足不同的需求。
Reptile.png
Reptile作为爬虫主体可在主线程运行也可以异步运行,爬虫主要有四个核心组件:
Downloader 执行恳求下载与解析响应ResponseHandler 由使用者提供实现来对响应处理,生成Result结果与新的爬取恳求RequestConsumer 来对处理的结果Result进行消费,例如持久化储存java单机爬虫框架,用户可自定义其具体实现
四个组件之间的关系如构架图所示,它们之间的相互调用产生一个完整的工作流并在Workflow线程中运行,Reptile爬虫会依照配置的线程数目通过线程池创建指定数目的工作流线程并发执行工作流任务。
clone项目并建立发布到本地库房
git clone git@github.com:xiepuhuan/reptile.git
cd reptile
mvn -Dmaven.test.skip=true
在项目中使用Maven引入对应的依赖
<dependency>
<groupId>com.xiepuhuan</groupId>
<artifactId>reptile</artifactId>
<version>0.3</version>
</dependency>
实现ResponseHandler插口,重写isSupport与handle技巧。实现Consumer插口java单机爬虫框架,重写consume方式,执行对数据的消费,可在该方式中对响应处理结果进行持久化等操作,目前提供了ConsoleConsumer,JsonFileConsumer, MongoDBConsumer等实现,默认使用ConsoleConsumer。
public class ZhihuPageHandler implements ResponseHandler {
private static final String[] URLS = new String[] {
"https://www.zhihu.com/api/v4/s ... ot%3B
};
@Override
public List<Request> handle(Response response, Result result) {
Content content = response.getContent();
JSONObject jsonObject = JSON.parseObject(content.getContent(), JSONObject.class);
result.setResults(jsonObject.getInnerMap());
JSONObject paging = jsonObject.getJSONObject("paging");
if (!paging.getBoolean("is_end")) {
List<Request> requests = new ArrayList<>();
requests.add(new Request(paging.getString("next")));
return requests;
}
return null;
}
@Override
public boolean isSupport(Request request, Response response) {
return true;
}
public static void main(String[] args) {
// 构建Reptile爬虫配置类,
ReptileConfig config = ReptileConfig.Builder.cutom()
.setThreadCount(8)
.appendResponseHandlers(new ZhihuPageHandler())
.setDeploymentMode(DeploymentModeEnum.SINGLE)
.setConsumer(new ConsoleConsumer())
.build();
// 根据reptile配置构建Reptile爬虫并添加爬去的URL
Reptile reptile = Reptile.create(config).addUrls(URLS);
// 启动爬虫
reptile.start();
}
}
分布式布署时,创建配置类时须要通过setDeploymentMode方式指定布署模式为DeploymentModeEnum.Distributed,并且须要通过setScheduler方式设置一个Redis队列调度器,可以使用RedisFIFOQueueScheduler作为实现。 查看全部
Reptile.png
Reptile作为爬虫主体可在主线程运行也可以异步运行,爬虫主要有四个核心组件:
Downloader 执行恳求下载与解析响应ResponseHandler 由使用者提供实现来对响应处理,生成Result结果与新的爬取恳求RequestConsumer 来对处理的结果Result进行消费,例如持久化储存java单机爬虫框架,用户可自定义其具体实现
四个组件之间的关系如构架图所示,它们之间的相互调用产生一个完整的工作流并在Workflow线程中运行,Reptile爬虫会依照配置的线程数目通过线程池创建指定数目的工作流线程并发执行工作流任务。
clone项目并建立发布到本地库房
git clone git@github.com:xiepuhuan/reptile.git
cd reptile
mvn -Dmaven.test.skip=true
在项目中使用Maven引入对应的依赖
<dependency>
<groupId>com.xiepuhuan</groupId>
<artifactId>reptile</artifactId>
<version>0.3</version>
</dependency>
实现ResponseHandler插口,重写isSupport与handle技巧。实现Consumer插口java单机爬虫框架,重写consume方式,执行对数据的消费,可在该方式中对响应处理结果进行持久化等操作,目前提供了ConsoleConsumer,JsonFileConsumer, MongoDBConsumer等实现,默认使用ConsoleConsumer。
public class ZhihuPageHandler implements ResponseHandler {
private static final String[] URLS = new String[] {
"https://www.zhihu.com/api/v4/s ... ot%3B
};
@Override
public List<Request> handle(Response response, Result result) {
Content content = response.getContent();
JSONObject jsonObject = JSON.parseObject(content.getContent(), JSONObject.class);
result.setResults(jsonObject.getInnerMap());
JSONObject paging = jsonObject.getJSONObject("paging");
if (!paging.getBoolean("is_end")) {
List<Request> requests = new ArrayList<>();
requests.add(new Request(paging.getString("next")));
return requests;
}
return null;
}
@Override
public boolean isSupport(Request request, Response response) {
return true;
}
public static void main(String[] args) {
// 构建Reptile爬虫配置类,
ReptileConfig config = ReptileConfig.Builder.cutom()
.setThreadCount(8)
.appendResponseHandlers(new ZhihuPageHandler())
.setDeploymentMode(DeploymentModeEnum.SINGLE)
.setConsumer(new ConsoleConsumer())
.build();
// 根据reptile配置构建Reptile爬虫并添加爬去的URL
Reptile reptile = Reptile.create(config).addUrls(URLS);
// 启动爬虫
reptile.start();
}
}
分布式布署时,创建配置类时须要通过setDeploymentMode方式指定布署模式为DeploymentModeEnum.Distributed,并且须要通过setScheduler方式设置一个Redis队列调度器,可以使用RedisFIFOQueueScheduler作为实现。 查看全部
Reptile是一个具有高拓展性的可支持单机与集群布署Java多线程爬虫框架,该框架可简化爬虫的开发流程。该框架各个组件高内聚松耦合的特点使用户可以对不同组件进行订制来满足不同的需求。

Reptile.png
Reptile作为爬虫主体可在主线程运行也可以异步运行,爬虫主要有四个核心组件:
Downloader 执行恳求下载与解析响应ResponseHandler 由使用者提供实现来对响应处理,生成Result结果与新的爬取恳求RequestConsumer 来对处理的结果Result进行消费,例如持久化储存java单机爬虫框架,用户可自定义其具体实现
四个组件之间的关系如构架图所示,它们之间的相互调用产生一个完整的工作流并在Workflow线程中运行,Reptile爬虫会依照配置的线程数目通过线程池创建指定数目的工作流线程并发执行工作流任务。
clone项目并建立发布到本地库房
git clone git@github.com:xiepuhuan/reptile.git
cd reptile
mvn -Dmaven.test.skip=true
在项目中使用Maven引入对应的依赖
<dependency>
<groupId>com.xiepuhuan</groupId>
<artifactId>reptile</artifactId>
<version>0.3</version>
</dependency>
实现ResponseHandler插口,重写isSupport与handle技巧。实现Consumer插口java单机爬虫框架,重写consume方式,执行对数据的消费,可在该方式中对响应处理结果进行持久化等操作,目前提供了ConsoleConsumer,JsonFileConsumer, MongoDBConsumer等实现,默认使用ConsoleConsumer。
public class ZhihuPageHandler implements ResponseHandler {
private static final String[] URLS = new String[] {
"https://www.zhihu.com/api/v4/s ... ot%3B
};
@Override
public List<Request> handle(Response response, Result result) {
Content content = response.getContent();
JSONObject jsonObject = JSON.parseObject(content.getContent(), JSONObject.class);
result.setResults(jsonObject.getInnerMap());
JSONObject paging = jsonObject.getJSONObject("paging");
if (!paging.getBoolean("is_end")) {
List<Request> requests = new ArrayList<>();
requests.add(new Request(paging.getString("next")));
return requests;
}
return null;
}
@Override
public boolean isSupport(Request request, Response response) {
return true;
}
public static void main(String[] args) {
// 构建Reptile爬虫配置类,
ReptileConfig config = ReptileConfig.Builder.cutom()
.setThreadCount(8)
.appendResponseHandlers(new ZhihuPageHandler())
.setDeploymentMode(DeploymentModeEnum.SINGLE)
.setConsumer(new ConsoleConsumer())
.build();
// 根据reptile配置构建Reptile爬虫并添加爬去的URL
Reptile reptile = Reptile.create(config).addUrls(URLS);
// 启动爬虫
reptile.start();
}
}
分布式布署时,创建配置类时须要通过setDeploymentMode方式指定布署模式为DeploymentModeEnum.Distributed,并且须要通过setScheduler方式设置一个Redis队列调度器,可以使用RedisFIFOQueueScheduler作为实现。
使用Python编撰多线程爬虫抓取百度贴吧邮箱与手机号
采集交流 • 优采云 发表了文章 • 0 个评论 • 454 次浏览 • 2020-07-05 08:00
不知道你们春节都是如何过的,反正栏主是在家睡了三天,醒来的时侯登QQ发觉有人找我要一份帖吧爬虫的源代码,想起之前练手的时侯写过一个抓取百度贴吧发贴记录中的邮箱与手机号的爬虫,于是开源分享给你们学习与参考。
本爬虫主要是对百度贴吧中各类贴子的内容进行抓取,并且剖析贴子内容将其中的手机号和邮箱地址抓取下来。主要流程在代码注释中有详尽解释。
代码在Windows7 64bit,python 2.7 64bit(安装mysqldb扩充)以及centos 6.5,python 2.7(带mysqldb扩充)环境下测试通过
工欲善其事必先利其器,大家可以从截图看出我的环境是Windows 7 + PyCharm。我的Python环境是Python 2.7 64bit。这是比较适宜菜鸟使用的开发环境。然后我再建议你们安装一个easy_install,听名子就晓得这是一个安装器,它是拿来安装一些扩充包的,比如说在python中假如我们要操作mysql数据库的话,python原生是不支持的,我们必须安装mysqldb包来让python可以操作mysql数据库,如果有easy_install的话我们只须要一行命令就可以快速安装号mysqldb扩充包,他如同php中的composer,centos中的yum,Ubuntu中的apt-get一样便捷。
相关工具可在我的github中找到:cw1997/python-tools,其中easy_install的安装只须要在python命令行下运行哪个py脚本之后稍等片刻即可邮箱爬虫软件,他会手动加入Windows的环境变量,在Windows命令行下假如输入easy_install有回显说明安装成功。
至于电脑硬件其实是越快越好,内存至少8G起步,因为爬虫本身须要大量储存和解析中间数据,尤其是多线程爬虫,在遇到抓取带有分页的列表和详情页,并且抓取数据量很大的情况下使用queue队列分配抓取任务会特别占显存。包括有的时候我们抓取的数据是使用json,如果使用mongodb等nosql数据库储存,也会太占显存。
网络联接建议使用有线网,因为市面上一些劣质的无线路由器和普通的民用无线网卡在线程开的比较大的情况下会出现间歇性断网或则数据遗失,掉包等情况,这个我亲有感受。
至于操作系统和python其实肯定是选择64位。如果你使用的是32位的操作系统,那么难以使用大显存。如果你使用的是32位的python,可能在小规模抓取数据的时侯觉得不出有哪些问题,但是当数据量变大的时侯,比如说某个列表,队列,字典上面储存了大量数据,导致python的显存占用超过2g的时侯会报显存溢出错误。原因在我以前segmentfault上提过的问题中依云的回答有解释(java – python只要占用显存达到1.9G以后httplib模块就开始报内存溢出错误 – SegmentFault)
如果你打算使用mysql储存数据,建议使用mysql5.5之后的版本,因为mysql5.5版本支持json数据类型,这样的话可以抛弃mongodb了。(有人说mysql会比mongodb稳定一点,这个我不确定。)
至于现今python都早已出了3.x版本了,为什么我这儿还使用的是python2.7?我个人选择2.7版本的缘由是自己当年很早曾经买的python核心编程这本书是第二版的,仍然以2.7为示例版本。并且目前网上一直有大量的教程资料是以2.7为版本讲解,2.7在个别方面与3.x还是有很大差异,如果我们没有学过2.7,可能对于一些细微的句型差异不是太懂会导致我们理解上出现误差,或者看不懂demo代码。而且如今还是有部份依赖包只兼容2.7版本。我的建议是假如你是打算急着学python之后去公司工作,并且公司没有老代码须要维护,那么可以考虑直接上手3.x,如果你有比较充沛的时间,并且没有太系统的大牛带,只能借助网上零零散散的博客文章来学习,那么还是先学2.7在学3.x,毕竟学会了2.7以后3.x上手也很快。
其实对于任何软件项目而言,我们但凡想知道编撰这个项目须要哪些知识点,我们都可以观察一下这个项目的主要入口文件都导出了什么包。
现在来看一下我们这个项目,作为一个刚接触python的人,可能有一些包几乎都没有用过,那么我们在本小节就来简单的谈谈这种包起哪些作用,要把握她们分别会涉及到哪些知识点,这些知识点的关键词是哪些。这篇文章并不会耗费长篇大论来从基础讲起,因此我们要学会善用百度,搜索那些知识点的关键词来自学。下面就来一一剖析一下这种知识点。
我们的爬虫抓取数据本质上就是不停的发起http请求,获取http响应,将其存入我们的笔记本中。了解http协议有助于我们在抓取数据的时侯对一些才能加速抓取速率的参数才能精准的控制,比如说keep-alive等。
我们平常编撰的程序都是单线程程序,我们写的代码都在主线程上面运行,这个主线程又运行在python进程中。关于线程和进程的解释可以参考阮一峰的博客:进程与线程的一个简单解释 – 阮一峰的网路日志
在python中实现多线程是通过一个名子称作threading的模块来实现。之前还有thread模块,但是threading对于线程的控制更强,因此我们后来都改用threading来实现多线程编程了。
关于threading多线程的一些用法,我认为这篇文章不错:[python] 专题八.多线程编程之thread和threading 大家可以参考参考。
简单来说,使用threading模块编撰多线程程序,就是先自己定义一个类,然后这个类要承继threading.Thread,并且把每位线程要做的工作代码讲到一个类的run方式中,当然若果线程本身在创建的时侯假如要做一些初始化工作,那么就要在他的__init__方法中编撰好初始化工作所要执行的代码,这个方式如同php,java中的构造方式一样。
这里还要额外讲的一点就是线程安全这个概念。通常情况下我们单线程情况下每位时刻只有一个线程在对资源(文件,变量)操作,所以不可能会出现冲突。但是当多线程的情况下,可能会出现同一个时刻两个线程在操作同一个资源,导致资源受损,所以我们须要一种机制来解决这些冲突带来的破坏,通常有加锁等操作,比如说mysql数据库的innodb表引擎有行级锁等,文件操作有读取锁等等,这些都是她们的程序底层帮我们完成了。所以我们一般只要晓得这些操作,或者这些程序对于线程安全问题做了处理,然后就可以在多线程编程中去使用它们了。而这些考虑到线程安全问题的程序通常就叫做“线程安全版本”,比如说php就有TS版本,这个TS就是Thread Safety线程安全的意思。下面我们要提到的Queue模块就是一种线程安全的队列数据结构,所以我们可以放心的在多线程编程中使用它。
***我们就要来讲讲至关重要的线程阻塞这个概念了。当我们详尽学习完threading模块以后,大概就晓得怎样创建和启动线程了。但是假如我们把线程创建好了,然后调用了start方式,那么我们会发觉似乎整个程序立刻就结束了,这是如何回事呢?其实这是因为我们在主线程中只有负责启动子线程的代码,也就意味着主线程只有启动子线程的功能,至于子线程执行的这些代码,他们本质上只是写在类上面的一个方式,并没在主线程上面真正去执行他,所以主线程启动完子线程以后他的本职工作就早已全部完成了,已经光荣离场了。既然主线程都离场了,那么python进程就跟随结束了,那么其他线程也就没有显存空间继续执行了。所以我们应当是要使主线程大婶等到所有的子线程鄙人全部执行完毕再光荣离场,那么在线程对象中有哪些方式才能把主线程卡住呢?thread.sleep嘛?这确实是个办法,但是到底应当使主线程sleep多久呢?我们并不能确切晓得执行完一个任务要多久时间,肯定不能用这个办法。所以我们这个时侯应当上网查询一下有哪些办法才能使子线程“卡住”主线程呢?“卡住”这个词似乎很粗俗了,其实说专业一点,应该称作“阻塞”,所以我们可以查询“python 子线程阻塞主线程”,如果我们会正确使用搜索引擎的话,应该会查到一个方式称作join(),没错,这个join()方法就是子线程用于阻塞主线程的方式,当子线程还未执行完毕的时侯,主线程运行到富含join()方法的这一行都会卡在那里,直到所有线程都执行完毕才能执行join()方法前面的代码。
假设有一个这样的场景,我们须要抓取一个人的博客,我们晓得这个人的博客有两个页面,一个list.php页面显示的是此博客的所有文章链接,还有一个view.php页面显示的是一篇文章的具体内容。
如果我们要把这个人的博客上面所有文章内容抓取出来,编写单线程爬虫的思路是:先用正则表达式把这个list.php页面的所有链接a标签的href属性抓取出来,存入一个名子称作article_list的链表(在python中不叫链表,叫做list,中文名列表),然后再用一个for循环遍历这个article_list链表,用各类抓取网页内容的函数把内容抓取出来之后存入数据库。
如果我们要编撰一个多线程爬虫来完成这个任务的话,就假定我们的程序用10个线程把,那么我们就要想办法把之前抓取的article_list平均分成10份,分别把每一份分配给其中一个子线程。
但是问题来了,如果我们的article_list链表宽度不是10的倍数,也就是文章数量并不是10的整数倍,那么***一个线程都会比别的线程少分配到一些任务,那么它将会更快的结束。
如果仅仅是抓取这些只有几千字的博客文章这看似没哪些问题,但是假如我们一个任务(不一定是抓取网页的任务,有可能是物理估算,或者图形渲染等等历时任务)的运行时间太长,那么这将导致极大地资源和时间浪费。我们多线程的目的就是尽可能的借助一切估算资源而且估算时间,所以我们要想办法使任务才能愈发科学合理的分配。
并且我还要考虑一种情况,就是文章数量很大的情况下,我们要既能快速抓取到文章内容,又能尽早的看见我们早已抓取到的内容,这种需求在好多CMS采集站上常常会彰显下来。
比如说我们如今要抓取的目标博客,有几千万篇文章,通常这些情况下博客还会做分页处理,那么我们若果根据前面的传统思路先抓取完list.php的所有页面至少就要几个小时甚至几天,老板假如希望你还能早日显示出抓取内容,并且尽早将早已抓取到的内容诠释到我们的CMS采集站上,那么我们就要实现一边抓取list.php而且把早已抓取到的数据丢入一个article_list链表,一边用另一个线程从article_list链表中提取早已抓取到的文章URL地址,然后这个线程再去对应的URL地址中用正则表达式取到博客文章内容。如何实现这个功能呢?
我们就须要同时开启两类线程,一类线程专门负责抓取list.php中的url之后丢入article_list链表,另外一类线程专门负责从article_list中提取出url之后从对应的view.php页面中抓取出对应的博客内容。
但是我们是否还记得上面提及过线程安全这个概念?前一类线程一边往article_list字段中写入数据,另外那一类的线程从article_list中读取数据但是删掉早已读取完毕的数据。但是python中list并不是线程安全版本的数据结构,因此这样操作会导致不可预想的错误。所以我们可以尝试使用一个愈发便捷且线程安全的数据结构,这就是我们的子标题中所提及的Queue队列数据结构。
同样Queue也有一个join()方法,这个join()方法虽然和上一个小节所提到的threading中join()方法差不多,只不过在Queue中,join()的阻塞条件是当队列不为空空的时侯才阻塞,否则继续执行join()后面的代码。在这个爬虫中我便使用了这些技巧来阻塞主线程而不是直接通过线程的join方法来阻塞主线程,这样的用处是可以不用写一个死循环来判定当前任务队列中是否还有未执行完的任务,让程序运行愈发高效,也使代码愈发柔美。
还有一个细节就是在python2.7中队列模块的名子是Queue,而在python3.x中早已更名为queue,就是首字母大小写的区别,大家假如是复制网上的代码,要记得这个小区别。
如果你们学过c语言的话,对这个模块应当会太熟悉,他就是一个负责从命令行中的命令上面提取出附送参数的模块。比如说我们一般在命令行中操作mysql数据库,就是输入mysql -h127.0.0.1 -uroot -p,其中mysql前面的“-h127.0.0.1 -uroot -p”就是可以获取的参数部份。
我们平常在编撰爬虫的时侯,有一些参数是须要用户自己自动输入的,比如说mysql的主机IP,用户名密码等等。为了使我们的程序愈加友好通用,有一些配置项是不需要硬编码在代码上面,而是在执行他的时侯我们动态传入,结合getopt模块我们就可以实现这个功能。
哈希本质上就是一类物理算法的集合,这种物理算法有个特点就是你给定一个参数,他就能输出另外一个结果,虽然这个结果太短,但是他可以近似觉得是***的。比如说我们平常听过的md5,sha-1等等,他们都属于哈希算法。他们可以把一些文件,文字经过一系列的物理运算然后弄成短短不到一百位的一段数字中文混和的字符串。
python中的hashlib模块就为我们封装好了这种物理运算函数,我们只须要简单的调用它就可以完成哈希运算。
为什么在我这个爬虫中用到了这个包呢?因为在一些插口恳求中,服务器须要带上一些校验码,保证插口恳求的数据没有被篡改或则遗失,这些校验码通常都是hash算法,所以我们须要用到这个模块来完成这些运算。
很多时侯我们抓取到的数据不是html,而是一些json数据,json本质上只是一段富含通配符对的字符串,如果我们须要提取出其中特定的字符串,那么我们须要json这个模块来将这个json字符串转换为dict类型便捷我们操作。
有的时侯我们抓取到了一些网页内容,但是我们须要将网页中的一些特定格式的内容提取下来,比如说电子邮箱的格式通常都是上面几位英语数字字母加一个@符号加的域名,而要象计算机语言描述这些格式,我们可以使用一种称作正则表达式的表达式来抒发出这些格式,并且使计算机手动从一大段字符串上将符合这些特定格式的文字匹配下来。
这个模块主要用于处理一些系统方面的事情,在这个爬虫中我用他来解决输出编码问题。
稍微学过一点法语的人都还能猜下来这个模块用于处理时间,在这个爬虫中我用它来获取当前时间戳,然后通过在主线程末尾用当前时间戳除以程序开始运行时的时间戳,得到程序的运行时间。
如图所示,开50个线程抓取100页(每页30个贴子,相当于抓取了3000个贴子)贴吧贴子内容而且从中提取出手机邮箱这个步骤共历时330秒。
这两个模块都是用于处理一些http请求,以及url低格方面的事情。我的爬虫http请求部份的核心代码就是使用这个模块完成的。
这是一个第三方模块,用于在python中操作mysql数据库。
这里我们要注意一个细节问题:mysqldb模块并不是线程安全版本,意味着我们不能在多线程中共享同一个mysql联接句柄。所以你们可以在我的代码中听到,我在每位线程的构造函数中都传入了一个新的mysql联接句柄。因此每位子线程只会用自己独立的mysql联接句柄。
这也是一个第三方模块,网上还能找到相关代码,这个模块主要用于向命令行中输出彩色字符串。比如说我们一般爬虫出现错误,要输出黄色的字体会比较醒目,就要使用到这个模块。
如果你们在网路质量不是挺好的环境下使用该爬虫,会发觉有的时侯会报如图所示的异常,这是我为了偷懒并没有写各类异常处理的逻辑。
通常情况下我们假如要编撰高度自动化的爬虫,那么就须要意料到我们的爬虫可能会遇见的所有异常情况邮箱爬虫软件,针对这种异常情况做处理。
比如说如图所示的错误,我们就应当把当时正在处理的任务重新伸入任务队列,否则我们还会出现遗漏信息的情况。这也是爬虫编撰的一个复杂点。
其实多线程爬虫的编撰也不复杂,多看示例代码,多自己动手尝试,多去社区,论坛交流,很多精典的书上对多线程编程也有特别详尽的解释。这篇文章本质上主要还是一篇科普文章,内容讲解的都不是太深入,大家还须要课外自己多结合网上各类资料自己学习。
【编辑推荐】
Python程序员都该用的一个库Python正则表达式re模块简明笔记这种方式推动Python开发者提升效率Python并发编程之线程池/进程池Python黑魔法之描述符 查看全部
本爬虫主要是对百度贴吧中各类贴子的内容进行抓取,并且剖析贴子内容将其中的手机号和邮箱地址抓取下来。主要流程在代码注释中有详尽解释。
代码在Windows7 64bit,python 2.7 64bit(安装mysqldb扩充)以及centos 6.5,python 2.7(带mysqldb扩充)环境下测试通过
工欲善其事必先利其器,大家可以从截图看出我的环境是Windows 7 + PyCharm。我的Python环境是Python 2.7 64bit。这是比较适宜菜鸟使用的开发环境。然后我再建议你们安装一个easy_install,听名子就晓得这是一个安装器,它是拿来安装一些扩充包的,比如说在python中假如我们要操作mysql数据库的话,python原生是不支持的,我们必须安装mysqldb包来让python可以操作mysql数据库,如果有easy_install的话我们只须要一行命令就可以快速安装号mysqldb扩充包,他如同php中的composer,centos中的yum,Ubuntu中的apt-get一样便捷。
相关工具可在我的github中找到:cw1997/python-tools,其中easy_install的安装只须要在python命令行下运行哪个py脚本之后稍等片刻即可邮箱爬虫软件,他会手动加入Windows的环境变量,在Windows命令行下假如输入easy_install有回显说明安装成功。
至于电脑硬件其实是越快越好,内存至少8G起步,因为爬虫本身须要大量储存和解析中间数据,尤其是多线程爬虫,在遇到抓取带有分页的列表和详情页,并且抓取数据量很大的情况下使用queue队列分配抓取任务会特别占显存。包括有的时候我们抓取的数据是使用json,如果使用mongodb等nosql数据库储存,也会太占显存。
网络联接建议使用有线网,因为市面上一些劣质的无线路由器和普通的民用无线网卡在线程开的比较大的情况下会出现间歇性断网或则数据遗失,掉包等情况,这个我亲有感受。
至于操作系统和python其实肯定是选择64位。如果你使用的是32位的操作系统,那么难以使用大显存。如果你使用的是32位的python,可能在小规模抓取数据的时侯觉得不出有哪些问题,但是当数据量变大的时侯,比如说某个列表,队列,字典上面储存了大量数据,导致python的显存占用超过2g的时侯会报显存溢出错误。原因在我以前segmentfault上提过的问题中依云的回答有解释(java – python只要占用显存达到1.9G以后httplib模块就开始报内存溢出错误 – SegmentFault)
如果你打算使用mysql储存数据,建议使用mysql5.5之后的版本,因为mysql5.5版本支持json数据类型,这样的话可以抛弃mongodb了。(有人说mysql会比mongodb稳定一点,这个我不确定。)
至于现今python都早已出了3.x版本了,为什么我这儿还使用的是python2.7?我个人选择2.7版本的缘由是自己当年很早曾经买的python核心编程这本书是第二版的,仍然以2.7为示例版本。并且目前网上一直有大量的教程资料是以2.7为版本讲解,2.7在个别方面与3.x还是有很大差异,如果我们没有学过2.7,可能对于一些细微的句型差异不是太懂会导致我们理解上出现误差,或者看不懂demo代码。而且如今还是有部份依赖包只兼容2.7版本。我的建议是假如你是打算急着学python之后去公司工作,并且公司没有老代码须要维护,那么可以考虑直接上手3.x,如果你有比较充沛的时间,并且没有太系统的大牛带,只能借助网上零零散散的博客文章来学习,那么还是先学2.7在学3.x,毕竟学会了2.7以后3.x上手也很快。
其实对于任何软件项目而言,我们但凡想知道编撰这个项目须要哪些知识点,我们都可以观察一下这个项目的主要入口文件都导出了什么包。
现在来看一下我们这个项目,作为一个刚接触python的人,可能有一些包几乎都没有用过,那么我们在本小节就来简单的谈谈这种包起哪些作用,要把握她们分别会涉及到哪些知识点,这些知识点的关键词是哪些。这篇文章并不会耗费长篇大论来从基础讲起,因此我们要学会善用百度,搜索那些知识点的关键词来自学。下面就来一一剖析一下这种知识点。
我们的爬虫抓取数据本质上就是不停的发起http请求,获取http响应,将其存入我们的笔记本中。了解http协议有助于我们在抓取数据的时侯对一些才能加速抓取速率的参数才能精准的控制,比如说keep-alive等。
我们平常编撰的程序都是单线程程序,我们写的代码都在主线程上面运行,这个主线程又运行在python进程中。关于线程和进程的解释可以参考阮一峰的博客:进程与线程的一个简单解释 – 阮一峰的网路日志
在python中实现多线程是通过一个名子称作threading的模块来实现。之前还有thread模块,但是threading对于线程的控制更强,因此我们后来都改用threading来实现多线程编程了。
关于threading多线程的一些用法,我认为这篇文章不错:[python] 专题八.多线程编程之thread和threading 大家可以参考参考。
简单来说,使用threading模块编撰多线程程序,就是先自己定义一个类,然后这个类要承继threading.Thread,并且把每位线程要做的工作代码讲到一个类的run方式中,当然若果线程本身在创建的时侯假如要做一些初始化工作,那么就要在他的__init__方法中编撰好初始化工作所要执行的代码,这个方式如同php,java中的构造方式一样。
这里还要额外讲的一点就是线程安全这个概念。通常情况下我们单线程情况下每位时刻只有一个线程在对资源(文件,变量)操作,所以不可能会出现冲突。但是当多线程的情况下,可能会出现同一个时刻两个线程在操作同一个资源,导致资源受损,所以我们须要一种机制来解决这些冲突带来的破坏,通常有加锁等操作,比如说mysql数据库的innodb表引擎有行级锁等,文件操作有读取锁等等,这些都是她们的程序底层帮我们完成了。所以我们一般只要晓得这些操作,或者这些程序对于线程安全问题做了处理,然后就可以在多线程编程中去使用它们了。而这些考虑到线程安全问题的程序通常就叫做“线程安全版本”,比如说php就有TS版本,这个TS就是Thread Safety线程安全的意思。下面我们要提到的Queue模块就是一种线程安全的队列数据结构,所以我们可以放心的在多线程编程中使用它。
***我们就要来讲讲至关重要的线程阻塞这个概念了。当我们详尽学习完threading模块以后,大概就晓得怎样创建和启动线程了。但是假如我们把线程创建好了,然后调用了start方式,那么我们会发觉似乎整个程序立刻就结束了,这是如何回事呢?其实这是因为我们在主线程中只有负责启动子线程的代码,也就意味着主线程只有启动子线程的功能,至于子线程执行的这些代码,他们本质上只是写在类上面的一个方式,并没在主线程上面真正去执行他,所以主线程启动完子线程以后他的本职工作就早已全部完成了,已经光荣离场了。既然主线程都离场了,那么python进程就跟随结束了,那么其他线程也就没有显存空间继续执行了。所以我们应当是要使主线程大婶等到所有的子线程鄙人全部执行完毕再光荣离场,那么在线程对象中有哪些方式才能把主线程卡住呢?thread.sleep嘛?这确实是个办法,但是到底应当使主线程sleep多久呢?我们并不能确切晓得执行完一个任务要多久时间,肯定不能用这个办法。所以我们这个时侯应当上网查询一下有哪些办法才能使子线程“卡住”主线程呢?“卡住”这个词似乎很粗俗了,其实说专业一点,应该称作“阻塞”,所以我们可以查询“python 子线程阻塞主线程”,如果我们会正确使用搜索引擎的话,应该会查到一个方式称作join(),没错,这个join()方法就是子线程用于阻塞主线程的方式,当子线程还未执行完毕的时侯,主线程运行到富含join()方法的这一行都会卡在那里,直到所有线程都执行完毕才能执行join()方法前面的代码。
假设有一个这样的场景,我们须要抓取一个人的博客,我们晓得这个人的博客有两个页面,一个list.php页面显示的是此博客的所有文章链接,还有一个view.php页面显示的是一篇文章的具体内容。
如果我们要把这个人的博客上面所有文章内容抓取出来,编写单线程爬虫的思路是:先用正则表达式把这个list.php页面的所有链接a标签的href属性抓取出来,存入一个名子称作article_list的链表(在python中不叫链表,叫做list,中文名列表),然后再用一个for循环遍历这个article_list链表,用各类抓取网页内容的函数把内容抓取出来之后存入数据库。
如果我们要编撰一个多线程爬虫来完成这个任务的话,就假定我们的程序用10个线程把,那么我们就要想办法把之前抓取的article_list平均分成10份,分别把每一份分配给其中一个子线程。
但是问题来了,如果我们的article_list链表宽度不是10的倍数,也就是文章数量并不是10的整数倍,那么***一个线程都会比别的线程少分配到一些任务,那么它将会更快的结束。
如果仅仅是抓取这些只有几千字的博客文章这看似没哪些问题,但是假如我们一个任务(不一定是抓取网页的任务,有可能是物理估算,或者图形渲染等等历时任务)的运行时间太长,那么这将导致极大地资源和时间浪费。我们多线程的目的就是尽可能的借助一切估算资源而且估算时间,所以我们要想办法使任务才能愈发科学合理的分配。
并且我还要考虑一种情况,就是文章数量很大的情况下,我们要既能快速抓取到文章内容,又能尽早的看见我们早已抓取到的内容,这种需求在好多CMS采集站上常常会彰显下来。
比如说我们如今要抓取的目标博客,有几千万篇文章,通常这些情况下博客还会做分页处理,那么我们若果根据前面的传统思路先抓取完list.php的所有页面至少就要几个小时甚至几天,老板假如希望你还能早日显示出抓取内容,并且尽早将早已抓取到的内容诠释到我们的CMS采集站上,那么我们就要实现一边抓取list.php而且把早已抓取到的数据丢入一个article_list链表,一边用另一个线程从article_list链表中提取早已抓取到的文章URL地址,然后这个线程再去对应的URL地址中用正则表达式取到博客文章内容。如何实现这个功能呢?
我们就须要同时开启两类线程,一类线程专门负责抓取list.php中的url之后丢入article_list链表,另外一类线程专门负责从article_list中提取出url之后从对应的view.php页面中抓取出对应的博客内容。
但是我们是否还记得上面提及过线程安全这个概念?前一类线程一边往article_list字段中写入数据,另外那一类的线程从article_list中读取数据但是删掉早已读取完毕的数据。但是python中list并不是线程安全版本的数据结构,因此这样操作会导致不可预想的错误。所以我们可以尝试使用一个愈发便捷且线程安全的数据结构,这就是我们的子标题中所提及的Queue队列数据结构。
同样Queue也有一个join()方法,这个join()方法虽然和上一个小节所提到的threading中join()方法差不多,只不过在Queue中,join()的阻塞条件是当队列不为空空的时侯才阻塞,否则继续执行join()后面的代码。在这个爬虫中我便使用了这些技巧来阻塞主线程而不是直接通过线程的join方法来阻塞主线程,这样的用处是可以不用写一个死循环来判定当前任务队列中是否还有未执行完的任务,让程序运行愈发高效,也使代码愈发柔美。
还有一个细节就是在python2.7中队列模块的名子是Queue,而在python3.x中早已更名为queue,就是首字母大小写的区别,大家假如是复制网上的代码,要记得这个小区别。
如果你们学过c语言的话,对这个模块应当会太熟悉,他就是一个负责从命令行中的命令上面提取出附送参数的模块。比如说我们一般在命令行中操作mysql数据库,就是输入mysql -h127.0.0.1 -uroot -p,其中mysql前面的“-h127.0.0.1 -uroot -p”就是可以获取的参数部份。
我们平常在编撰爬虫的时侯,有一些参数是须要用户自己自动输入的,比如说mysql的主机IP,用户名密码等等。为了使我们的程序愈加友好通用,有一些配置项是不需要硬编码在代码上面,而是在执行他的时侯我们动态传入,结合getopt模块我们就可以实现这个功能。
哈希本质上就是一类物理算法的集合,这种物理算法有个特点就是你给定一个参数,他就能输出另外一个结果,虽然这个结果太短,但是他可以近似觉得是***的。比如说我们平常听过的md5,sha-1等等,他们都属于哈希算法。他们可以把一些文件,文字经过一系列的物理运算然后弄成短短不到一百位的一段数字中文混和的字符串。
python中的hashlib模块就为我们封装好了这种物理运算函数,我们只须要简单的调用它就可以完成哈希运算。
为什么在我这个爬虫中用到了这个包呢?因为在一些插口恳求中,服务器须要带上一些校验码,保证插口恳求的数据没有被篡改或则遗失,这些校验码通常都是hash算法,所以我们须要用到这个模块来完成这些运算。
很多时侯我们抓取到的数据不是html,而是一些json数据,json本质上只是一段富含通配符对的字符串,如果我们须要提取出其中特定的字符串,那么我们须要json这个模块来将这个json字符串转换为dict类型便捷我们操作。
有的时侯我们抓取到了一些网页内容,但是我们须要将网页中的一些特定格式的内容提取下来,比如说电子邮箱的格式通常都是上面几位英语数字字母加一个@符号加的域名,而要象计算机语言描述这些格式,我们可以使用一种称作正则表达式的表达式来抒发出这些格式,并且使计算机手动从一大段字符串上将符合这些特定格式的文字匹配下来。
这个模块主要用于处理一些系统方面的事情,在这个爬虫中我用他来解决输出编码问题。
稍微学过一点法语的人都还能猜下来这个模块用于处理时间,在这个爬虫中我用它来获取当前时间戳,然后通过在主线程末尾用当前时间戳除以程序开始运行时的时间戳,得到程序的运行时间。
如图所示,开50个线程抓取100页(每页30个贴子,相当于抓取了3000个贴子)贴吧贴子内容而且从中提取出手机邮箱这个步骤共历时330秒。
这两个模块都是用于处理一些http请求,以及url低格方面的事情。我的爬虫http请求部份的核心代码就是使用这个模块完成的。
这是一个第三方模块,用于在python中操作mysql数据库。
这里我们要注意一个细节问题:mysqldb模块并不是线程安全版本,意味着我们不能在多线程中共享同一个mysql联接句柄。所以你们可以在我的代码中听到,我在每位线程的构造函数中都传入了一个新的mysql联接句柄。因此每位子线程只会用自己独立的mysql联接句柄。
这也是一个第三方模块,网上还能找到相关代码,这个模块主要用于向命令行中输出彩色字符串。比如说我们一般爬虫出现错误,要输出黄色的字体会比较醒目,就要使用到这个模块。
如果你们在网路质量不是挺好的环境下使用该爬虫,会发觉有的时侯会报如图所示的异常,这是我为了偷懒并没有写各类异常处理的逻辑。
通常情况下我们假如要编撰高度自动化的爬虫,那么就须要意料到我们的爬虫可能会遇见的所有异常情况邮箱爬虫软件,针对这种异常情况做处理。
比如说如图所示的错误,我们就应当把当时正在处理的任务重新伸入任务队列,否则我们还会出现遗漏信息的情况。这也是爬虫编撰的一个复杂点。
其实多线程爬虫的编撰也不复杂,多看示例代码,多自己动手尝试,多去社区,论坛交流,很多精典的书上对多线程编程也有特别详尽的解释。这篇文章本质上主要还是一篇科普文章,内容讲解的都不是太深入,大家还须要课外自己多结合网上各类资料自己学习。
【编辑推荐】
Python程序员都该用的一个库Python正则表达式re模块简明笔记这种方式推动Python开发者提升效率Python并发编程之线程池/进程池Python黑魔法之描述符 查看全部
不知道你们春节都是如何过的,反正栏主是在家睡了三天,醒来的时侯登QQ发觉有人找我要一份帖吧爬虫的源代码,想起之前练手的时侯写过一个抓取百度贴吧发贴记录中的邮箱与手机号的爬虫,于是开源分享给你们学习与参考。
本爬虫主要是对百度贴吧中各类贴子的内容进行抓取,并且剖析贴子内容将其中的手机号和邮箱地址抓取下来。主要流程在代码注释中有详尽解释。
代码在Windows7 64bit,python 2.7 64bit(安装mysqldb扩充)以及centos 6.5,python 2.7(带mysqldb扩充)环境下测试通过


工欲善其事必先利其器,大家可以从截图看出我的环境是Windows 7 + PyCharm。我的Python环境是Python 2.7 64bit。这是比较适宜菜鸟使用的开发环境。然后我再建议你们安装一个easy_install,听名子就晓得这是一个安装器,它是拿来安装一些扩充包的,比如说在python中假如我们要操作mysql数据库的话,python原生是不支持的,我们必须安装mysqldb包来让python可以操作mysql数据库,如果有easy_install的话我们只须要一行命令就可以快速安装号mysqldb扩充包,他如同php中的composer,centos中的yum,Ubuntu中的apt-get一样便捷。
相关工具可在我的github中找到:cw1997/python-tools,其中easy_install的安装只须要在python命令行下运行哪个py脚本之后稍等片刻即可邮箱爬虫软件,他会手动加入Windows的环境变量,在Windows命令行下假如输入easy_install有回显说明安装成功。
至于电脑硬件其实是越快越好,内存至少8G起步,因为爬虫本身须要大量储存和解析中间数据,尤其是多线程爬虫,在遇到抓取带有分页的列表和详情页,并且抓取数据量很大的情况下使用queue队列分配抓取任务会特别占显存。包括有的时候我们抓取的数据是使用json,如果使用mongodb等nosql数据库储存,也会太占显存。
网络联接建议使用有线网,因为市面上一些劣质的无线路由器和普通的民用无线网卡在线程开的比较大的情况下会出现间歇性断网或则数据遗失,掉包等情况,这个我亲有感受。
至于操作系统和python其实肯定是选择64位。如果你使用的是32位的操作系统,那么难以使用大显存。如果你使用的是32位的python,可能在小规模抓取数据的时侯觉得不出有哪些问题,但是当数据量变大的时侯,比如说某个列表,队列,字典上面储存了大量数据,导致python的显存占用超过2g的时侯会报显存溢出错误。原因在我以前segmentfault上提过的问题中依云的回答有解释(java – python只要占用显存达到1.9G以后httplib模块就开始报内存溢出错误 – SegmentFault)
如果你打算使用mysql储存数据,建议使用mysql5.5之后的版本,因为mysql5.5版本支持json数据类型,这样的话可以抛弃mongodb了。(有人说mysql会比mongodb稳定一点,这个我不确定。)
至于现今python都早已出了3.x版本了,为什么我这儿还使用的是python2.7?我个人选择2.7版本的缘由是自己当年很早曾经买的python核心编程这本书是第二版的,仍然以2.7为示例版本。并且目前网上一直有大量的教程资料是以2.7为版本讲解,2.7在个别方面与3.x还是有很大差异,如果我们没有学过2.7,可能对于一些细微的句型差异不是太懂会导致我们理解上出现误差,或者看不懂demo代码。而且如今还是有部份依赖包只兼容2.7版本。我的建议是假如你是打算急着学python之后去公司工作,并且公司没有老代码须要维护,那么可以考虑直接上手3.x,如果你有比较充沛的时间,并且没有太系统的大牛带,只能借助网上零零散散的博客文章来学习,那么还是先学2.7在学3.x,毕竟学会了2.7以后3.x上手也很快。
其实对于任何软件项目而言,我们但凡想知道编撰这个项目须要哪些知识点,我们都可以观察一下这个项目的主要入口文件都导出了什么包。

现在来看一下我们这个项目,作为一个刚接触python的人,可能有一些包几乎都没有用过,那么我们在本小节就来简单的谈谈这种包起哪些作用,要把握她们分别会涉及到哪些知识点,这些知识点的关键词是哪些。这篇文章并不会耗费长篇大论来从基础讲起,因此我们要学会善用百度,搜索那些知识点的关键词来自学。下面就来一一剖析一下这种知识点。
我们的爬虫抓取数据本质上就是不停的发起http请求,获取http响应,将其存入我们的笔记本中。了解http协议有助于我们在抓取数据的时侯对一些才能加速抓取速率的参数才能精准的控制,比如说keep-alive等。
我们平常编撰的程序都是单线程程序,我们写的代码都在主线程上面运行,这个主线程又运行在python进程中。关于线程和进程的解释可以参考阮一峰的博客:进程与线程的一个简单解释 – 阮一峰的网路日志
在python中实现多线程是通过一个名子称作threading的模块来实现。之前还有thread模块,但是threading对于线程的控制更强,因此我们后来都改用threading来实现多线程编程了。
关于threading多线程的一些用法,我认为这篇文章不错:[python] 专题八.多线程编程之thread和threading 大家可以参考参考。
简单来说,使用threading模块编撰多线程程序,就是先自己定义一个类,然后这个类要承继threading.Thread,并且把每位线程要做的工作代码讲到一个类的run方式中,当然若果线程本身在创建的时侯假如要做一些初始化工作,那么就要在他的__init__方法中编撰好初始化工作所要执行的代码,这个方式如同php,java中的构造方式一样。
这里还要额外讲的一点就是线程安全这个概念。通常情况下我们单线程情况下每位时刻只有一个线程在对资源(文件,变量)操作,所以不可能会出现冲突。但是当多线程的情况下,可能会出现同一个时刻两个线程在操作同一个资源,导致资源受损,所以我们须要一种机制来解决这些冲突带来的破坏,通常有加锁等操作,比如说mysql数据库的innodb表引擎有行级锁等,文件操作有读取锁等等,这些都是她们的程序底层帮我们完成了。所以我们一般只要晓得这些操作,或者这些程序对于线程安全问题做了处理,然后就可以在多线程编程中去使用它们了。而这些考虑到线程安全问题的程序通常就叫做“线程安全版本”,比如说php就有TS版本,这个TS就是Thread Safety线程安全的意思。下面我们要提到的Queue模块就是一种线程安全的队列数据结构,所以我们可以放心的在多线程编程中使用它。
***我们就要来讲讲至关重要的线程阻塞这个概念了。当我们详尽学习完threading模块以后,大概就晓得怎样创建和启动线程了。但是假如我们把线程创建好了,然后调用了start方式,那么我们会发觉似乎整个程序立刻就结束了,这是如何回事呢?其实这是因为我们在主线程中只有负责启动子线程的代码,也就意味着主线程只有启动子线程的功能,至于子线程执行的这些代码,他们本质上只是写在类上面的一个方式,并没在主线程上面真正去执行他,所以主线程启动完子线程以后他的本职工作就早已全部完成了,已经光荣离场了。既然主线程都离场了,那么python进程就跟随结束了,那么其他线程也就没有显存空间继续执行了。所以我们应当是要使主线程大婶等到所有的子线程鄙人全部执行完毕再光荣离场,那么在线程对象中有哪些方式才能把主线程卡住呢?thread.sleep嘛?这确实是个办法,但是到底应当使主线程sleep多久呢?我们并不能确切晓得执行完一个任务要多久时间,肯定不能用这个办法。所以我们这个时侯应当上网查询一下有哪些办法才能使子线程“卡住”主线程呢?“卡住”这个词似乎很粗俗了,其实说专业一点,应该称作“阻塞”,所以我们可以查询“python 子线程阻塞主线程”,如果我们会正确使用搜索引擎的话,应该会查到一个方式称作join(),没错,这个join()方法就是子线程用于阻塞主线程的方式,当子线程还未执行完毕的时侯,主线程运行到富含join()方法的这一行都会卡在那里,直到所有线程都执行完毕才能执行join()方法前面的代码。
假设有一个这样的场景,我们须要抓取一个人的博客,我们晓得这个人的博客有两个页面,一个list.php页面显示的是此博客的所有文章链接,还有一个view.php页面显示的是一篇文章的具体内容。
如果我们要把这个人的博客上面所有文章内容抓取出来,编写单线程爬虫的思路是:先用正则表达式把这个list.php页面的所有链接a标签的href属性抓取出来,存入一个名子称作article_list的链表(在python中不叫链表,叫做list,中文名列表),然后再用一个for循环遍历这个article_list链表,用各类抓取网页内容的函数把内容抓取出来之后存入数据库。
如果我们要编撰一个多线程爬虫来完成这个任务的话,就假定我们的程序用10个线程把,那么我们就要想办法把之前抓取的article_list平均分成10份,分别把每一份分配给其中一个子线程。
但是问题来了,如果我们的article_list链表宽度不是10的倍数,也就是文章数量并不是10的整数倍,那么***一个线程都会比别的线程少分配到一些任务,那么它将会更快的结束。
如果仅仅是抓取这些只有几千字的博客文章这看似没哪些问题,但是假如我们一个任务(不一定是抓取网页的任务,有可能是物理估算,或者图形渲染等等历时任务)的运行时间太长,那么这将导致极大地资源和时间浪费。我们多线程的目的就是尽可能的借助一切估算资源而且估算时间,所以我们要想办法使任务才能愈发科学合理的分配。
并且我还要考虑一种情况,就是文章数量很大的情况下,我们要既能快速抓取到文章内容,又能尽早的看见我们早已抓取到的内容,这种需求在好多CMS采集站上常常会彰显下来。
比如说我们如今要抓取的目标博客,有几千万篇文章,通常这些情况下博客还会做分页处理,那么我们若果根据前面的传统思路先抓取完list.php的所有页面至少就要几个小时甚至几天,老板假如希望你还能早日显示出抓取内容,并且尽早将早已抓取到的内容诠释到我们的CMS采集站上,那么我们就要实现一边抓取list.php而且把早已抓取到的数据丢入一个article_list链表,一边用另一个线程从article_list链表中提取早已抓取到的文章URL地址,然后这个线程再去对应的URL地址中用正则表达式取到博客文章内容。如何实现这个功能呢?
我们就须要同时开启两类线程,一类线程专门负责抓取list.php中的url之后丢入article_list链表,另外一类线程专门负责从article_list中提取出url之后从对应的view.php页面中抓取出对应的博客内容。
但是我们是否还记得上面提及过线程安全这个概念?前一类线程一边往article_list字段中写入数据,另外那一类的线程从article_list中读取数据但是删掉早已读取完毕的数据。但是python中list并不是线程安全版本的数据结构,因此这样操作会导致不可预想的错误。所以我们可以尝试使用一个愈发便捷且线程安全的数据结构,这就是我们的子标题中所提及的Queue队列数据结构。
同样Queue也有一个join()方法,这个join()方法虽然和上一个小节所提到的threading中join()方法差不多,只不过在Queue中,join()的阻塞条件是当队列不为空空的时侯才阻塞,否则继续执行join()后面的代码。在这个爬虫中我便使用了这些技巧来阻塞主线程而不是直接通过线程的join方法来阻塞主线程,这样的用处是可以不用写一个死循环来判定当前任务队列中是否还有未执行完的任务,让程序运行愈发高效,也使代码愈发柔美。
还有一个细节就是在python2.7中队列模块的名子是Queue,而在python3.x中早已更名为queue,就是首字母大小写的区别,大家假如是复制网上的代码,要记得这个小区别。
如果你们学过c语言的话,对这个模块应当会太熟悉,他就是一个负责从命令行中的命令上面提取出附送参数的模块。比如说我们一般在命令行中操作mysql数据库,就是输入mysql -h127.0.0.1 -uroot -p,其中mysql前面的“-h127.0.0.1 -uroot -p”就是可以获取的参数部份。
我们平常在编撰爬虫的时侯,有一些参数是须要用户自己自动输入的,比如说mysql的主机IP,用户名密码等等。为了使我们的程序愈加友好通用,有一些配置项是不需要硬编码在代码上面,而是在执行他的时侯我们动态传入,结合getopt模块我们就可以实现这个功能。
哈希本质上就是一类物理算法的集合,这种物理算法有个特点就是你给定一个参数,他就能输出另外一个结果,虽然这个结果太短,但是他可以近似觉得是***的。比如说我们平常听过的md5,sha-1等等,他们都属于哈希算法。他们可以把一些文件,文字经过一系列的物理运算然后弄成短短不到一百位的一段数字中文混和的字符串。
python中的hashlib模块就为我们封装好了这种物理运算函数,我们只须要简单的调用它就可以完成哈希运算。
为什么在我这个爬虫中用到了这个包呢?因为在一些插口恳求中,服务器须要带上一些校验码,保证插口恳求的数据没有被篡改或则遗失,这些校验码通常都是hash算法,所以我们须要用到这个模块来完成这些运算。
很多时侯我们抓取到的数据不是html,而是一些json数据,json本质上只是一段富含通配符对的字符串,如果我们须要提取出其中特定的字符串,那么我们须要json这个模块来将这个json字符串转换为dict类型便捷我们操作。
有的时侯我们抓取到了一些网页内容,但是我们须要将网页中的一些特定格式的内容提取下来,比如说电子邮箱的格式通常都是上面几位英语数字字母加一个@符号加的域名,而要象计算机语言描述这些格式,我们可以使用一种称作正则表达式的表达式来抒发出这些格式,并且使计算机手动从一大段字符串上将符合这些特定格式的文字匹配下来。
这个模块主要用于处理一些系统方面的事情,在这个爬虫中我用他来解决输出编码问题。
稍微学过一点法语的人都还能猜下来这个模块用于处理时间,在这个爬虫中我用它来获取当前时间戳,然后通过在主线程末尾用当前时间戳除以程序开始运行时的时间戳,得到程序的运行时间。

如图所示,开50个线程抓取100页(每页30个贴子,相当于抓取了3000个贴子)贴吧贴子内容而且从中提取出手机邮箱这个步骤共历时330秒。
这两个模块都是用于处理一些http请求,以及url低格方面的事情。我的爬虫http请求部份的核心代码就是使用这个模块完成的。
这是一个第三方模块,用于在python中操作mysql数据库。
这里我们要注意一个细节问题:mysqldb模块并不是线程安全版本,意味着我们不能在多线程中共享同一个mysql联接句柄。所以你们可以在我的代码中听到,我在每位线程的构造函数中都传入了一个新的mysql联接句柄。因此每位子线程只会用自己独立的mysql联接句柄。
这也是一个第三方模块,网上还能找到相关代码,这个模块主要用于向命令行中输出彩色字符串。比如说我们一般爬虫出现错误,要输出黄色的字体会比较醒目,就要使用到这个模块。

如果你们在网路质量不是挺好的环境下使用该爬虫,会发觉有的时侯会报如图所示的异常,这是我为了偷懒并没有写各类异常处理的逻辑。
通常情况下我们假如要编撰高度自动化的爬虫,那么就须要意料到我们的爬虫可能会遇见的所有异常情况邮箱爬虫软件,针对这种异常情况做处理。
比如说如图所示的错误,我们就应当把当时正在处理的任务重新伸入任务队列,否则我们还会出现遗漏信息的情况。这也是爬虫编撰的一个复杂点。
其实多线程爬虫的编撰也不复杂,多看示例代码,多自己动手尝试,多去社区,论坛交流,很多精典的书上对多线程编程也有特别详尽的解释。这篇文章本质上主要还是一篇科普文章,内容讲解的都不是太深入,大家还须要课外自己多结合网上各类资料自己学习。
【编辑推荐】
Python程序员都该用的一个库Python正则表达式re模块简明笔记这种方式推动Python开发者提升效率Python并发编程之线程池/进程池Python黑魔法之描述符
高拓展性的Java多线程爬虫框架reptile(个人开源项目)
采集交流 • 优采云 发表了文章 • 0 个评论 • 292 次浏览 • 2020-05-07 08:03
Reptile是一个具有高拓展性的可支持单机与集群布署Java多线程爬虫框架,该框架可简化爬虫的开发流程。该框架各个组件高内聚松耦合的特点使用户可以对不同组件进行订制来满足不同的需求。
Reptile.png
Reptile作为爬虫主体可在主线程运行也可以异步运行,爬虫主要有四个核心组件:
Downloader 执行恳求下载与解析响应ResponseHandler 由使用者提供实现来对响应处理,生成Result结果与新的爬取恳求RequestConsumer 来对处理的结果Result进行消费,例如持久化储存java单机爬虫框架,用户可自定义其具体实现
四个组件之间的关系如构架图所示,它们之间的相互调用产生一个完整的工作流并在Workflow线程中运行,Reptile爬虫会依照配置的线程数目通过线程池创建指定数目的工作流线程并发执行工作流任务。
clone项目并建立发布到本地库房
git clone git@github.com:xiepuhuan/reptile.git
cd reptile
mvn -Dmaven.test.skip=true
在项目中使用Maven引入对应的依赖
<dependency>
<groupId>com.xiepuhuan</groupId>
<artifactId>reptile</artifactId>
<version>0.3</version>
</dependency>
实现ResponseHandler插口,重写isSupport与handle技巧。实现Consumer插口java单机爬虫框架,重写consume方式,执行对数据的消费,可在该方式中对响应处理结果进行持久化等操作,目前提供了ConsoleConsumer,JsonFileConsumer, MongoDBConsumer等实现,默认使用ConsoleConsumer。
public class ZhihuPageHandler implements ResponseHandler {
private static final String[] URLS = new String[] {
"https://www.zhihu.com/api/v4/s ... ot%3B
};
@Override
public List<Request> handle(Response response, Result result) {
Content content = response.getContent();
JSONObject jsonObject = JSON.parseObject(content.getContent(), JSONObject.class);
result.setResults(jsonObject.getInnerMap());
JSONObject paging = jsonObject.getJSONObject("paging");
if (!paging.getBoolean("is_end")) {
List<Request> requests = new ArrayList<>();
requests.add(new Request(paging.getString("next")));
return requests;
}
return null;
}
@Override
public boolean isSupport(Request request, Response response) {
return true;
}
public static void main(String[] args) {
// 构建Reptile爬虫配置类,
ReptileConfig config = ReptileConfig.Builder.cutom()
.setThreadCount(8)
.appendResponseHandlers(new ZhihuPageHandler())
.setDeploymentMode(DeploymentModeEnum.SINGLE)
.setConsumer(new ConsoleConsumer())
.build();
// 根据reptile配置构建Reptile爬虫并添加爬去的URL
Reptile reptile = Reptile.create(config).addUrls(URLS);
// 启动爬虫
reptile.start();
}
}
分布式布署时,创建配置类时须要通过setDeploymentMode方式指定布署模式为DeploymentModeEnum.Distributed,并且须要通过setScheduler方式设置一个Redis队列调度器,可以使用RedisFIFOQueueScheduler作为实现。 查看全部
Reptile.png
Reptile作为爬虫主体可在主线程运行也可以异步运行,爬虫主要有四个核心组件:
Downloader 执行恳求下载与解析响应ResponseHandler 由使用者提供实现来对响应处理,生成Result结果与新的爬取恳求RequestConsumer 来对处理的结果Result进行消费,例如持久化储存java单机爬虫框架,用户可自定义其具体实现
四个组件之间的关系如构架图所示,它们之间的相互调用产生一个完整的工作流并在Workflow线程中运行,Reptile爬虫会依照配置的线程数目通过线程池创建指定数目的工作流线程并发执行工作流任务。
clone项目并建立发布到本地库房
git clone git@github.com:xiepuhuan/reptile.git
cd reptile
mvn -Dmaven.test.skip=true
在项目中使用Maven引入对应的依赖
<dependency>
<groupId>com.xiepuhuan</groupId>
<artifactId>reptile</artifactId>
<version>0.3</version>
</dependency>
实现ResponseHandler插口,重写isSupport与handle技巧。实现Consumer插口java单机爬虫框架,重写consume方式,执行对数据的消费,可在该方式中对响应处理结果进行持久化等操作,目前提供了ConsoleConsumer,JsonFileConsumer, MongoDBConsumer等实现,默认使用ConsoleConsumer。
public class ZhihuPageHandler implements ResponseHandler {
private static final String[] URLS = new String[] {
"https://www.zhihu.com/api/v4/s ... ot%3B
};
@Override
public List<Request> handle(Response response, Result result) {
Content content = response.getContent();
JSONObject jsonObject = JSON.parseObject(content.getContent(), JSONObject.class);
result.setResults(jsonObject.getInnerMap());
JSONObject paging = jsonObject.getJSONObject("paging");
if (!paging.getBoolean("is_end")) {
List<Request> requests = new ArrayList<>();
requests.add(new Request(paging.getString("next")));
return requests;
}
return null;
}
@Override
public boolean isSupport(Request request, Response response) {
return true;
}
public static void main(String[] args) {
// 构建Reptile爬虫配置类,
ReptileConfig config = ReptileConfig.Builder.cutom()
.setThreadCount(8)
.appendResponseHandlers(new ZhihuPageHandler())
.setDeploymentMode(DeploymentModeEnum.SINGLE)
.setConsumer(new ConsoleConsumer())
.build();
// 根据reptile配置构建Reptile爬虫并添加爬去的URL
Reptile reptile = Reptile.create(config).addUrls(URLS);
// 启动爬虫
reptile.start();
}
}
分布式布署时,创建配置类时须要通过setDeploymentMode方式指定布署模式为DeploymentModeEnum.Distributed,并且须要通过setScheduler方式设置一个Redis队列调度器,可以使用RedisFIFOQueueScheduler作为实现。 查看全部
Reptile是一个具有高拓展性的可支持单机与集群布署Java多线程爬虫框架,该框架可简化爬虫的开发流程。该框架各个组件高内聚松耦合的特点使用户可以对不同组件进行订制来满足不同的需求。

Reptile.png
Reptile作为爬虫主体可在主线程运行也可以异步运行,爬虫主要有四个核心组件:
Downloader 执行恳求下载与解析响应ResponseHandler 由使用者提供实现来对响应处理,生成Result结果与新的爬取恳求RequestConsumer 来对处理的结果Result进行消费,例如持久化储存java单机爬虫框架,用户可自定义其具体实现
四个组件之间的关系如构架图所示,它们之间的相互调用产生一个完整的工作流并在Workflow线程中运行,Reptile爬虫会依照配置的线程数目通过线程池创建指定数目的工作流线程并发执行工作流任务。
clone项目并建立发布到本地库房
git clone git@github.com:xiepuhuan/reptile.git
cd reptile
mvn -Dmaven.test.skip=true
在项目中使用Maven引入对应的依赖
<dependency>
<groupId>com.xiepuhuan</groupId>
<artifactId>reptile</artifactId>
<version>0.3</version>
</dependency>
实现ResponseHandler插口,重写isSupport与handle技巧。实现Consumer插口java单机爬虫框架,重写consume方式,执行对数据的消费,可在该方式中对响应处理结果进行持久化等操作,目前提供了ConsoleConsumer,JsonFileConsumer, MongoDBConsumer等实现,默认使用ConsoleConsumer。
public class ZhihuPageHandler implements ResponseHandler {
private static final String[] URLS = new String[] {
"https://www.zhihu.com/api/v4/s ... ot%3B
};
@Override
public List<Request> handle(Response response, Result result) {
Content content = response.getContent();
JSONObject jsonObject = JSON.parseObject(content.getContent(), JSONObject.class);
result.setResults(jsonObject.getInnerMap());
JSONObject paging = jsonObject.getJSONObject("paging");
if (!paging.getBoolean("is_end")) {
List<Request> requests = new ArrayList<>();
requests.add(new Request(paging.getString("next")));
return requests;
}
return null;
}
@Override
public boolean isSupport(Request request, Response response) {
return true;
}
public static void main(String[] args) {
// 构建Reptile爬虫配置类,
ReptileConfig config = ReptileConfig.Builder.cutom()
.setThreadCount(8)
.appendResponseHandlers(new ZhihuPageHandler())
.setDeploymentMode(DeploymentModeEnum.SINGLE)
.setConsumer(new ConsoleConsumer())
.build();
// 根据reptile配置构建Reptile爬虫并添加爬去的URL
Reptile reptile = Reptile.create(config).addUrls(URLS);
// 启动爬虫
reptile.start();
}
}
分布式布署时,创建配置类时须要通过setDeploymentMode方式指定布署模式为DeploymentModeEnum.Distributed,并且须要通过setScheduler方式设置一个Redis队列调度器,可以使用RedisFIFOQueueScheduler作为实现。