Python爬虫三:抓取链家已成交二手房信息(58W数据)

环境:Windows7+python3.6+Pycharm2017

目标:抓取链家北京地区已成交二手房信息(无需登录),如下图,户型、朝向、成交时间价格等,保存到csv。最后一共抓取约58W数据,程序运行8h。

-------其他案例: 美团爬虫京东爬虫微信公众号爬虫 

一、打开北京二手房网页https://bj.lianjia.com/ershoufang/,默认显示的是在售二手房信息,一共45634套,但是只显示了100页,每页30条,这3000条信息是没有任何反爬的,可以直接抓取,如果要抓取全部45634条,应该要按小区来。本文主要讨论已成交二手房信息,数据量更大,难度也要高一点。

 

二、点击页面右上角成交,切换到已成交二手房信息,显示一共有73W条数据,但是也只显示100页,每页30条共3000条信息。而且还有个问题就是近30天内成交的房源的成交时间、价格信息是不显示的。我们可以右键检查进入开发者模式,在网页的html代码中找到房源的详情页面的url,然后进入详情页面抓取成交时间、价格。

 

三、如何抓取尽可能多的房源信息

现在问题就是73W已成交二手房信息,怎么能尽可能多的抓下来。 办法就是这些房源通过分类来抓取,比如分不同区域,价格,小区,这样可以抓到更多的数据。本文选用按小区抓取。点击页面上方小区,进入如下页面,再点击返回全部小区列表。显示一共有11435个小区,虽然下面翻页只有30页,但是我们可以通过构造url来翻页,实测可以翻到100页,100页后都是重复的,共3000个小区。每页的url如下:

第2页:https://bj.lianjia.com/xiaoqu/pg2/

第3页:https://bj.lianjia.com/xiaoqu/pg3/

第100页:https://bj.lianjia.com/xiaoqu/pg100/

 

四、如何抓取每个小区的已成交二手房信息

点击某个小区如北京新天地,进入小区详情页面,下拉找到北京新天地小区成交记录,点击下面的查看全部成交记录,即可得到该小区全部已成交房源信息。通过左上角房源总数2133套,除以每页30套,我们可以得到该小区已成交房源一共有多少页。近30天内成交的进入详情页面抓取。观察页面的url  https://bj.lianjia.com/chengjiao/c1111027375945/ ,观察规律就是最后的一串数字是变化的,是每个小区的id。翻页的规律如下:

第二页:https://bj.lianjia.com/chengjiao/pg2c1111027375945/

第三页:https://bj.lianjia.com/chengjiao/pg3c1111027375945/

    所以我们的思路就是先抓取每个小区的id,然后构造小区成交房源页面的url,通过房源总数来得知该小区一共有多少页,翻页抓取。近30天内成交的需要进入详情页面抓取,其他的直接在列表页面就可以。

 

五、抓取小区id

一共100页,每页的url如下,也很简单,直接每个li标签中的data-id属性就是小区的id。注意的是该页面有对ip访问次数做限制,单ip连续访问好像是25页就会被封,所以需要采用代理ip,这里每个ip只抓取20页。还有一点需要注意的就是抓取的id需要做排重,此处用的set。还有就是对于第一次访问没有得到数据的页面需要再次访问。

https://bj.lianjia.com/xiaoqu/pg2/

https://bj.lianjia.com/xiaoqu/pg3/ 

 实际一共抓取到2990个id,保存到本地csv,代码如下。

import requests
from lxml import etree
import csv
import time
import json
#单线程抓取小区id前100页信息
def get_xiaoqu(x,p):
    head = {'Host': 'bj.lianjia.com',
            'Referer': 'https://bj.lianjia.com/chengjiao/',
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36'
            }
    n=x*20+1
    l=list(range(n,n+20))
    for i in l:
        url = 'https://bj.lianjia.com/xiaoqu/pg' + str(i)
        try:
            r = requests.get(url, headers=head, proxies=p, timeout=3)
            html = etree.HTML(r.text)
            datas=html.xpath('//li[@class="clear xiaoquListItem"]/@data-id')
            title=html.xpath('//li[@class="clear xiaoquListItem"]/div[@class="info"]/div[@class="title"]/a/text()')
            print('No:' + str(x), 'page:' + str(i), len(s), len(datas), len(title))
            #如果当前页没有返回数据,将当前页数加到列表l末尾,再次抓取
            if len(datas)==0:
                print(url)
                l.append(i)
            else:
                for data in datas:
                    s.add(data)
        # 如果当前页访问出现异常,将当前页数加到列表l末尾,再次抓取
        except Exception as e:
            l.append(i)
            print(e)

    print('      ****No:'+str(x)+' finish')
