欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

动态爬取链家二手房成交记录并保存至Excel

程序员文章站 2022-03-07 19:49:06
...

 

一、先观察网页结构

链家成交记录网址:https://bj.lianjia.com/chengjiao/

每页有30条成交记录,点击记录提示要下载APP才能查看详细信息。不管它,我们直接审查元素,找到成交记录的链接,点击打开。如下图

                                     动态爬取链家二手房成交记录并保存至Excel

链接后面有一串数字,应该是这个成交记录的id号,由于记录每日更新,我们每次爬取完成之后用一个txt文本保存最新记录的id号,以便下次准确定位爬取结束的位置。

接下来回到成交记录页面,观察其翻页时网址的变化:

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

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

地址后加上:/pg+页数 就可以翻页了。

然后在详细信息页面观察要爬取的内容,如图:

动态爬取链家二手房成交记录并保存至Excel

包括基本属性、交易属性、成交额、成交单价和成交日期,全部爬取。

接下来老规矩,审查元素,找到这些内容所在的标签:

动态爬取链家二手房成交记录并保存至Excel

动态爬取链家二手房成交记录并保存至Excel

动态爬取链家二手房成交记录并保存至Excel

标签位置已经清楚,说一下爬取的基本思路:

先获取30个记录的链接,然后依次爬取信息,最后打开下一页,循环往复。

这是为了在第一次爬取的时候(有100页),一旦出现异常,可以将已爬取的先保存。

二、代码解析

需要的包有numpy,pandas,BeautifulSoup,re,urllib等

代码解析如下:

1、getbsobj函数

def getbsobj(url):
    try:
        html = urlopen(url,timeout=3)
    except (HTTPError,socket.timeout):
        return None
    return BeautifulSoup(html,'html.parser')

定义一个返回bs对象的函数,包括一些异常处理。

2、getLinksList函数

def getLinksList(url,n):
    '''

    :param url: 链接
    :param n: 第n页
    :return: 链接列表
    '''
    ls = []
    bsobj=getbsobj(url+'/pg%d'%n)
    if not bsobj:
        print('该页打不开')
        return None
    while True:
        aTagList = bsobj.find('ul', {'class': 'listContent'}).findAll('a', {'class': 'img'})
        if len(aTagList)>0:
            print('打开成功')
            break
        else:
            print('进入休眠')
            time.sleep(60)
            bsobj=getbsobj(url+'/pg%d'%n)
    for aTag in aTagList:
        if 'href' in aTag.attrs:
            ls.append(aTag['href'])
    return ls

定义获取30条成交记录链接的函数,以列表形式返回。

链接在ul标签下的a标签中。

中间有一个循环,原因是有的时候网页能打开,但是成交记录却没有显示,这个时候需要尝试重新打开,直到出现成交记录为止。没打开可能是因为访问过于频繁,所以打不开的时候进入休眠,60秒。

3、getInfo函数

infoArray = []

一条记录的信息由一个一维列表保存,而infoArray是保存获取的所有记录信息的二维列表。

tags = bsobj.find('div',{'class':'introContent'}).findAll('li')

获取保存基本属性和交易属性内容的标签列表。

info = list()
info.append(bsobj.head.title.get_text().split()[0])

info是保存一条记录的所有信息的临时列表。

这里添加的是小区名字。

for tag in tags:
     info.append(tag.get_text().strip()[4:])

这里添加的是基本属性和交易属性的内容

ul = bsobj.find('ul',{'class':'record_list'})
info.append(ul.li.span.get_text().strip())
text = ul.li.p.get_text().strip()
a = re.search(r'\d+元/平',text)
if a ==None:
    info.append('无')
else:
    info.append(a.group()[:-3])
b = re.search(r'\d+-\d+-\d+', text)
if b == None:
    info.append(text)
else:
    info.append(b.group())

这里添加的是成交额、单价和成交日期,因为和上面的属性不在一起,需要另外处理。

infoArray.append(info)

添加一条记录

arr = np.array(infoArray)

将最终的infoArray转换成矩阵arr,并返回。

4、checkid函数

在动态爬取的时候检查该id是否已经获取,若已获取,那么说明已经爬取到上一次运行时的最新记录,此时已经无需继续爬取。

def checkid(ls,id):
    '''

    :param ls: 链接列表
    :param id: 成交id
    :return: bool
    '''
    links = []
    tag = False
    for lk in ls:
        if lk[-17:-5]!=str(id):
            links.append(lk)
        else:break
    if len(links)!=len(ls):
        tag = True
    ls = links
    return tag,ls

这里对列表中的每一条链接进行检查,若该id与上一次保存的id相同,则该链接及其之后的链接全部丢弃,返回一个bool值和处理后的列表。

5、getIndex函数

用于获取保存至Excel时需要的属性标签

def getIndex(url):
    cols = ['小区名字']
    ls = getLinksList(url, 1)
    bs = getbsobj(ls[0])
    tags = bs.find('div', {'class': 'introContent'}).findAll('li')
    for tag in tags:
        cols.append(tag.span.get_text().strip())
    cols += ['成交额(万元)','单价(元/平)','日期']
    new_id = ls[0][-17:-5]
    return cols,new_id

6、download函数

主函数

def download(url,start,end):

