如何高效地爬取链家的房源信息(一)

Python实现的链家网站的爬虫第一部分。


在之前的文章,以链家成都站为例,分析过链家网站数据的爬取,文章如下:

干货!链家二手房数据抓取及内容解析要点


但是,当时没有根据分析,将爬取实现。


本系列文将以链家南京站为例,使用Python实现链家二手房源信息的爬虫,将数据爬取,并存入数据库中,以便使用。


本文是第一部分,是整个爬取的基础,包括爬取逻辑、伪装正常访问、数据库设计及封装、区域URL爬取四个部分。


01

爬取逻辑


本文爬取的地区站虽与之前分析的地区站不同,但二者的结构是一样的,之前分析的成果可以直接套用。


根据之前的分析成果,得到爬取流程如下:

第一步,找到爬取站点的地址,这里爬取的是南京站,为https://nj.lianjia.com/。

第二步,从二手房查询页面获取大区信息,以便后续的查询。

这样的好处是可以分区查询,避免单次数据太多,链家服务器无法返回全部内容,最终导致数据不全。

第二步,根据分区信息,获取小区信息,存数据库,后续爬取操作,均以小区信息为起点。

第三步,根据各个小区信息,获取该小区的所有在售房源信息,存数据库。

第四步,根据各个小区信息,获取该小区的所有成交房源信息,存数据库。


确定了爬取流程,也就确定了程序的基本框架,接下来我们还需要准备相关Python库,数据库——sqlite3,以及分析网页库——BeautifulSoup。后者的安装参考:

Windows下Python 3.6 安装BeautifulSoup库


02


伪装正常访问


为避免被服务器发现爬虫行为,通常需要准备一批User Agents,这里收集了一些UA,可以直接拿去使用:

hds = [{'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'},

       {'User-Agent': 'Mozilla/5.0 (Windows NT 6.2) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.12 Safari/535.11'},

       {'User-Agent': 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0)'},

       {'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:34.0) Gecko/20100101 Firefox/34.0'},

       {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/44.0.2403.89 Chrome/44.0.2403.89 Safari/537.36'},

       {'User-Agent': 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50'},

       {'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50'},

       {'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0'},

       {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1'},

       {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1'},

       {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11'},

       {'User-Agent': 'Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11'},

       {'User-Agent': 'Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11'}]


03


数据库设计及封装


由于房源数据量不小,使用文本方式保存明显不便,因此,我们采用SQLite来进行数据的存储。这里,设计三个数据库,来保存不同的信息:

lianjia-xq.db,用来存储小区信息,需要存储URL、小区名称、小区区域及建设年代等信息。


lianjia-cj.db,用来存储小区成交二手房的信息,需要存储链接、小区名称、户型、面积、朝向、装修、电梯、签约时间、签约总价、楼层、年代楼型、来源、签约单价、税费、地铁、挂牌价、成交周期等信息。


lianjia-zs.db用来存储小区在售二手房的信息,需要存储链接、房子描述、小区名称、户型、面积、朝向、装修、电梯、楼层年代楼型、区域、关注、带看、发布时间、税费、地铁、限制、挂牌价、挂牌单价等信息。


这样,基本上把网站上的描述点都给提取出来了。


同时,我们对数据库的操作进行封装以便操作,直接参考网上的代码,并适当修改,这里给出代码:

class SQLiteWraper(object):

    """

    数据库的一个小封装,更好的处理多线程写入

    """

    def __init__(self, path, command='', *args, **kwargs):

        self.lock = threading.RLock()  # 锁

        self.path = path  # 数据库连接参数

        if command != '':

            conn = self.get_conn()

            cu = conn.cursor()

            cu.execute(command)


    def get_conn(self):

        conn = sqlite3.connect(self.path)  # ,check_same_thread=False)

        conn.text_factory = str

        return conn


    def conn_close(self, conn=None):

        conn.close()


    def conn_trans(func):

        def connection(self, *args, **kwargs):

            self.lock.acquire()

            conn = self.get_conn()

            kwargs['conn'] = conn

            rs = func(self, *args, **kwargs)

            self.conn_close(conn)

            self.lock.release()

            return rs


        return connection


    @conn_trans

    def execute(self, command, method_flag=0, conn=None):

        cu = conn.cursor()

        try:

            if not method_flag:

                cu.execute(command)

            else:

                cu.execute(command[0], command[1])

            conn.commit()

        except sqlite3.IntegrityError as e:

            # print e

            return -1

        except Exception as e:

            print(e)

            return -2

        return 0


    @conn_trans

    def fetchall(self, command="select name from xiaoqu", conn=None):

        cu = conn.cursor()

        lists = []

        try:

            cu.execute(command)

            lists = cu.fetchall()

        except Exception as e:

            print(e)

            pass

        return lists


同时,封装几个具体的操作。

生成小区数据库插入操作命令:

def gen_xiaoqu_insert_command(info_dict):

    """

    生成小区数据库插入命令

    """

    info_list = [ u'url',u'小区名称', u'大区域', u'小区域', u'建造时间'] 

    t = []

    for il in info_list:

        if il in info_dict:

            t.append(info_dict[il])

        else:

            t.append('')

    t = tuple(t)

    command = (r"insert into xiaoqu values(?,?,?,?,?)", t) #?,

    return command


生成成交记录数据库插入操作命令:

def gen_chengjiao_insert_command(info_dict):

    """

    生成成交记录数据库插入命令

    """

    info_list = [u'链接',u'小区名称',u'户型',u'面积',u'朝向',u'装修',u'电梯',u'签约时间',u'签约总价',u'楼层',u'年代楼型',u'来源',u'签约单价',u'税费',u'地铁',u'挂牌价',u'成交周期']

    t = []

    for il in info_list:

        if il in info_dict:

            t.append(info_dict[il])

        else:

            t.append('')

    t = tuple(t)

    command = (r"insert into chengjiao values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", t)

    return command


生成在售记录数据库插入操作命令:

def gen_zaishou_insert_command(info_dict):

    """

    生成在售记录数据库插入命令

    """

    info_list = [u'链接',u'房子描述',u'小区名称',u'户型',u'面积',u'朝向',u'装修',u'电梯',u'楼层年代楼型',u'区域',u'关注',u'带看',u'发布时间',u'税费',u'地铁',u'限制',u'挂牌价',u'挂牌单价']

    t = []

    for il in info_list:

        if il in info_dict:

            t.append(info_dict[il])

        else:

            t.append('')

    t = tuple(t)

    command = (r"insert into zaishou values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", t)

    return command


当然,数据库表需要创建:

command = "create table if not exists xiaoqu (url TEXT primary key UNIQUE,name TEXT, regionb TEXT, regions TEXT, year TEXT)"#

db_xq = SQLiteWraper('lianjia-xq.db', command)


command = "create table if not exists zaishou (href TEXT primary key UNIQUE, detail TEXT,xiaoqu TEXT, style TEXT, area TEXT, orientation TEXT, zhuangxiu TEXT, dianti TEXT, loucenniandai TEXT, quyu TEXT, guanzhu TEXT, daikan TEXT, fabushijian TEXT,shuifei TEXT,subway TEXT,xianzhi TEXT, total_price TEXT, unit_price TEXT)"

db_zs = SQLiteWraper('lianjia-zs.db', command)


command = "create table if not exists chengjiao (href TEXT primary key UNIQUE, name TEXT, style TEXT, area TEXT, orientation TEXT, zhuangxiu TEXT, dianti TEXT, sign_time TEXT, total_price TEXT, loucen TEXT, year TEXT,source TEXT, unit_price TEXT,shuifei TEXT, subway TEXT, guapaiprice TEXT, cycletime TEXT)"

db_cj = SQLiteWraper('lianjia-cj.db', command)


基础准备好了,接下来就是具体的爬取了。


04


区域URL爬取及异常处理


爬取大区域的URL:

def get_qu_url_spider():

    """

    爬取大区信息

    """

    url = u"https://nj.lianjia.com/xiaoqu/"

    try:

        req = urllib.request.Request(url, headers=hds[random.randint(0, len(hds) - 1)])

        source_code = urllib.request.urlopen(req, timeout=5).read()

        plain_text = source_code.decode('utf-8');

        soup = BeautifulSoup(plain_text,"html.parser")

    except (urllib.request.HTTPError, urllib.request.URLError) as e:

        print(e)

        return

    except Exception as e:

        print(e)

        return


    d=soup.find('div', {'data-role':"ershoufang"}).findAll('a');

    for item in d:

        href=item['href'];

        region = item.contents[0];

        fullhref=u"https://nj.lianjia.com"+href;

        print(region+fullhref);

        regions.append(region)

        regionurls.append(fullhref)


爬取过程中如果异常,写入异常日志:

def exception_write(fun_name,url):

    """

    写入异常信息到日志

    """

    lock.acquire()

    f = open('log.txt','a')

    line="%s %s " % (fun_name,url)

    f.write(line)

    f.close()

    lock.release()


爬取的URL不需要存入数据库,直接存到一个列表即可,然后遍历,获取各个大区域的小区信息。




在接下来将说明如何爬取小区信息、在售二手房信息、历史成交二手房信息,敬请期待。

640?wx_fmt=jpeg

长按进行关注。

原文地址:https://www.cnblogs.com/protosec/p/11673338.html