#本人购买的代理获取方式,需要根据你们自己的修改。函数功能获取n个ip,并以列表形式返回,每个元素为字典:{'https':'https://118.120.228.202:4286'}
def get_ip(n):
    url='XXXXXXXXXXXXXXXXXXXXXXXX'
    r=requests.get(url)
    html=json.loads(r.text)
    proxies=[]
    for i in range(n):
        a=html['data'][i]['ip']
        b=html['data'][i]['port']
        val='https://'+str(a)+':'+str(b)
        p={'https':val}
        proxies.append(p)
    return(proxies)

if __name__=='__main__':
    global s
    #将id保存在set中,达到排重效果
    s = set()
    #该页面网站会禁ip,所以每个ip只访问20页
    for x in range(5):
        now=time.time()
        ls = get_ip(1)
        p=ls[0]
        get_xiaoqu(x,p)
        print(time.time()-now)
    print('******************')
    print('抓取完成')
    #将抓取的id保存到本地csv
    with open('xiaoqu_id.csv', 'a', newline='', encoding='gb18030')as f:
        write = csv.writer(f)
        for data in s:
            write.writerow([data])
        f.close()







 

六、对每个小区已成交房源信息进行抓取 

本文没有开多线程(开5个线程跑了几分钟好像也没遇到问题),也没遇到封ip,就没加代理。逻辑很简单,parse_xiaoqu(url,pa) 函数用于爬取小区具体一页的房源信息,先抓取第一页数据,获取房源信息的同时获得该小区房源总数,然后确定该小区一共有多少页。然后就是对每一页调用parse_xiaoqu(url,pa)进行抓取。主要注意点有:

1、对于第一次抓取失败的页面,包括timeout这种异常和无异常但是返回0条房源信息两种情况,都需要对这些页面进行第二次的抓取。parse_xiaoqu(url,pa)返回值中有一个1或0就是用以标记本次抓取是否成功。第一次抓取失败的,第二次抓取成功的数据还挺多的。

2、爬取过程中遇到报错中断,可以通过已经抓取的小区数量,修改range(0,2990)函数的第一个参数达到断点后续抓。

代码如下,代码应该是把小区id导入就可以直接运行的。

import requests
from lxml import etree
import csv
import time
import threading
#小区具体一页房源信息的抓取,输入为当前页面url,当前爬取页数pa。返回数据为小区房源总数num,该页抓取的房源信息home_list,状态码1或0(1表示成功)
def parse_xiaoqu(url,pa):
    head = {'Host': 'bj.lianjia.com',
            'Referer': 'https://bj.lianjia.com/chengjiao/',
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36'

            }
    r = requests.get(url, headers=head,timeout=5)
    html = etree.HTML(r.text)
    num = html.xpath('//div[@class="content"]//div[@class="total fl"]/span/text()')[0]
    num = int(num)
    datas = html.xpath('//li/div[@class="info"]')
    print('小区房源总数:', num,'第%d页房源数:'%pa,len(datas))
    print(url)
    if len(datas)==0:
        return(num,[],0)   #服务器无返回数据,状态码返回0
    house_list=[]
    for html1 in datas:
        title = html1.xpath('div[@class="title"]/a/text()')
        info = html1.xpath('div[@class="address"]/div[@class="houseInfo"]/text()')
        floor = html1.xpath('div[@class="flood"]/div[@class="positionInfo"]/text()')
        info[0] = info[0].replace('\xa0','')  #该条信息中有个html语言的空格符号 需要去掉,不然gbk编码会报错,gb18030显示问号
        date = html1.xpath('div[@class="address"]/div[@class="dealDate"]/text()')
        #30天内成交的进入详情页面抓取
        if date[0] == '近30天内成交':
            p_url = html1.xpath('div[@class="title"]/a/@href')
            r = requests.get(p_url[0], headers=head,timeout=5)
            html = etree.HTML(r.text)
            price = html.xpath('//div[@class="overview"]/div[@class="info fr"]/div[@class="price"]/span/i/text()')
            unitprice = html.xpath('//div[@class="overview"]/div[@class="info fr"]/div[@class="price"]/b/text()')
            date = html.xpath('//div[@class="house-title LOGVIEWDATA LOGVIEW"]/div[@class="wrapper"]/span/text()')
            #有的房源信息没有价格信息,显示暂无价格
            if len(price)==0:
                price.append('暂无价格')
            if len(unitprice)==0:
                unitprice.append('暂无单价')
            date[0] = date[0].replace('链家成交', '')
            a = [title[0], info[0], floor[0], date[0], price[0], unitprice[0]]
            house_list.append(a)
            print(title[0], info[0], floor[0], date[0], price[0], unitprice[0])
        else:
            price = html1.xpath('div[@class="address"]/div[@class="totalPrice"]/span/text()')
            unitprice = html1.xpath('div[@class="flood"]/div[@class="unitPrice"]/span/text()')
            if len(price) == 0:
                price = ['暂无价格']
            if len(unitprice) == 0:
                unitprice = ['暂无单价']
            a = [title[0], info[0], floor[0], date[0], price[0], unitprice[0]]
            house_list.append(a)
            print(title[0], info[0], floor[0], date[0], price[0], unitprice[0])
    print('                *********************         ','第%d页完成!'%pa)
    return (num,house_list,1)

