使用scrapy框架爬取‘房天下’的全国房价

使用scrapy框架爬取‘房天下’的全国房价

学习python有一段时间了,所以也想写一下自己的博客,该程序是运行在win10系统上,程序有很多不足,希望大家更正。首先,我爬取的是‘房天下’中全国的新房以及二手房的房源的具体信息。

1.分析网页
  • 找到含有全国各个城市的url: https://www.fang.com/SoufunFamily.htm
  • 注意:‘其他’地区的url没有想要的信息,所以不必提取
    在这里插入图片描述
  • 新房的url:https://newhouse.fang.com/house/s/
  • 二手房的url:https://esf.fang.com/
2.使用终端命令创建爬虫框架
  • scrapy startproject fang
  • 切换到刚刚创建好的‘fang’目录下创建爬虫
  • scrapy genspider fang_spider fang.com
  • 创建完成后在pycham中打开项目,如图:
    在这里插入图片描述
3.编写程序
  • items.py:Item 定义结构化数据字段,用来保存爬取到的数据,有点像Python中的dict,但是提供了一些额外的保护减少错误,可以通过创建一个 scrapy.Item 类, 并且定义类型为 scrapy.Field的类属性来定义一个Item,以下我创建出NewFangItem和EsfFangItem两个item类,用来存放爬取到的房源信息。
class NewFangItem(scrapy.Item):
    # 省份
    province = scrapy.Field()
    # 城市
    city = scrapy.Field()
    # 小区
    name = scrapy.Field()
    # 居室
    room = scrapy.Field()
    # 地址
    address = scrapy.Field()
    # 面积
    area = scrapy.Field()
    # 价格
    price = scrapy.Field()
    # 是否在售
    on_sale = scrapy.Field()
    # 详情页面
    dateil_url = scrapy.Field()


class EsfFangItem(scrapy.Item):
    # 省份
    province = scrapy.Field()
    # 城市
    city = scrapy.Field()
    # 小区
    name = scrapy.Field()
    # 居室
    room = scrapy.Field()
    # 地址
    address = scrapy.Field()
    # 朝向
    toward = scrapy.Field()
    # 几层
    floor = scrapy.Field()
    # 面积
    area = scrapy.Field()
    # 年代
    year = scrapy.Field()
    # 总价
    price = scrapy.Field()
    # 单价
    unit_price = scrapy.Field()
    # 详情页面
    dateil_url = scrapy.Field()
  • fang_spider:Spider类定义了如何爬取某个(或某些)网站。包括了爬取的动作以及如何从网页的内容中提取结构化数据(爬取item)。Spider就是定义爬取的动作及分析某个网页的地方。
    parse() : 解析response,并返回Item或Requests(需指定回调函数)。Item传给Item pipline持久化 , 而Requests交由Scrapy下载,并由指定的回调函数处理(默认parse()),一直进行循环,直到处理完所有的数据为止,以下是解析https://www.fang.com/SoufunFamily.htm网页并提取出新房和二手房url的代码:
# -*- coding: utf-8 -*-
import scrapy
import re
from fang.items import NewFangItem, EsfFangItem


class FangSpiderSpider(scrapy.Spider):
    name = 'fang_spider'
    allowed_domains = ['fang.com']
    start_urls = ['https://www.fang.com/SoufunFamily.htm']

    def parse(self, response):
        trs = response.xpath("//div[@class='outCont']//tr")
        province = None
        for tr in trs:
            tds = tr.xpath(".//td")
            province_td = tds[1]
            province_text = province_td.xpath(".//text()").get()
            province_text = re.sub(r'\s','',province_text)
            if province_text:
                province = province_text
            # 海外的房价不能爬取
            if province == '其它':
                continue
            city_td = tds[2]
            citys = city_td.xpath(".//a")
            for city_temp in citys:
                city = city_temp.xpath(".//text()").get()
                city_url = city_temp.xpath(".//@href").get()
                # 构建新房的链接:https://wuhu.newhouse.fang.com /house/s/
                url_model = city_url.split("//")
                url_model = url_model[1].split(".")[0]
                new_house_url = 'https://'+url_model+'.newhouse.fang.com/house/s/'
                # 构建二手房链接:https://wuhu.esf.fang.com/
                esf_house_url = 'https://'+url_model+'.esf.fang.com/'
                # 新房
                yield scrapy.Request(url=new_house_url,
                                     callback=self.parse_new_house,
                                     meta={'info':(province,city)}
                                     )

                # 二手房
                # yield scrapy.Request(url=esf_house_url,
                #                      callback=self.parse_esf_house,
                #                      meta={'info': (province, city)}