从第start页开始,爬取到end页为止(包括end页)

cols,new_id = getIndex(url)

获取属性列表和最新记录的id号 

ls = getLinksList(url,i)

 

获取链接列表

tag,ls = checkid(ls,id)

检查id

arr=getInfo(ls)

获取数据矩阵

df_new = pd.DataFrame(arr,columns=cols)
df = df.append(df_new,ignore_index=True)

转化为DataFrame格式。

df_old = pd.read_excel('链家成交数据.xlsx')
df_old = df.append(df_old, ignore_index=True)
df_old.to_excel('链家成交数据.xlsx')

保存为Excel。

三、输出结果

动态爬取链家二手房成交记录并保存至Excel

动态爬取链家二手房成交记录并保存至Excel

四、完整代码

from bs4 import BeautifulSoup
from urllib.request import urlopen
from urllib.error import HTTPError
import pandas as pd
import numpy as np
import re
import socket,time

url = 'https://bj.lianjia.com/chengjiao'

def getbsobj(url):
    try:
        html = urlopen(url,timeout=3)
    except (HTTPError,socket.timeout):
        return None
    return BeautifulSoup(html,'html.parser')

def getLinksList(url,n):
    '''

    :param url: 链接
    :param n: 第n页
    :return: 链接列表
    '''
    ls = []
    bsobj=getbsobj(url+'/pg%d'%n)
    if not bsobj:
        print('该页打不开')
        return None
    while True:
        aTagList = bsobj.find('ul', {'class': 'listContent'}).findAll('a', {'class': 'img'})
        if len(aTagList)>0:
            print('打开成功')
            break
        else:
            print('进入休眠')
            time.sleep(60)
            bsobj=getbsobj(url+'/pg%d'%n)
    for aTag in aTagList:
        if 'href' in aTag.attrs:
            ls.append(aTag['href'])
    return ls

def getInfo(ls):
    '''

    :param ls:链接列表
    :return: 数组
    '''
    infoArray = []
    i = 1
    for lk in ls:
        print('正在获取第%d条信息'%i)
        bsobj = getbsobj(lk)
        if not bsobj:continue
        tags = bsobj.find('div',{'class':'introContent'}).findAll('li')
        info = list()
        info.append(bsobj.head.title.get_text().split()[0])
        for tag in tags:
            info.append(tag.get_text().strip()[4:])
        ul = bsobj.find('ul',{'class':'record_list'})
        info.append(ul.li.span.get_text().strip())
        text = ul.li.p.get_text().strip()
        a = re.search(r'\d+元/平',text)
        if a ==None:
            info.append('无')
        else:
            info.append(a.group()[:-3])
        b = re.search(r'\d+-\d+-\d+', text)
        if b == None:
            info.append(text)
        else:
            info.append(b.group())
        infoArray.append(info)
        i += 1
    print('-'*20)
    arr = np.array(infoArray)
    return arr

def checkid(ls,id):
    '''

    :param ls: 链接列表
    :param id: 成交id
    :return: bool
    '''
    links = []
    tag = False
    for lk in ls:
        if lk[-17:-5]!=str(id):
            links.append(lk)
        else:break
    if len(links)!=len(ls):
        tag = True
    ls = links
    return tag,ls

def getIndex(url):
    cols = ['小区名字']
    ls = getLinksList(url, 1)
    bs = getbsobj(ls[0])
    tags = bs.find('div', {'class': 'introContent'}).findAll('li')
    for tag in tags:
        cols.append(tag.span.get_text().strip())
    cols += ['成交额(万元)','单价(元/平)','日期']
    new_id = ls[0][-17:-5]
    return cols,new_id

def download(url,start,end):
    i = start
    fn = open('id.txt','r')
    id = fn.readline()
    fn.close()
    cols,new_id = getIndex(url)
    df = pd.DataFrame()

    try:
        while True:
            print('正在获取第%d页链接'%i)
            ls = getLinksList(url,i)
            if not ls:
                print(ls)
                print(1)
                break
            #print(ls)
            tag,ls = checkid(ls,id)
            #print(ls)
            arr=getInfo(ls)
            df_new = pd.DataFrame(arr,columns=cols)
            df = df.append(df_new,ignore_index=True)

            if tag:
                print(2)
                break
            i += 1
            if i>end:
                print(3)
                break
    except Exception as e:
        print(e)
    else:
        df_old = pd.read_excel('链家成交数据.xlsx')
        df_old = df.append(df_old, ignore_index=True)
        df_old.to_excel('链家成交数据.xlsx')
        fn = open('id.txt', 'w')
        fn.write(new_id)
        fn.close()
        print('程序执行完毕!')





if __name__ == '__main__':
    download(url,1,20)

       进行第一次爬取的时候,上述代码需要稍作修改,try的后面加个finally,将已爬取的内容保存,防止出现异常带来的损失。之后的爬取就可以用上述代码了。根据观察,链家数据每天的更新大约有3至16页不等,因此download函数参数取1和20。

        正常结束应该是先print(2),然后print(程序执行完毕),假如某天更新了21页,那么会先print(3),然后print(程序执行完毕),这时需要修改参数再次执行,当然,也可以直接把参数20改为100,这样除非出现莫名异常,一般都可以正常执行完。

相关标签: 爬虫