当前位置: 首页 > 科技观察

使用Python编写多线程爬虫抓取百度贴吧邮箱与手机号

时间:2023-03-18 20:16:41 科技观察

用Python写多线程抓取百度贴吧邮箱和手机号的爬虫爬虫源码,我记得我写了一个抓取百度贴吧邮箱和手机号的爬虫之前在练习的时候贴吧,所以就开源出来分享给大家学习参考。需求分析:本爬虫主要爬取百度贴吧的各种帖子内容,分析帖子内容抓取手机号和邮箱。主要流程在代码注释中有详细说明。测试环境:代码在Windows764bit、python2.764bit(安装了mysqldb扩展)和centos6.5、python2.7(安装了mysqldb扩展)环境准备:工欲善其事必先利其器,从截图可以看出我的环境是Windows7+PyCharm。我的Python环境是Python2.764位。这是一个更适合新手的开发环境。那我建议你安装一个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的环境变量,在Windowscommand行下输入easy_install有回显即表示安装成功。环境选择细节:至于电脑硬件,当然是越快越好,内存起码8G起步,因为爬虫本身需要大量存储和分析中间数据,尤其是多线程爬虫,当他们遇到爬取列表和详情有分页的Pages,并且需要抓取的数据量很大时,使用队列分配抓取任务会消耗大量内存。包括有的时候我们抓取的数据是用json的,如果用mongodb等nosql数据库存储,也会占用很大的内存。网络连接建议使用有线网络,因为市面上的一些劣质无线路由器和普通民用无线网卡在线程比较大的时候会出现间歇性断网、数据丢失、丢包等情况。我亲身经历过这一点。至于操作系统和python,当然要选64位的。如果您使用的是32位操作系统,则无法使用大内存。如果你用的是32位的python,小规模抓取数据时可能不会觉得有什么问题,但是当数据量变大时,比如列表、队列、字典,大量的数据存储在它,导致python的内存使用量超过2g时,会报内存溢出错误。原因在Evian对我曾经在segmentfault上问过的问题的回答中有解释(java-python的内存使用量一达到1.9G就会开始报内存溢出错误——SegmentFault)。如果打算使用mysql存储数据,建议使用mysql5.5之后的版本,因为mysql5.5版本支持json数据类型,mongodb可以舍弃。(有人说mysql会比mongodb稳定,这个我不太确定)至于3.x版本的python,为什么我这里还是用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响应,存储到我们的电脑中来进行数据爬取。了解http协议有助于我们在爬取数据时准确控制一些可以加快爬取速度的参数,比如keep-alive。线程模块(多线程):我们平时写的程序都是单线程程序,我们写的代码运行在主线程中,主线程运行在python进程中。关于线程和进程的解释可以参考阮一峰的博客:进程和线程的简单解释——阮一峰的网络日志通过一个叫做threading的模块在python中实现了多线程。之前有thread模块,但是threading对线程的控制比较强,所以我们都改用threading来实现多线程编程。关于threading和multi-threading的一些用法,我觉得这篇文章不错:【python】Topic8.Threadandthreadingofmulti-threadedprogramming,大家可以参考一下。简单的说,使用threading模块写多线程程序就是先定义一个类,然后这个类必须继承threading.Thread,把每个线程要做的工作代码写到一个的run方法中班级。当然,如果线程在创建的时候想做一些初始化工作,就必须在它的__init__方法中编写初始化工作要执行的代码。这个方法就像php和java中的构造方法一样。这里还要讲一讲线程安全的概念。通常,在我们的单线程情况下,每一时刻只有一个线程在对资源(文件、变量)进行操作,所以不可能发生冲突。但是在多线程的情况下,可能会出现两个线程同时操作同一个资源,导致资源损坏,所以我们需要一种机制来解决这种冲突造成的损坏,通常是加锁等操作,比如mysql数据库的innodb表引擎有行级锁等,文件操作有读锁等,这些都是他们程序底层为我们完成的。所以我们通常只需要知道那些操作,或者那些程序处理过线程安全问题,然后我们就可以在多线程编程中使用它们了。而这种考虑了线程安全问题的程序,一般被称为“线程安全版”。比如php有个TS版本,这个TS就是ThreadSafety线程安全的意思。下面我们要讲的Queue模块是一个线程安全的队列数据结构,所以我们可以放心的在多线程编程中使用它。***我们将讨论线程阻塞的关键概念。在我们详细研究了线程模块之后,我们大概知道如何创建和启动线程。但是如果我们创建线程然后调用start方法,那么我们会发现整个程序好像马上就结束了,这是怎么回事呢?其实这是因为我们在主线程中只有负责启动子线程的代码,也就是说主线程只有启动子线程的功能。至于子线程执行的代码,本质上只是写在类中的一个方法。他实际上并不在主线程中执行,所以主线程启动子线程后,自己的工作已经完成,光荣退出。现在主线程退出了,python进程也就结束了,其他线程就没有内存空间继续执行了。所以我们应该让主线程的大哥等到子线程的小弟全部执行完才光荣离场,那么线程对象中有没有办法阻塞主线程呢?线。睡觉?这确实是一个解决方案,但是主线程应该睡多久呢?我们并不知道完成一个任务到底需要多长时间,所以我们肯定不能使用这种方法。所以这个时候我们应该上网查一下。有没有办法让子线程“卡”在主线程上?“卡”这个词似乎太俗了。其实更专业一点,应该叫“阻塞”,所以我们可以查询“python子线程阻塞主线程”。如果我们能正确使用搜索引擎的话,应该能找到一个方法,它叫join(),没错,这个join()方法就是子线程用来阻塞主线程的方法,当子线程还没有执行完毕,主线程会卡在join()方法所在的那一行,直到所有线程都执行完毕,join()方法后面的代码才会执行??。队列模块(queue):假设有这样一个场景,我们需要爬取一个人的博客,我们知道这个人的博客有两个页面,一个list.php页面显示这个博客的所有文章链接,还有一个view.php页面显示一篇文章的具体内容。如果我们要抓取这个人博客的所有内容,写一个单线程爬虫的思路是:先用正则表达式抓取这个list.php页面的所有linka标签的href属性,并存储他们在一个name是article_list的数组(在python里不叫数组,叫list,中文名字列表),然后用for循环遍历article_list数组,用各种函数抓取webcontent获取内容并将其存储在数据库中。.如果我们要写一个多线程的爬虫来完成这个任务,假设我们的程序使用了10个线程,那么我们就得想办法把之前抓取的article_list平均分成10份,每份分配给其中的一份一个子线程。但是问题来了,如果我们的article_list数组的长度不是10的倍数,也就是文章的数量不是10的整数倍,那么最后一个线程会比其他线程少分配一些任务,那么它会更快结束。如果我们只是抓取只有几千字的博文似乎没有问题,但是如果我们有一个任务(不一定是爬网页的任务,可能是数学计算或者图形渲染等耗时的任务)如果运行时间很长,那么这会造成资源和时间的极大浪费。我们多线程的目的就是尽可能的利用所有的计算资源和计算时间,所以我们得想办法让任务更加科学合理。而我还需要考虑一种情况,就是在文章数量较多的情况下,我们不仅要快速抓取文章内容,还要尽快看到抓取到的内容。这个要求在很多CMS收集网站上也经常体现出来。比如我们现在要抓取的目标博客有几千万篇文章。通常这种情况下blog会被分页,所以如果按照上面的传统思路抓取list的所有页数。几个小时甚至几天,如果老板要你尽快把抓取的内容展示出来,尽快把抓取的内容展示到我们的CMS采集站,那我们就得抓取list.php,把抓取到的数据丢到一个article_list数组中,另外一个线程从article_list数组中提取抓取到的文章的url地址,然后这个线程去对应的url地址,使用正则表达式获取博客文章的内容。如何实现这个功能?我们需要同时开启两种类型的线程。一类线程负责抓取list.php中的url丢到article_list数组中。获取对应的博客内容。但是我们还记得前面提到的线程安全的概念吗?前一种线程向article_list数组写入数据,另一种线程从article_list中读取数据,并删除读取到的数据。但是python中的list并不是线程安全版的数据结构,所以这种操作会导致不可预知的错误。所以我们可以尝试使用一种更方便和线程安全的数据结构,也就是我们小标题中提到的Queue队列数据结构。同样,Queue也有一个join()方法。这个join()方法其实和上一节提到的threading中的join()方法类似,只是在Queue中,join()的阻塞条件是队列不为空的时候。只有当它被阻塞时,否则继续执行join()后面的代码。在这个爬虫中,我使用了这个方法来阻塞主线程,而不是直接通过线程的join方法来阻塞主线程。这样做的好处是不需要写死循环来判断当前任务队列中是否有未完成的任务。任务使程序运行更高效,代码更优雅。还有一个细节就是queue模块在python2.7中的名字是Queue,而在python3.x中已经改名为queue了,就是首字母大小写的区别。如果你在网上复制代码,你一定要记住这个小区别。getopt模块:如果学过C语言,应该对这个模块不陌生。它是一个负责从命令行中的命令中提取附加参数的模块。例如,我们通常在命令行操作mysql数据库,即输入mysql-h127.0.0.1-uroot-p,其中mysql后面的“-h127.0.0.1-uroot-p”为参数部分可以获得。我们平时写爬虫的时候,有些参数需要用户手动输入,比如mysql主机IP、用户名和密码等等。为了让我们的程序更加友好和通用,有些配置项不需要硬写在代码中,而是在执行时动态传入。结合getopt模块,我们就可以实现这个功能。hashlib(哈希):哈希本质上是数学算法的集合。这个数学算法有一个特点,你给一个参数,它可以输出另一个结果。这个结果虽然很短,但是可以近似。是的。比如我们平时听到的md5、sha-1等,都属于hash算法。他们可以把一些文件和文本经过一系列的数学运算,变成一串数字和英文混合的不到百位的数字。python中的hashlib模块为我们封装了这些数学运算函数,我们只需要简单调用它就可以完成哈希运算。为什么我的爬虫要用这个包?因为在一些接口请求中,服务端需要带上一些验证码来保证接口请求的数据没有被篡改或者丢失。这些验证码一般都是哈希算法,所以我们需要使用这个模块来完成这个操作。json:很多时候我们抓取的数据并不是html,而是一些json数据。json本质上只是一个包含键值对的字符串。如果我们需要提取特定的字符串,那么就需要json模块来将这个json字符串转换为dict类型,方便我们操作。re(正则表达式):有时候我们抓取一些网页内容,但是需要从网页中提取一些特定格式的内容。例如,电子邮件的格式一般是在开头的几个英文数字和字母加上一个在http://xxx.xxx的域名后加上@符号,要像计算机语言一样描述这种格式,我们可以用一个叫做正则表达式的表达式来表达这种格式,让计算机自动从一个大段落开始匹配字符串中符合这种特定格式的文本。sys:这个模块主要用来处理一些系统问题。在这个爬虫中,我用它来解决输出编码问题。time:学过一点英语的人都能猜到,这个模块是用来处理时间的。在这个爬虫中,我用它来获取当前时间戳,然后在主线程Timestamp结束时用当前时间戳减去程序开始运行的时间,得到程序的运行时间。如图所示,开启50个线程抓取100页(每页30个帖子,相当于抓取3000个帖子),从贴吧帖子内容中提取手机邮箱,耗时330秒。urllib和urllib2:这两个模块用来处理一些http请求和url格式化。我的爬虫http请求部分的核心代码就是使用这个模块完成的。MySQLdb:这是python操作mysql数据库的第三方模块。这里我们要注意一个细节:mysqldb模块不是线程安全的版本,这意味着我们不能在多个线程中共享同一个mysql连接句柄。所以你可以在我的代码中看到,我在每个线程的构造函数中传入了一个新的mysql连接句柄。因此,每个子线程只会使用自己独立的mysql连接句柄。cmd_color_printers:这个也是第三方模块,相关代码可以在网上找到。该模块主要用于将颜色字符串输出到命令行。例如,我们通常在爬虫出现错误时使用该模块。如果我们要输出红色字体,会比较显眼。自动爬虫的错误处理:如果你在网络质量不是很好的环境下使用这个爬虫,你会发现有时会报如图所示的异常。这是因为我没有写各种异常处理逻辑来偷懒。通常,如果我们要编写一个高度自动化的爬虫,我们需要预见到我们的爬虫可能遇到的所有异常情况,并对这些异常情况进行处理。比如图中的错误,我们应该把当时正在处理的任务重新放入任务队列中,否则会漏掉信息。这也是爬虫写法的复杂点。总结:其实多线程爬虫的编写并不复杂。多看示例代码,多自己尝试,多去社区和论坛交流,很多经典书籍对多线程编程也有很详细的讲解。本文本质上是一篇科普文章,内容不是很深入。还是需要课后结合网上的各种资料自学。