#抓取某小区所有已成交二手房信息,排重后存入本地csv,输入为小区id,返回抓取到的该小区的房源总数
def crow_xiaoqu(id):
    url='https://bj.lianjia.com/chengjiao/c%d/'%int(id)
    h_list=[]      #保存该小区抓取的所有房源信息
    fail_list=[]   #保存第一次抓取失败的页数,第一遍抓取完成后对这些页数再次抓取
    try:
        #爬取小区第一页信息
        result=parse_xiaoqu(url,1)
    except:
        #如果第一页信息第一次爬取失败,sleep2秒再次爬取
        time.sleep(2)
        result=parse_xiaoqu(url,1)
    #获取该小区房源总数num
    num = result[0]
    #如果无数据返回,sleep2秒再爬取一次
    if num == 0:
        time.sleep(2)
        result=parse_xiaoqu(url,1)
        num = result[0]
    new_list = result[1]
    pages=1
    for data in new_list:
        if data not in h_list:
            h_list.append(data)
    # 确定当前小区房源页数pages
    if num > 30:
        if num % 30 == 0:
            pages = num // 30
        else:
            pages = num // 30 + 1
    for pa in range(2,pages+1):
        new_url = 'https://bj.lianjia.com/chengjiao/pg'+str(pa)+'c'+str(id)
        try:
            result=parse_xiaoqu(new_url,pa)
            status=result[2]
            if status==1:
                new_list=result[1]
                #排重后存入h_list
                for data in new_list:
                    if data not in h_list:
                        h_list.append(data)
            else:
                fail_list.append(pa)
        except Exception as e:
            fail_list.append(pa)
            print(e)
    print('   开始抓取第一次失败页面')
    for pa in fail_list:
        new_url = 'https://bj.lianjia.com/chengjiao/pg' + str(pa) + 'c' + str(id)
        print(new_url)
        try:
            result = parse_xiaoqu(new_url,pa)
            status = result[2]
            if status == 1:
                new_list = result[1]
                for data in new_list:
                    if data not in h_list:
                        h_list.append(data)
            else:
                pass
        except Exception as e:
            print(e)
    print('    抓取完成,开始保存数据')
    #一个小区的数据全部抓完后存入csv
    with open('lianjia_123.csv','a',newline='',encoding='gb18030')as f:
        write=csv.writer(f)
        for data in h_list:
            write.writerow(data)
    #返回抓取到的该小区房源总数
    return(len(h_list))
if __name__=='__main__':
    counts=0    #记录爬取到的房源总数
    now=time.time()
    id_list=[]
    with open('xiaoqu_id.csv','r')as f:
        read=csv.reader(f)
        for id in read:
            id_list.append(id[0])
    m=0
    #可以通过修改range函数的起始参数来达到断点续抓
    for x in range(0,2990):
        m+=1    #记录一共抓取了多少个小区
        print('    开始抓取第'+str(m)+'个小区')
        time.sleep(1)
        count=crow_xiaoqu(id_list[x])
        counts=counts+count
        #打印已经抓取的小区数量,房源数量,所花的时间
        print('     已经抓取'+str(m)+'个小区  '+str(counts)+'条房源信息',time.time()-now)
   

七、总结

    一共抓取了 58W条数据,程序跑了8h,速度一般。主要缺点就是程序不够顽强,中途中断了很多次,需要人工的再次启动,修改range参数才能断点续抓。还有就是1.1W个小区也就抓了3000个,但是从房源数据来看73W数据抓了58w,大概80%。

水平有限,希望大家指正。

-------其他案例: 美团爬虫京东爬虫微信公众号爬虫 

欢迎关注个人公众号,更多案例持续更新! 

猜你喜欢

转载自blog.csdn.net/xing851483876/article/details/81408281
今日推荐