美文网首页大数据 爬虫Python AI Sqlpython入门基础学习
Python爬虫开发(三):数据存储以及多线程

Python爬虫开发(三):数据存储以及多线程

作者: 编程新视野 | 来源:发表于2019-01-10 13:30 被阅读3次

    0×00 介绍

    本文我们就两个方面来讨论如何改进我们的爬虫:数据存储和多线程,当然我承认这是为我们以后要讨论的一些东西做铺垫。

    本人对于Python学习创建了一个小小的学习圈子,为各位提供了一个平台,大家一起来讨论学习Python。欢迎各位到来Python学习群:943752371 一起讨论视频分享学习。Python是未来的发展方向,正在挑战我们的分析能力及对世界的认知方式,因此,我们与时俱进,迎接变化,并不断的成长,掌握Python核心技术,才是掌握真正的价值所在。

    目的:通常我们需要对爬虫捕捉的数据进行分析,处理,再次利用或者格式化,显然我们不能只是把爬虫捕捉到的数据在内存中处理,然后打印在屏幕上。在本章,我将介绍几种主流的数据存储方法。爬虫处理数据的能力往往是决定爬虫价值的决定性因素,同时一个稳定的存储数据的方法也绝对是一个爬虫的价值体现。

    另外,采用多开线程的爬虫,创造多个并行线程协调工作也绝对是提高爬虫效率,降低失败率的好办法。

    0×01 引导

    我们就接下来要讲的部分做一个简单的引导,关于数据存储方式:

    1、 存储索引或者直接下载数据

    2、CSV

    3、MySQL

    关于线程:

    如果读者并不会python的线程处理,可以参考这篇文章

    分为函数式和类包装,这两个方法进行线程处理。

    0×02 数据存储:存储索引或者直接下载数据

    关于这一点我觉得没有必要做深入的解释,因为这一点我们在前几篇文章中或多或少都有接触:比如制作sitemap:这里存储了整个网站你需要的链接,比如抓取freebuff文章生成.docx文档的这一节,这些其实都属于本节所说的数据存储方式。那么就本节而言,我再介绍一个例子,爬取一个freebuf商品列表区域所有的图片(听起来还是挺有趣的吧!?)

    步骤1:了解网站结构

    步骤2:编写脚本

    步骤3:测试

    首先我们需要了解一下我们的目标(为了避免广告嫌疑,这里还是以freebuf作为目标吧)

    审查元素发现下面的div标签包含了单个的商品信息,

    Div(class=nall-news)->div(class=col-sm6col-md-lg-4mall-product-list)->div(class=photo)->a->img

    这样我们就轻松加愉快地找到了img所在的地方,那么根据这些,我们可以指定简单的方案:获取商品的所在的标签,然后由于商品标签的一致性,我们可以一层一层索引下去找到图片的位置,当然有个不保险的办法就是,获取的直接获取img,(幸运的是,在这个例子中只存在一个img标签),我们测试从简,节约时间,那么一两分钟我们就写出了自己的脚本:

    importurllibfrombs4importBeautifulSoupimportreurl ='http://shop.freebuf.com/'print"prepare&reading to read theweb"data = urllib.urlopen(url).read()printdataprint"parsing ... ... ... "soup = BeautifulSoup(data)#<div class="col-sm-6 col-md-4col-lg-4 mall-product-list">itemlist =soup.findAll(name='div',attrs={'class':'col-sm-6 col-md-4 col-lg-4mall-product-list'})foriteminitemlist:printitem.img

    这样我们就在自己的debug I/O看到了打印出的九个img标签:

    然后我们用以前学到的技能,就足够把这些图片dump下来了,

    完善脚本!

    importurllibfrombs4importBeautifulSoupimportreurl ='http://shop.freebuf.com/'print"prepare&reading to read theweb"data = urllib.urlopen(url).read()printdataprint"parsing ... ... ... "soup = BeautifulSoup(data)#<div class="col-sm-6 col-md-4col-lg-4 mall-product-list">itemlist = soup.findAll(name='div',attrs={'class':'col-sm-6col-md-4 col-lg-4 mall-product-list'})foriteminitemlist:"""

    为了适配图片的格式,我们这里这样处理。

    不过不是绝对的,某些时候这样做就不合适:

    """   print item.img['src'][-4:]"""

    urlretrieve这个方法是我们以前接触过的,用于下载图片,还可以下载整个页面:

    """urllib.urlretrieve(url=item.img['src'],filename=item.img['alt']+item.img['src'][-4:])

    然后我们可以看一下成果,这样做的好处就是避免下来一大堆无关的图片,(有些时候我们下载整站,然后提取图片会发现各种图片混在一起了,那样确实烦得很):

    效果可以说是还不错吧,当然我懒并没有把图片建立文件夹存起来。

    0×03 数据存储:CSV

    CSV(comma-separated values),是目前比较流行的一种文件存储格式。被Excel和很多的应用程序支持。CSV文件存储的例子如下:

    Fruit,cost

    Apple,1.00

    Banana,0.30

    Pear,1.25

    看起来就是表格的压缩版,其实真的没有什么奇怪的,这个很简单的对吧?当然,大家都能想到这种方法存储表格再好不过了。不过笔者在这里建议:如果你只有一个table要处理,复制粘贴应该是比这样快,如果一堆table要处理,或者是要从各种数据中挑选出表格,然后组合成一张新表,这样无疑可以加快你的速度。

    那么我们就举一个例子来介绍一个下我们下一个例子。一定是一个有趣的体验:

    作为上一个例子的拓展:我们腰身成一个.csv文件,存储每个商品的名称和需要的金币数。

    我们观察一下具体的金币位置,商品信息都在哪里?笔者相信大家已经看到了,那么接下来我们得先整理一下获取info的办法:

    ->div(class=info)

    仅仅一步我们就可以得到信息位置。

    Div(class=info)->h4->商品信息

    Div(class=info)->p->strong->商品价格

    那么我们这就很简单了,对不对?

    在使用csv模块时,打开一个文件然后把文件描述符传入csv的writer然后写入row,但是由于我们这里存在中文,要注意要utf-8处理一下,否则报错或者是中文没法正常显示:

    importurllibfrombs4importBeautifulSoupimportcsv csvFile = open('items.csv','w+') url ='http://shop.freebuf.com/'print"prepare&reading to read theweb"data = urllib.urlopen(url).read()printdataprint"parsing ... ... ... "soup = BeautifulSoup(data)#<div class="col-sm-6 col-md-4col-lg-4 mall-product-list">itemlist =soup.findAll(name='div',attrs={'class':'col-sm-6 col-md-4 col-lg-4mall-product-list'}) writer = csv.writer(csvFile)writer.writerow(('name','price'))foriteminitemlist:#name#print item.find(name="h4").stringname = item.find(name='h4').string#price#print item.find(name='strong').stringprice = item.find(name='strong').string   writer.writerow((name.encode('utf-8','ignore'),price.encode('utf-8','ignore'))) csvFile.close()print"success to create the csvfile"

    那么最后我们得到了.csv文件,这个文件可以被excel打开的,在我的linux中,已经被识别成了csv:

    但是这里要注意中文编码问题。具体的解决办法限于篇幅我就不介绍了。

    0×04 MySQL

    我们在这一部分改造上面的例子,把MySQL整合在爬虫中,至于MySQL的安装配置我就不再解释了,我们简要梳理一下MySQL的py使用过程:

    0、提前建立好数据库scraping,建立好表items

    1、引入pymysql库

    2、通过数据库参数建立一个链接conn,

    3、Cur.conn.cursor()

    4、Cur.execute(query)

    5、如果需要的话还需要使用Cur.commit()

    6、Cur.close()

    7、Conn.close()

    数据库建立:CREATE DATABASE `scraping` DEFAULTCHARACTER SET utf8 COLLATE utf8_general_ci;

    创建表:CREATE TABLE item(namevarchar(255),price int(11));

    插入:INSERT INTO item (name,price) VALUES(‘test’,4);

    删除:DELETE FROM item WHRER name=’test’

    稍微修改一下上面的代码就可以简单适配MySQL了。

    importurllibfrombs4importBeautifulSoupimportpymysql conn =pymysql.connect(host="127.0.0.1",user='root',passwd='toor',db='mysql')cur = conn.cursor()cur.execute('USE scraping')url ='http://shop.freebuf.com/'print"prepare&reading to read theweb"data = urllib.urlopen(url).read()printdataprint"parsing ... ... ... "soup = BeautifulSoup(data)#<div class="col-sm-6 col-md-4col-lg-4 mall-product-list">itemlist =soup.findAll(name='div',attrs={'class':'col-sm-6 col-md-4 col-lg-4mall-product-list'})foriteminitemlist:#name#print item.find(name="h4").stringname = item.find(name='h4').string#price#print item.find(name='strong').stringprice = item.find(name='strong').string   query ='insert item(name,price) value('+"\'"+name.encode('utf-8','ignore') +"\',"+price.encode('utf-8','ignore') +');'cur.execute(query)   conn.commit()print"success to update thedatabase"print"preparing to read the data fromdatabase!"query ="Select * from item where1;"cur.execute(query)printcur.fetchall() cur.close()conn.close()

    当然上面的代码只是展示最简单的用法而已,我们还需要弄清楚的是编码问题,数据库还需要配置,要知道毕竟数据库的使用如果处理不好的话,也是一个不大不小的问题。

    0×06 嵌入式数据库BerkeleyDB(BDB)

    关于介绍我不想太官方,那么简单来说,笔者对BDB的认识如下:

    BDB是oracle的一个轻量型嵌入式数据库,只支持key-value数据形式存储,介于内存数据库和硬盘数据库之间,是单写入多读取的相当好的解决方案。

    BDB的python接口,bsddb模块,可以把BDB的数据库读写操作作为一个数组来进行。查阅python2手册可以找到这个模块,非常易于使用。

    笔者在这里建议,使用BDB来存储url。笔者可以提出一个比较可行的方案,一个url的md5值作为key,url的值作为value,读写直接操作数据库,简化url管理。

    0×05 多线程

    我们回顾sitemap爬虫的时候,我们发现,爬取一个相对比较小的网站(一百页左右)的时候大概用了2分40秒(加上为了避免频繁请求设置的延时),显然对于我们来说这是不够的,我们当然有很多办法来加速,但是笔者在这里并不建议修改源代码中的url请求等待时间。我们仅仅多开线程执行就可以达到我们预期的效果,这是基本所有类似程序都会采用的方法,但是实际的使用的时候,可能会有各种问题:

    1、同步问题

    2、线程池管理

    ……

    高效稳定的线程管理是编写多线程程序或者脚本的基础。

    预备知识:

    Python的线程模块Threading模块,【链接

    Python自制线程池:线程池是解决并发问题的有力武器,在多线程爬虫设计中,我们仍然可以使用这样的方法改装一下

    理论:

    我们采用多线程爬虫的时候,要清楚这个过程:每个爬虫爬到的数据都要汇总到一起,然后处理,然后再分配新任务到空闲的爬虫。然后根据这样的过程我们可以想到这样的过程好像和master-slave模式类似,如果大家没有接触过这个东西也没关系,简单来说,就是包工头和工人的关系,包工头负责整个小项目的统筹和任务派发,工人负责埋头苦干。

    然后根据这样的需求,我们初步设计一下这个多线程爬虫系统应该是怎么样的。

    在开始之前,我们首先需要明白一个manager的最基础职能:

    1、维护任务队列

    2、派发任务

    3、处理子线程返回的数据

    这样我们可以初步设想一下,再主线程的循环中进行所有的操作。那么,我们就意识到了这个manager的重要性了。那么就按照我们现在的想法,我们来整理以下这个多线程爬虫的设计思路:

    按着这个思路,笔者实现了两套多线程爬虫,一套是简易,不稳定的版本,一套是相对稳定的版本。为什么不是直接看第二套比较完整的呢?显然第一个版本简易不稳定,但是易于大家理解架构,第二个版本相对稳定,但是读起来可能有点痛苦。

    具体的代码在Github

    因为单个脚本太长了300+行。

    第二个版本,为了项目管理方便也为了贴合我自己的习惯,我就使用了vs13作为开发和管理工具,然后代码现在托管在github上,项目目录如下:

    参见Github链接,如果读者喜欢这个项目可以随意fork或者赏star,笔者将会更有力气维护这个多线程爬虫。当然这是一个未完成的项目,但是现在功能基本是完整的,可以实现自己自定义线程数稳定爬取固定域名下的网站sitemap,如果需要爬取内容的话,需要使用者自己去定义worker的分析部分,url处理我已经替大家基本写完了,这个项目的目的实际就是作为一个scraper platform存在,因此取名scraplat,但是笔者真的水平有限,赶着这篇文章之前完成了基本功能,在今后的一段时间内这个项目还会不断完善,实现动态网页爬取,自动化网页测试等高级接口。如果对爬虫技术感兴趣的读者可以长期关注以下这个项目,也欢迎大家在留言区写下自己想要实现的功能。

    0×06 总结与下章预告

    本章我们讨论了数据存储和多线程爬虫的实现,如果大家明白原理以后,就可以自己设计出自己的多线程爬虫甚至是分布式爬虫。到现在位置,我们手中的爬虫才算是像模像样。

    但是这还是不够,直到现在为止,我们只能处理静态的网页,如果想要处理动态加载的网页,(不知道有没有好事的读者曾经试过爬取淘宝商品页面,试过的朋友会发现传统的方案是没有办法处理淘宝商品页面的)。还有我们有时候希望我们的爬虫能真正的进入互联网,自由爬行,这些都是我们渴望解决的问题。

    相关文章

      网友评论

        本文标题:Python爬虫开发(三):数据存储以及多线程

        本文链接:https://www.haomeiwen.com/subject/tbfarqtx.html