python爬虫抓取链家租房数据

python爬虫抓取链家租房数据

初学python和爬虫,正好赶上要在帝都租房,于是打算自己抓下链家的租房数据试试。看到网上有人抓取链家的二手房买卖数据,参考了下,不过我抓租房数据的时候发现还比较简单,不需要模拟登陆,链家也没怎么反爬虫,因而一路还比较顺利。

总体思路,虽然链家没有采用太多的反爬虫技术,但是基本的限制IP访问密度还是做了的,所以得动用代理,这么一来,抓取效率也必然降低,所以得采用多线程。实现的时候先实现代理的抓取,然后实现单线程单页面的抓取,接着改为多线程,再结合代理。

先看下代理部分。网上搜索了下,几个不断更新的免费的代理平台有,快代理、西刺代理和proxy360。那就针对这几个网站,分别封装类,提取代理IP和端口呗。限于初学,为了扎实基本功,就用了最笨的正则表达式提取数据。

先把代码全部贴出来:

几个类都类似,如果放C#下面,可以实现个接口。getUrlList先生成可以抓取的页面URL,然后getproxy抓取代理服务器IP端口。此外定义了proxyValidate和multiThreadValidate对抓取的代理服务器进行验证,毕竟免费的质量毕竟低,很多是用不了或者可用时间很短。验证的时候就下载指定url,并匹配关键词,匹配成功则表明服务器可用,这是最简单的做法。multiThreadValidate用了多线程的方法,每个线程还是执行proxyValidate进行验证,但是在该函数进行了任务拆分。

真正进行到链家数据抓取,考虑到数据量比较大(不适合写入文本,但又不至于动用高性能大型数据库),但有都是结构化数据,为了高效存储,采用轻量级的sqlite,考虑到多线程操作数据库,必然带来访问一致性问题,而网络IO较慢,数据库读写频率不高,可以加个线程锁保证单次只有一个线程访问。

数据库封装如下:

 

其中为了保证线程锁不被滥用造成死锁,以及数据库连接能被及时关闭,定义了一个高阶函数conn_trans,也即装饰器函数(decorator)。定义了一个全局函数createDB来初始化数据库。仅执行一次。

抓取链家数据的类结构如下:

这里涉及几个多线程访问的列表变量,allPages表示待抓取的所有页面url,errorPageUrlList是抓取错误的页面url,errorRoomList是抓取房屋信息时发生错误的房屋ID,errorCommList是抓取小区信息失败时的小区ID。proxyList是可用代理服务器列表,一直更新中。proxyForceoutList是被网站封杀了的代理服务器列表。

首先是几个用于验证代理服务器是否可用的函数,threadUpdateProxy为主函数,程序启动后也随之启动,里面主要为一个死循环,判断当可用代理服务器列表proxyListOld小于阈值,启动抓取代理服务器,执行multiThreadValidate函数,参见前述getProxy模块。

getContentbyProxy完成使用代理服务器下载指定页面内容的功能。仅负责下载内容,解析由其他函数完成,参见后文。

这里得说两句,链家的租房信息是树状分布,以北京为例,顶层是按几大城区(东城、西城、海淀、大兴、顺义等等,程序中用mainRegion表示)分,然后是各区内的小街区(如东城区内的安定门、安贞、朝阳门外、前门等等,程序中用region或者subRegion表示),再然后是出租房数据。链家限制一次最多呈现100个页面的结果,每个页面三十条数据,因此不能直接按几大城区+页码的形式构造url获取查询结果。而通常子街区包含的结果数不超过30页,子街区之间又不重复,故而可以直接按子街区,然后按页构造url以此获取全部数据。这里我先将所有的子街区下载好,按页将url构造好并保存到数据库免去每次测试期间每次启动爬虫重新构造url的麻烦。

crawAllRentInfo为单线程版,仅测试用。实际采用crawAllRentInfo_multi函数,该函数调用multiThreadCrawList抓取所有带抓取页面url。multiThreadCrawList用多线程执行crawRentInfo_Regions,传入参数subRegionList包含子区域列表,由crawRentInfo_Regions调用crawRentInfo_certainRegion抓取每个子区域的搜索结果页url并返回。最终crawAllRentInfo_mult得到所有带抓取的页面url,然后再调用multiThreadCrawList开启多线程抓取所有页面,传入的抓取函数为crawRentInfo_Pages。第一次抓取到所有页面url可以用updatePageList()将页面url更新到数据库,后续执行可以直接用注释的语句getPageListFromDB()完成所有页面url获取。当然不想用多线程抓取所有页面url可以直接用crawRentInfo_Regions(self.regionUnrepeat)。

crawRentInfo_certainPage根据url抓取指定搜索结果页面,用正则表达式匹配出该页面上的房屋结果信息,crawRentInfo_pageTagInfo抓取搜索结果页上的特有的房屋标签信息(详情页里没有的)。然后根据房屋的ID信息转由crawRentInfo_certainRoom进一步抓取该ID房屋详细信息,由crawRentInfo_community进一步抓取对应的小区信息。由crawRentInfo_json抓取由json传送的带看信息。

后续的insertInfo根据表名和字段字典构造数据库插入sql语句并插入。updateInfo构造数据库更新语句并执行。insertDicListInfo批量插入数据。dumpErrorListToFile将抓取错误的数据以json形式序列化到文档中。loadErrorListFromFile完成对应的反序列化工作。recrawErrorList_multi多线程方式重新抓取之前抓取失败的各种信息。调用recrawError_Comms重新抓取之前抓取失败的小区信息。调用recrawError_Rooms同理。

剩下的没啥好说的啦。入口函数依次执行。

源码地址:https://github.com/atp798/BlogStraka/tree/master/CrawLianJia

发表评论