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

0×00 介绍

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

本人对于Python学习创建了一个小小的学习圈子,为各位提供了一个平台,大家一起来讨论学习Python。欢迎各位到来Python学习群:960410445一起讨论视频分享学习。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 总结与下章预告

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • pyspark.sql模块 模块上下文 Spark SQL和DataFrames的重要类: pyspark.sql...
    mpro阅读 9,451评论 0 13
  • 线程 操作系统线程理论 线程概念的引入背景 进程 之前我们已经了解了操作系统中进程的概念,程序并不能单独运行,只有...
    go以恒阅读 1,641评论 0 6
  • 爬虫爬取的数据要经过数据存储步骤存储在磁盘上,对一些数据量较小的项目,数据可以暂时以磁盘文件的形式存储,如果数据量...
    zhile_doing阅读 254评论 0 0
  • 最近由于优化搜索功能,要把之前工程内废弃的UISearchDisplayController换成UISearchC...
    he15his阅读 4,171评论 1 5