一种有想做个爬虫的想法,正好上个月有足够的时间和精力就学了下scrapy,一个python开源爬虫框架。好多事开始以为很难,但真正下定决心去做的时候,才发现非常简单,scrapy我从0基础到写出第一个可用的爬虫只用了两天时间,从官网实例到我的demo,真是遇到一堆问题,通过查docs查博客,一个个问题解决下来,发现已经渐渐熟知了这个框架,真是发现带着问题去学习才是快的学习方式。
大学的时候有用python写过爬虫,但没用什么框架,用urllib把网页源码down下来后,写一堆正则表达式来提取其中的内容,真是快吐了。所以我一直觉得爬虫网页内容解析才是最麻烦的地方,scrapy提供xpath的方式提取网页内容,大大简化了爬虫的开发。另外,我们自己实现爬虫还要去管理所有的爬取动作,你爬取完这页,你还得去触发下一页,为了防止被ban,你还要构造header头,设置爬取规则…… scrapy简化了这一切,你只需要告诉它你要爬什么,要哪些数据,数据怎么保存即可。你只需要专注于爬取结果就好了,剩下的写middleware、pipline、item…… 简单的爬虫甚至不需要这些。
我用scrapy实现了一个爬取链家二手房的爬虫,全部源码我已经放到github上了https://github.com/xindoo/ershoufang。我需要声明的是这只是个简答的demo,存在一些问题,接下来我先说明有哪些问题,再来看看核心代码。
问题一
链家网站也有反爬虫策略和robots限制,robots限制忽略(不然没法爬),另外频繁爬取会直接导致被ban,需要隔天才会解禁止。防止被ban的方法有多种,1.禁止cookie 2.设置header 3.加大爬取间隔 4.使用代理。我只用了前三种方法,具体可以在settings.py 和middlewares.py里看到。因为没有免费好用的代理,所以在爬虫实际使用中没用方法4,但我在middlewares.py里也留下了相关代码,可稍做参考,但需要注意那几个代理ip是不可用的。
问题二
我代码里只爬取了3000套二手房价格,北京市实际在售的二手房大概有两万套,不是我不想全爬,只是链家只展示100页(3000套)的内容,排序方式我也并不清楚。我尝试通过分区域来爬取以获得更多的数据,但爬虫更容易被ban,大概爬几页后就被禁了,目前看来只能通过使用代理的方式解决。
问题三
我的爬取起始页是http://bj.lianjia.com/ershoufang/pg1/,一直爬取到100页, 我在代码里注释掉的 start_urls包含了北京市所有的区,如果不被ban,理论上是可以拿到北京市所有的二手房信息的。爬取的数据有如下。
'region': 小区
'url': 房屋详情页链接
'houseInfo': 房屋信息 类似| 3室2厅 | 126.4平米 | 南 北 | 精装 | 有电梯
'unitPrice': 每平米单价(元)
'totalPrice': 房屋总结(万元)
'attention': 被关注数
'visited': 被经纪人带看次数
'publishday': 房屋发布多长时间
下面是爬虫核心代码,全部代码可以上我github获取。
# -*- coding: utf-8 -*-
import scrapy
import re
class ershoufangSpider(scrapy.Spider):
name = "ershoufang"
#下面是北京市所有区的起始url
# start_urls = ["http://bj.lianjia.com/ershoufang/dongcheng/pg1", "http://bj.lianjia.com/ershoufang/xicheng/pg1", "http://bj.lianjia.com/ershoufang/chaoyang/pg1", "http://bj.lianjia.com/ershoufang/haidian/pg1", "http://bj.lianjia.com/ershoufang/fengtai/pg1", "http://bj.lianjia.com/ershoufang/shijingshan/pg1", "http://bj.lianjia.com/ershoufang/tongzhou/pg1", "http://bj.lianjia.com/ershoufang/changping/pg1", "http://bj.lianjia.com/ershoufang/daxing/pg1", "http://bj.lianjia.com/ershoufang/yizhuangkaifaqu/pg1", "http://bj.lianjia.com/ershoufang/shunyi/pg1", "http://bj.lianjia.com/ershoufang/fangshan/pg1", "http://bj.lianjia.com/ershoufang/mentougou/pg1", "http://bj.lianjia.com/ershoufang/pinggu/pg1", "http://bj.lianjia.com/ershoufang/huairou/pg1", "http://bj.lianjia.com/ershoufang/miyun/pg1", "http://bj.lianjia.com/ershoufang/yanqing/pg1", "http://bj.lianjia.com/ershoufang/yanjiao/pg1"]
#实际爬取过程中我只用了默认的起始url,不容易被ban
start_urls = ["http://bj.lianjia.com/ershoufang/pg1"]
def parse(self, response):
houses = response.xpath(".//ul[@class='sellListContent']/li")
for house in houses:
attention = ''
visited = ''
publishday = ''
try:
attention = house.xpath(".//div[@class='followInfo']/text()").re("\d+")[0]
visited = house.xpath(".//div[@class='followInfo']/text()").re("\d+")[1]
#因为发布日期中可能单位不是天,所以我做了简单的转化。
if u'月' in house.xpath(".//div[@class='followInfo']/text()").extract()[0].split('/')[2]:
number = house.xpath(".//div[@class='followInfo']/text()").re("\d+")[2]
publishday = '' + int(number)*30
elif u'年' in house.xpath(".//div[@class='followInfo']/text()").extract()[0].split('/')[2]:
number = house.xpath(".//div[@class='followInfo']/text()").re("\d+")[2]
publishday = '365'
else:
publishday = house.xpath(".//div[@class='followInfo']/text()").re("\d+")[2]
except:
print "These are some ecxeptions"
else:
pass
yield {
'region': house.xpath(".//div[@class='houseInfo']/a/text()").extract(),
'url':house.xpath(".//a[@class='img ']/@href").extract(),
'houseInfo':house.xpath(".//div[@class='houseInfo']/text()").extract(),
'unitPrice':house.xpath(".//div[@class='unitPrice']/span").re("\d+.\d+"),
'totalPrice':house.xpath(".//div[@class='totalPrice']/span").re("\d+.\d+"),
'attention': attention,
'visited': visited,
'publishday': publishday
}
page = response.xpath("//div[@class='page-box house-lst-page-box'][@page-data]").re("\d+")
p = re.compile(r'[^\d]+')
#这里是判断有没有下一页,毕竟不是所有区都是有第100页的,不能for循环到100
if len(page)>1 and page[0] != page[1]:
next_page = p.match(response.url).group()+str(int(page[1])+1)
next_page = response.urljoin(next_page)
yield scrapy.Request(next_page, callback=self.parse)
结语
说几个我拿数据看出来的结果。1.通过publishday我发现平均房屋留存时间变长。2.房屋均价上个月7万,这个月大概下降3-5k。 3.北京最便宜房屋单价1.6万/平方米,最贵14.9万/平方米(最贵和最便宜的一直都没卖出去)。 说明房市稍有降温。再次申明,这是从3000套房数据的统计结果,不是全量房屋统计结果,大家看看就好。
上个月爬取过几天的数据,我决定以后每天定时爬一次,长期积累的数据肯定能分析出一些有趣的结论,我把所有爬取的数据放在ftp://xindoo.me/,方便大家获取。同时别忘记访问下我博客http://xindoo.me/。