python爬虫爬取链家二手房信息

  一种有想做个爬虫的想法,正好上个月有足够的时间和精力就学了下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/

打赏

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.