有了新房和二手房的url,就可以跳转到这两个url,从而提取出每一个房源的信息,值得注意的是要想爬取所有的新房房源信息,则在爬取完一个页面后需要获取到下一页的url地址,如图:在这里插入图片描述
我们可以使用xpath定位到class=‘next’的a标签的href就可以提取出来下一页的url地址,但这有一个问题会导致程序出错。我们进入到后面的某一页,如图: 在这里插入图片描述
会发现在后面的几页中,已经没有了class=‘next’的a标签,所以我们应该使用其他的方法,分析页面可以发现当你在某一页的时候,该页的a标签的class属性会变成class=‘active’,后面几页依旧如此,所以我们只需要定位到该页的a标签,然后找到它下一个的兄弟节点,就可以提取出来下一页url地址。而在xpath中,便是提供了这一方法:
following-sibling 选取当前节点之后的所有同级节点,跟preceding-sibling一样都是选取同级同父的节点,只不过following是取对应节点之后的节点,preceding-sibling取的是该节点之前的节点。于是我们便可以将提取下一页的url地址的代码写为:next_url = response.xpath("//div[@class='page']//a[@class='active']/followingsibling::a[1]/@href").get()
如下:

def parse_new_house(self, response):
    province, city = response.meta.get('info')
    lis = response.xpath("//div[contains(@class, 'nl_con')]//li")
    for li in lis:
        name = li.xpath(".//div[@class='nlcd_name']/a/text()").get()
        if name is not None:
            name = name.strip()
        room = li.xpath(".//div[contains(@class, 'house_type')]//a/text()").getall()
        room = "/".join(room)
        area = ''.join(li.xpath(".//div[contains(@class, 'house_type')]/text()").getall())
        area = re.sub(r"\s|-",'',area)
        address = li.xpath(".//div[@class='address']/a/@title").get()
        price = ''.join(li.xpath(".//div[@class='nhouse_price']//text()").getall())
        price = re.sub(r"\s","",price)
        on_sale = li.xpath(".//div[contains(@class, 'fangyuan')]/span/text()").get()
        dateil_url = li.xpath(".//div[@class='address']/a/@href").get()
        dateil_url = response.urljoin(dateil_url)
        item = NewFangItem(province=province, city=city, name=name, room=room, area=area, address=address, price=price, on_sale=on_sale, dateil_url=dateil_url)
        yield item
    next_url = response.xpath("//div[@class='page']//a[@class='active']/following-sibling::a[1]/@href").get()
    next_url = response.urljoin(next_url)
    if next_url:
        yield scrapy.Request(url=next_url,
                             callback=self.parse_new_house,
                             meta={'info':(province,city)}
                             # dont_filter=True
                             )
        # print('*'*100)
        # print(next_url)

在提取二手房房源信息的时候有一点需要注意:二手房的房型有联排、叠加,双拼,独栋和层几种,所以在提取时需要注意,代码如下:

def parse_esf_house(self, response):
    province, city = response.meta['info']
    dl_list = response.xpath("//div[contains(@class, 'shop_list')]//dl")
    for dl in dl_list:
        item = EsfFangItem(province=province, city=city)
        item['name'] = dl.xpath(".//span[@class='tit_shop']/text()").extract_first()
        room_list = dl.xpath(".//p[@class='tel_shop']/text()").extract()
        # 去掉rooms中的空格------>['1室1厅', '50㎡', '高层(共17层)', '东向', '2019年建', '']
        room_list = list(map(lambda x:re.sub(r"\s",'',x), room_list))
        for rooms in room_list:
            if "厅"in rooms:
                item['room'] = rooms
            elif "㎡" in rooms:
                item['area'] = rooms
            elif "层"in rooms or "排"in rooms or "加"in rooms or "栋" in rooms or "拼" in rooms:
                item['floor'] = rooms
            elif "向" in rooms:
                item['toward'] = rooms
            elif "建" in rooms:
                item['year'] = rooms
        item['address'] = dl.xpath(".//p[@class='add_shop']//span/text()").extract_first()
        item['price'] = "".join(dl.xpath(".//dd[@class='price_right']/span[1]//text()").extract())
        item['unit_price'] = "".join(dl.xpath(".//dd[@class='price_right']/span[2]//text()").extract())
        item['dateil_url'] = response.urljoin(dl.xpath(".//dt[@class='floatl']/a/@href").extract_first())
        yield item
    next_url = response.xpath("//div[@class='page_al']//p[1]/a/@href").extract_first()
    next_url = response.urljoin(next_url)
    if next_url:
        yield scrapy.Request(url=next_url,
                             callback=self.parse_esf_house,
                             meta={'info':(province,city)}
                             )
  • middleware.py:下载中间件是处于引擎(crawler.engine)和下载器(crawler.engine.download())之间的一层组件,可以有多个下载中间件被加载运行。当引擎传递请求给下载器的过程中,下载中间件可以对请求进行处理 (例如增加http header信息,增加proxy信息等);在下载器完成http请求,传递响应给引擎的过程中, 下载中间件可以对响应进行处理(例如进行gzip的解压等)
    设置随机请求头减小被反爬虫识别的概率(也可以增加使用代理ip),编写完后需要在setting中进行配置:
import random


class UserAgentDownloadMiddleware(object):
    USER_AGENTS = [
        "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
        "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
        "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
        "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
        "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
        "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
        "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
        "Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5"
    ]
    def process_request(self, request, spider):
        usre_agent = random.choice(self.USER_AGENTS)
        request.headers['User-Agent'] = usre_agent
  • settings.py:Scrapy设置(settings)提供了定制Scrapy组件的方法。可以控制包括核心(core),插件(extension),pipeline及spider组件。比如 设置Json Pipeliine、LOG_LEVEL等。
    进行配置:ROBOTSTXT_OBEY = False
    开启item_pipelne:ITEM_PIPELINES = { 'fang.pipelines.FangPipeline': 300, }
DOWNLOADER_MIDDLEWARES = {
   'fang.middlewares.UserAgentDownloadMiddleware': 543,
}
  • pipelines.py:当Item在Spider中被收集之后,它将会被传递到Item Pipeline,这些Item Pipeline组件按定义的顺序处理Item。以下pipeline将所有(从所有’spider’中)爬取到的item,存储到mysql数据库中(也可以存储到csv、json等文件中),需要先创建好数据库和数据表:
import pymysql

class FangPipeline(object):
    def __init__(self):
        dbparams = {
            'host':'127.0.0.1',
            'port':3306,
            'user':'root',
            'password':'root',
            'database':'fang',
            'charset':'utf8'
        }
        self.conn = pymysql.connect(**dbparams)
        self.cursor = self.conn.cursor()
        self._sql = None
    def process_item(self, item, spider):
        self.cursor.execute(self.sql,(item["province"],item['city'],item['name'],item['room'],item['address'],item['price'],item['area'],item['on_sale'],item['dateil_url']))
        self.conn.commit()
        return item

    @property
    def sql(self):
        if not self._sql:
            self._sql = """
            insert into fang_info(id, province, city, name, room, address, price, area, on_sale, dateil_url) values(null, %s, %s, %s, %s, %s, %s, %s, %s, %s)
            """
            return self._sql
        return self._sql

完成以上工作后便可以运行程序了,由于数据较多,所以我只爬取了新房的房源信息,我保存在本机的数据库fang中的fang_info数据表中,爬取完成之后可以打开数据库查看,如下:
在这里插入图片描述
也可以使用终端命令查看:windows操作系统中要先找到mysql.exe文件所在的目录,输入命令:mysql -uroot -p便可以打开mysql终端命令行进行查询,如下:
在这里插入图片描述在win10中使用命令行查询如果中文显示乱码的话可以参照这个解决:https://blog.csdn.net/weixin_38684352/article/details/88995383

至此整个爬虫便完成了,有了数据之后,便可以对房源数据进行分析了,详情可以看我下一篇博客。

  • 6
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值