【Python3】南京链家二手房信息采集

写在前面的话

本文参考的信息如下:

【房价网房价信息爬虫】整站40万条房价数据并行抓取,可更换抓取城市

python3 爬虫教学之爬取链家二手房(最下面源码) //以更新源码

关于爬虫的初体验视频可以参考Python网络爬虫实战

重复的内容可以参考以上信息,在此根据个人理解写下这篇文章,记录个人爬虫的过程,做个备忘

一、获取二手房信息索引

1.1 网页信息提取

包装请求request,设置超时timeout

# 获取列表页面
def get_page(url):
    # 两种设置headers的方法,方法一:
    # headers = {
    #     'User-Agent':"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 "
    #                     "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
    #     # 'Referer': r'https://nj.lianjia.com/ershoufang/',
    #     # 'Host': r'nj.lianjia.com',
    #     # 'Connection': 'keep-alive'
    # }
    # 方法二:使用fake-useragent第三方库
    headers = {'User-Agent': UserAgent().random}
    timeout = 60
    socket.setdefaulttimeout(timeout)  # 设置超时,设置socket层的超时时间为60秒
    try:
        req = request.Request(url, headers=headers)
        response = request.urlopen(req)
        page = response.read().decode('utf-8')
        response.close()  # 注意关闭response
    except error.URLError as e:
        print(e.reason)
    time.sleep(1)  # 自定义,设置sleep()等待一段时间后继续下面的操作
    return page

这里使用第三方库fake-useragent设置headers比较方便,用法见fake-useragent库:值得花2分钟学习的库

1.2 街道信息提取

Image 1.jpg

将所属位置地铁信息,添加至search_dict中。->{'区域': {'鼓楼': {'草场门大街': 'https://nj.lianjia.com/ershoufang/caochangmendajie/', '定淮门大街': 'https://nj.lianjia.com/ershoufang/dinghuaimendajie/',...}}},输出为每个街道及其对应的URL地址

# 获取查询关键词key所包含的dict
def get_search(url,page, key):
    soup = BS(page, 'lxml')
    all_a = soup.find("div", {"data-role":key}).find_all("a") # 提取所有区域/地铁信息
    temp_list={}
    temp_url=[]
    all_url = []  # 唯一网页链接
    repeat_url=[] # 重复的网页
    for temp in all_a:
        temp_list[temp.get_text()]={}
        temp_url.append(url+temp['href'])
        temp_page=get_page(url+temp['href'])
        temp_soup = BS(temp_page, 'lxml')
        temp_a = temp_soup.find("div", {"data-role": key}).find_all("div")[1].find_all("a")
        # 提取所有区域/地铁信息下的区域信息
        for temp_i in temp_a:
            if (url+temp_i['href']) not in all_url:
                all_url.append(url+temp_i['href'])
                temp_list[temp.get_text()][temp_i.get_text()]=url+temp_i['href']
            else:
                repeat_url.append(url + temp_i['href'])
                print('区域URL重复')
    return temp_list

定义的get_search函数根据关键词key来得到区域下每条街道和每条地铁线经过的街道的URL链接

Image 1.jpg

key值取为'/ershoufang/'和'/ditiefang/'

得到的search_dict为字典格式不方便操作,将其转换为列表形式,输出[['区域', '鼓楼', '草场门大街', 'https://nj.lianjia.com/ershoufang/caochangmendajie/'], ['区域', '鼓楼', '定淮门大街', 'https://nj.lianjia.com/ershoufang/dinghuaimendajie/'],...]得到每条街道的URL地址信息,数据格式为列表

# 获取房源信息列表(嵌套字典遍历)
def get_info_list(search_dict, layer, tmp_list, search_list):
    layer += 1  # 设置字典层级
    for i in range(len(search_dict)):
        tmp_key = list(search_dict.keys())[i]  # 提取当前字典层级key
        tmp_list.append(tmp_key)   # 将当前key值作为索引添加至tmp_list
        tmp_value = search_dict[tmp_key]
        if isinstance(tmp_value, str):   # 当键值为url时
            tmp_list.append(tmp_value)   # 将url添加至tmp_list
            search_list.append(copy.deepcopy(tmp_list))   # 将tmp_list索引url添加至search_list
            tmp_list = tmp_list[:layer]  # 根据层级保留索引
        elif tmp_value == '':   # 键值为空时跳过
            layer -= 2           # 跳出键值层级
            tmp_list = tmp_list[:layer]   # 根据层级保留索引
        else:
            get_info_list(tmp_value, layer, tmp_list, search_list)  # 当键值为列表时,迭代遍历
            tmp_list = tmp_list[:layer]
    return search_list

1.3 二手房网页提取

该部分分为两个两个小部分,第一部分先提取总页数以及每页所对应的URL链接,第二部分则根据每页的URL链接提取二手房网页信息

第一部分

得到街道的URL信息之后,考虑获取每条街道下的所有二手房信息,首先获取二手房的URL,首先观察一下每条街道的二手房情况,我们选取草场门大街作为观察的样本,可见每条街所对应的二手房信息不止一页

Image 1.jpg

# 获取当前索引页面页数的url列表
def get_info_pn_list(url,search_list):
    fin_search_list = []
    for i in range(len(search_list)):
        print('>>>正在抓取%s每页URL地址' % search_list[i][:3])
        search_url = search_list[i][3]
        try:
            page = get_page(search_url)
        except:
            print('获取页面超时')
            continue
        soup = BS(page, 'lxml')
        # 组装URL
        fin_search_list.append(copy.deepcopy(search_list[i][:3]))
        # 方法一:
       # 获取最大页数
        if soup.find('div', class_='page-box house-lst-page-box'):
            pn_num = json.loads(soup.find('div', class_='page-box house-lst-page-box')['page-data'])  # 获取最大页数以及当前页
            max_pn = pn_num['totalPage']  # 获取最大页数,'curPage'存放当前页
            page_url = soup.find('div', class_='page-box house-lst-page-box')['page-url']
            for page in range(1, max_pn + 1):
                fin_search_list[i].append(url + page_url.replace('{page}', str(page)))
        # 方法二:
        # all_a = soup.find('div', class_="pagination_group_a").find_all('a')
        # if all_a:
        #     for a in all_a:
        #         fin_search_list.append(url + a['href'])
    return fin_search_list

获取总页数有两种方法:
方法一:


1.jpg

方法二:


1.jpg

得到的结果为如下:
1.jpg

fin_info_pn_list=[['区域', '鼓楼', '草场门大街', 'https://nj.lianjia.com/ershoufang/caochangmendajie/pg1/', 'https://nj.lianjia.com/ershoufang/caochangmendajie/pg2/',...],['区域', '鼓楼', '定淮门大街', 'https://nj.lianjia.com/ershoufang/dinghuaimendajie/pg1/',...],...]
获得页数之后就可以提取每页的二手房信息了,首先获取二手房的URL,

第二部分

#根据每页链接获取该区域所有二手房网址
def get_url_page(fin_search_list):
    fin_info_list = []
    global global_all_url,global_repeat_url
    for i in range(len(fin_search_list)):
        fin_info_list.append(fin_search_list[i][:3])
        for j in range(3,len(fin_search_list[i])):
            print('>>>正在抓取%s第%d页的URL地址' % (fin_search_list[i][:3],j-2))
            url = fin_search_list[i][j]
            try:
                page = get_page(url)
            except:
                print('获取tag超时')
                continue
            soup = BS(page, 'lxml')
            all_li = soup.find('ul', class_='sellListContent').find_all('li')
            print(soup)
            input()
            for li in all_li:
                temp_url = li.find_all('a')[1]
                if temp_url['href'] not in global_all_url:
                    fin_info_list[i].append(temp_url['href']) # 储存区域对应的URL
                    global_all_url.append(temp_url['href'])
                else:
                    global_repeat_url.append(temp_url['href'])
                    print('区域二手房URL重复')
    return fin_info_list

1.jpg

得到每条街道的所有二手房信息:
fin_url_list=[['区域', '鼓楼', '草场门大街', 'https://nj.lianjia.com/ershoufang/103101164368.html', 'https://nj.lianjia.com/ershoufang/103102024868.html',...]]
1.jpg

详细的二手房信息根据fin_url_list所获取的URL进行获取,
自此已获取每个区域每条街道所发布的所有URL信息,但是这个方法有些复杂,简单的方法可以通过python3 爬虫教学之爬取链家二手房(最下面源码) //以更新源码所介绍的进行操作。
接下来可以根据二手房信息的URL索引进行相关信息的提取操作。
global_all_url,global_repeat_url为两个全局变量,一个用于存放全局唯一的二手房URL链接,另一个用于存放重复的URL链接。global_all_url与fin_url_list是等价的

二、链接二手房信息采集

二手房的信息比较多,可以酌情考虑进行采集,从宁工新寓 龙江地铁口 配套齐全 采光充足信息的页面可以看出,该信息有标题、总价、均价、地址、小区、面积、经纪人、户型...等等。采集的内容非常多,

# 获取tag信息
def get_info(fin_search_list):
    print('信息采集开始')
    fin_info_list = {}
    global global_num,base_url
    for url in fin_search_list:
        print('正在获取%s房源信息...' % url)
        fin_info_list[global_num]={}
        try:
            page = get_page(url)
        except:
            print('获取tag超时')
            continue
        soup = BS(page, 'lxml')
        temp=soup.select('.areaName')[0].text.split('\xa0')
        fin_info_list[global_num]['区域']=temp[0].strip('所在区域') # 区域名
        fin_info_list[global_num]['街道'] = temp[1] # 街道名
        if temp[2]:
            fin_info_list[global_num]['位置信息补充']=temp[2]
        fin_info_list[global_num]['小区']=soup.select('.communityName')[0].text.strip('小区名称').strip('地图')
        # 社区名
        communityUrl = base_url+soup.find('div',class_='communityName').find('a')['href']
        fin_info_list[global_num]['小区URL'] = communityUrl
        #
        # 以下获取租房所在经纬度
        # 方法一:通过百度地图获得
        # address = soup.find('div', class_='fl l-txt').find('a').text.strip('链家网').strip('站')
        # address = address + fin_info_list[global_num]['areaName'] + fin_info_list[global_num]['streetName'] + \
        #           fin_info_list[global_num]['communityName']
        # address为小区地址
        # fin_info_list[global_num]['longitude'] = getlnglat(address)['lng']  # 经度
        # fin_info_list[global_num]['latitude'] = getlnglat(address)['lat']  # 维度
        fin_info_list[global_num]['租房网址']=url # 租房网址
        fin_info_list[global_num]['租房信息标题'] = soup.select('.main')[0].text # 标题
        fin_info_list[global_num]['房屋总价'] = soup.select('.total')[0].text + '万' # 总价
        fin_info_list[global_num]['每平方单价']=soup.select('.unitPriceValue')[0].text # 单价
        fin_info_list[global_num]['首付'] = soup.find('div', class_='tax').find('span').text.split()[0].strip('首付') # 首付
        fin_info_list[global_num]['税费'] = \
        soup.find('div', class_='tax').find('span').text.split()[1].strip('税费').strip('(仅供参考)') # 税费
        # 户型信息,此信息差异较大,不提取
        # temp = soup.find('div', id='infoList').find_all('div', class_='col')
        # for i in range(0, len(temp), 4):
        #     fin_info_list[global_num][temp[i].text] = {}
        #     fin_info_list[global_num][temp[i].text]['面积'] = temp[i + 1].text
        #     fin_info_list[global_num][temp[i].text]['朝向'] = temp[i + 2].text
        #     fin_info_list[global_num][temp[i].text]['窗户'] = temp[i + 3].text

        hid = soup.select('.houseRecord')[0].text.strip('链家编号').strip('举报')
        fin_info_list[global_num]['链家编号'] = hid
        # hid:链家编号
        rid = soup.find('div', class_='communityName').find('a')['href'].strip('/xiaoqu/').strip('/')
        fin_info_list[global_num]['社区ID'] = rid
        # rid:社区ID
        fin_info_list[global_num]['房屋建造年代'] = soup.find('div', class_='area').find('div',
                                                                                    class_='subInfo').get_text()  # 建造年代
        fin_info_list[global_num]['房屋朝向'] = soup.find('div', class_='type').find('div',
                                                                                     class_='mainInfo').get_text()  # 朝向

        # 提取基本信息,包括基本属性和交易属性:
        all_li = soup.find('div', class_='introContent').find_all('li')
        for li in all_li:
            fin_info_list[global_num][li.find('span').text] = li.text.split(li.find('span').text)[1]
        # 经纪人信息,该信息有可能为空:
        if soup.find('div', class_='brokerInfoText fr'):
            # 姓名
            fin_info_list[global_num]['经纪人姓名'] = soup.find('div', class_='brokerInfoText fr').find('a',
                                                                                                        target='_blank').text
            temp = soup.find('div', class_='brokerInfoText fr').find('span', class_='tag first')
            fin_info_list[global_num]['经纪人评价URL']=temp.find('a')['href']
            # 评分
            fin_info_list[global_num]['评分'] = temp.text.split('/')[0].strip('评分:')
            # 评价人数
            fin_info_list[global_num]['评价人数'] = temp.text.split('/')[1].strip('人评价')
            # 联系电话,输出结果:'phone': '4008896039转8120'
            fin_info_list[global_num]['经纪人电话'] = soup.find('div', class_='brokerInfoText fr').find('div',
                                                                                               class_='phone').text
            soup2 = BS(get_page(communityUrl), 'lxml')
            CommunityInfo = soup2.find_all('div', class_='xiaoquInfoItem')
        else:
            print('无经纪人信息')
        # 以下为获取小区简介信息
        for temp in CommunityInfo:
            fin_info_list[global_num][temp.find('span', class_='xiaoquInfoLabel').text] = \
                temp.find('span',class_='xiaoquInfoContent').text
            # 获取小区经纬度的第二种方法
            if temp.find('span', class_='actshowMap'):
                lng_lat = temp.find('span', class_='actshowMap')['xiaoqu'].lstrip('[').rstrip(']').split(',')
                # 附近门店的经纬度信息需要替换为temp.find('span', class_='actshowMap')['mendian'].split(',')
                fin_info_list[global_num]['经度'] = lng_lat[0]
                fin_info_list[global_num]['纬度'] = lng_lat[1]
        fin_info_list[global_num]['小区均价'] = soup2.find('span', class_='xiaoquUnitPrice').text + '元/㎡'
        global_num=global_num+1
    return fin_info_list

获取小区经纬度的方法有很多种,可以根据提取网页自身携带的经纬度信息提取,也可以通过百度地图获取,下面介绍通过百度地图获取经纬度的方法,代码如下:

# 获取经纬度信息
def getlnglat(address):
    url = 'http://api.map.baidu.com/geocoder/v2/'
    output = 'json'
    ak = '请输入你申请的密钥'
    add = quote(address) #由于本文城市变量为中文,为防止乱码,先用quote进行编码
    uri = url + '?' + 'address=' + add + '&output=' + output + '&ak=' + ak
    req = urlopen(uri)
    res = req.read().decode() #将其他编码的字符串解码成unicode
    temp = json.loads(res) #对json数据进行解析
    return temp['result']['location'] # 返回经纬度信息

不知道百度地图怎么用的可以参考python利用百度API进行地理编码(将地名转换为经纬度信息)这篇文章。
第二种方法则根据小区的详情页面获取,但是这里获取的只是小区的经纬度,不能具体到哪栋楼,没有百度地图方便灵活(PS:百度地图我这里也只是根据小区位置查找,暂时没有具体到哪栋楼)

三、数据保存

数据保存的方法比较简单,因为提取的信息用的字典结构,因此通过pandas保存为CSV文件比较方便。

HouseData=get_info(global_all_url,3)
    df = pd.DataFrame(HouseData).T # 转置,行表示每套房的基本信息,列表示每套房屋
    df.to_csv('HouseInfoData.csv',encoding='utf-8_sig') # 通过encoding解决保存中文乱码问题

四、完整代码

因为信息量太大,本次选择两条街道的二手房信息进行采集,限定一下fin_info_list的取值范围
fin_info_list=[['区域', '鼓楼', '草场门大街', 'https://nj.lianjia.com/ershoufang/caochangmendajie/'],
['区域', '鼓楼', '定淮门大街', 'https://nj.lianjia.com/ershoufang/dinghuaimendajie/']]# for test

#! -*-coding:utf-8-*-
# Function: 链接房价调查
# Author:haffner2010
# Environment:Windows 7 64 + Python3.6 + Pycharm

# 原贴:https://www.cnblogs.com/Lands-ljk/archive/2016/05/06/5467236.html

from urllib import request,error
from bs4 import BeautifulSoup as BS
from fake_useragent import UserAgent
import json
import pandas as pd
import socket
import copy
import time


# 获取列表页面
def get_page(url):
    # 两种设置headers的方法,方法一:
    # headers = {
    #     'User-Agent':"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 "
    #                     "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
    #     # 'Referer': r'https://nj.lianjia.com/ershoufang/',
    #     # 'Host': r'nj.lianjia.com',
    #     # 'Connection': 'keep-alive'
    # }
    # 方法二:使用fake-useragent第三方库
    headers = {'User-Agent': UserAgent().random}
    timeout = 60
    socket.setdefaulttimeout(timeout)  # 设置超时,设置socket层的超时时间为60秒
    try:
        req = request.Request(url, headers=headers)
        response = request.urlopen(req)
        page = response.read().decode('utf-8')
        response.close()  # 注意关闭response
    except error.URLError as e:
        print(e.reason)
    time.sleep(1)  # 自定义,设置sleep()等待一段时间后继续下面的操作
    return page


# 获取房源信息列表(嵌套字典遍历)
def get_info_list(search_dict, layer, tmp_list, search_list):
    layer += 1  # 设置字典层级
    for i in range(len(search_dict)):
        tmp_key = list(search_dict.keys())[i]  # 提取当前字典层级key
        tmp_list.append(tmp_key)   # 将当前key值作为索引添加至tmp_list
        tmp_value = search_dict[tmp_key]
        if isinstance(tmp_value, str):   # 当键值为url时
            tmp_list.append(tmp_value)   # 将url添加至tmp_list
            search_list.append(copy.deepcopy(tmp_list))   # 将tmp_list索引url添加至search_list
            tmp_list = tmp_list[:layer]  # 根据层级保留索引
        elif tmp_value == '':   # 键值为空时跳过
            layer -= 2           # 跳出键值层级
            tmp_list = tmp_list[:layer]   # 根据层级保留索引
        else:
            get_info_list(tmp_value, layer, tmp_list, search_list)  # 当键值为列表时,迭代遍历
            tmp_list = tmp_list[:layer]
    return search_list

# 获取当前索引页面页数的url列表
def get_info_pn_list(url,search_list):
    fin_search_list = []
    for i in range(len(search_list)):
        print('>>>正在抓取%s每页URL地址' % search_list[i][:3])
        search_url = search_list[i][3]
        try:
            page = get_page(search_url)
        except:
            print('获取页面超时')
            continue
        soup = BS(page, 'lxml')
        # 组装URL
        fin_search_list.append(copy.deepcopy(search_list[i][:3]))
        # 方法一:
        # 获取最大页数
        if soup.find('div', class_='page-box house-lst-page-box'):
            pn_num = json.loads(soup.find('div', class_='page-box house-lst-page-box')['page-data'])  # 获取最大页数以及当前页
            max_pn = pn_num['totalPage']  # 获取最大页数,'curPage'存放当前页
            page_url = soup.find('div', class_='page-box house-lst-page-box')['page-url']
            for page in range(1, max_pn + 1):
                fin_search_list[i].append(url + page_url.replace('{page}', str(page)))
        # 方法二:
        # all_a = soup.find('div', class_="pagination_group_a").find_all('a')
        # if all_a:
        #     for a in all_a:
        #         fin_search_list.append(url + a['href'])
    return fin_search_list


#根据每页链接获取该区域所有二手房网址
def get_url_page(fin_search_list):
    fin_info_list = []
    global global_all_url,global_repeat_url
    for i in range(len(fin_search_list)):
        fin_info_list.append(fin_search_list[i][:3])
        for j in range(3,len(fin_search_list[i])):
            print('>>>正在抓取%s第%d页的URL地址' % (fin_search_list[i][:3],j-2))
            url = fin_search_list[i][j]
            try:
                page = get_page(url)
            except:
                print('获取tag超时')
                continue
            soup = BS(page, 'lxml')
            all_li = soup.find('ul', class_='sellListContent').find_all('li')
            print(soup)
            input()
            for li in all_li:
                temp_url = li.find_all('a')[1]
                if temp_url['href'] not in global_all_url:
                    fin_info_list[i].append(temp_url['href']) # 储存区域对应的URL
                    global_all_url.append(temp_url['href'])
                else:
                    global_repeat_url.append(temp_url['href'])
                    print('区域二手房URL重复')
    return fin_info_list

# 获取tag信息
def get_info(fin_search_list):
    print('信息采集开始')
    fin_info_list = {}
    global global_num,base_url
    for url in fin_search_list:
        print('正在获取%s房源信息...' % url)
        fin_info_list[global_num]={}
        try:
            page = get_page(url)
        except:
            print('获取tag超时')
            continue
        soup = BS(page, 'lxml')
        temp=soup.select('.areaName')[0].text.split('\xa0')
        fin_info_list[global_num]['区域']=temp[0].strip('所在区域') # 区域名
        fin_info_list[global_num]['街道'] = temp[1] # 街道名
        if temp[2]:
            fin_info_list[global_num]['位置信息补充']=temp[2]
        fin_info_list[global_num]['小区']=soup.select('.communityName')[0].text.strip('小区名称').strip('地图')
        # 社区名
        communityUrl = base_url+soup.find('div',class_='communityName').find('a')['href']
        fin_info_list[global_num]['小区URL'] = communityUrl
        #
        # 以下获取租房所在经纬度
        # 方法一:通过百度地图获得
        # address = soup.find('div', class_='fl l-txt').find('a').text.strip('链家网').strip('站')
        # address = address + fin_info_list[global_num]['areaName'] + fin_info_list[global_num]['streetName'] + \
        #           fin_info_list[global_num]['communityName']
        # address为小区地址
        # fin_info_list[global_num]['longitude'] = getlnglat(address)['lng']  # 经度
        # fin_info_list[global_num]['latitude'] = getlnglat(address)['lat']  # 维度
        fin_info_list[global_num]['租房网址']=url # 租房网址
        fin_info_list[global_num]['租房信息标题'] = soup.select('.main')[0].text # 标题
        fin_info_list[global_num]['房屋总价'] = soup.select('.total')[0].text + '万' # 总价
        fin_info_list[global_num]['每平方单价']=soup.select('.unitPriceValue')[0].text # 单价
        fin_info_list[global_num]['首付'] = soup.find('div', class_='tax').find('span').text.split()[0].strip('首付') # 首付
        fin_info_list[global_num]['税费'] = \
        soup.find('div', class_='tax').find('span').text.split()[1].strip('税费').strip('(仅供参考)') # 税费
        # 户型信息,此信息差异较大,不提取
        # temp = soup.find('div', id='infoList').find_all('div', class_='col')
        # for i in range(0, len(temp), 4):
        #     fin_info_list[global_num][temp[i].text] = {}
        #     fin_info_list[global_num][temp[i].text]['面积'] = temp[i + 1].text
        #     fin_info_list[global_num][temp[i].text]['朝向'] = temp[i + 2].text
        #     fin_info_list[global_num][temp[i].text]['窗户'] = temp[i + 3].text

        hid = soup.select('.houseRecord')[0].text.strip('链家编号').strip('举报')
        fin_info_list[global_num]['链家编号'] = hid
        # hid:链家编号
        rid = soup.find('div', class_='communityName').find('a')['href'].strip('/xiaoqu/').strip('/')
        fin_info_list[global_num]['社区ID'] = rid
        # rid:社区ID
        fin_info_list[global_num]['房屋建造年代'] = soup.find('div', class_='area').find('div',
                                                                                    class_='subInfo').get_text()  # 建造年代
        fin_info_list[global_num]['房屋朝向'] = soup.find('div', class_='type').find('div',
                                                                                     class_='mainInfo').get_text()  # 朝向

        # 提取基本信息,包括基本属性和交易属性:
        all_li = soup.find('div', class_='introContent').find_all('li')
        for li in all_li:
            fin_info_list[global_num][li.find('span').text] = li.text.split(li.find('span').text)[1]
        # 经纪人信息,该信息有可能为空:
        if soup.find('div', class_='brokerInfoText fr'):
            # 姓名
            fin_info_list[global_num]['经纪人姓名'] = soup.find('div', class_='brokerInfoText fr').find('a',
                                                                                                        target='_blank').text
            temp = soup.find('div', class_='brokerInfoText fr').find('span', class_='tag first')
            fin_info_list[global_num]['经纪人评价URL']=temp.find('a')['href']
            # 评分
            fin_info_list[global_num]['评分'] = temp.text.split('/')[0].strip('评分:')
            # 评价人数
            fin_info_list[global_num]['评价人数'] = temp.text.split('/')[1].strip('人评价')
            # 联系电话,输出结果:'phone': '4008896039转8120'
            fin_info_list[global_num]['经纪人电话'] = soup.find('div', class_='brokerInfoText fr').find('div',
                                                                                               class_='phone').text
            soup2 = BS(get_page(communityUrl), 'lxml')
            CommunityInfo = soup2.find_all('div', class_='xiaoquInfoItem')
        else:
            print('无经纪人信息')
        # 以下为获取小区简介信息
        for temp in CommunityInfo:
            fin_info_list[global_num][temp.find('span', class_='xiaoquInfoLabel').text] = \
                temp.find('span',class_='xiaoquInfoContent').text
            # 获取小区经纬度的第二种方法
            if temp.find('span', class_='actshowMap'):
                lng_lat = temp.find('span', class_='actshowMap')['xiaoqu'].lstrip('[').rstrip(']').split(',')
                # 附近门店的经纬度信息需要替换为temp.find('span', class_='actshowMap')['mendian'].split(',')
                fin_info_list[global_num]['经度'] = lng_lat[0]
                fin_info_list[global_num]['纬度'] = lng_lat[1]
        fin_info_list[global_num]['小区均价'] = soup2.find('span', class_='xiaoquUnitPrice').text + '元/㎡'
        global_num=global_num+1
    return fin_info_list

# 获取经纬度信息
def getlnglat(address):
    url = 'http://api.map.baidu.com/geocoder/v2/'
    output = 'json'
    ak = '请输入你申请的密钥'
    add = quote(address) #由于本文城市变量为中文,为防止乱码,先用quote进行编码
    uri = url + '?' + 'address=' + add + '&output=' + output + '&ak=' + ak
    req = urlopen(uri)
    res = req.read().decode() #将其他编码的字符串解码成unicode
    temp = json.loads(res) #对json数据进行解析
    return temp['result']['location'] # 返回经纬度信息

# 获取查询关键词key所包含的dict
def get_search(url,page, key):
    soup = BS(page, 'lxml')
    all_a = soup.find("div", {"data-role":key}).find_all("a") # 提取所有区域/地铁信息
    temp_list={}
    temp_url=[]
    all_url = []  # 唯一网页链接
    repeat_url=[] # 重复的网页
    for temp in all_a:
        temp_list[temp.get_text()]={}
        temp_url.append(url+temp['href'])
        temp_page=get_page(url+temp['href'])
        temp_soup = BS(temp_page, 'lxml')
        temp_a = temp_soup.find("div", {"data-role": key}).find_all("div")[1].find_all("a")
        # 提取所有区域/地铁信息下的区域信息
        for temp_i in temp_a:
            if (url+temp_i['href']) not in all_url:
                all_url.append(url+temp_i['href'])
                temp_list[temp.get_text()][temp_i.get_text()]=url+temp_i['href']
            else:
                repeat_url.append(url + temp_i['href'])
                print('区域URL重复')
    return temp_list




base_url = r'https://gz.lianjia.com'


search_list = []  # 房源信息url列表
tmp_list = []  # 房源信息url缓存列表
layer = -1
global_all_url=[] # 全局网页唯一URL
global_repeat_url=[] # 全局网页重复URL
global_num=0
if __name__ == '__main__':
    file_name = '111'# input(r'抓取完成,输入文件名保存:')
    fin_save_list = []  # 抓取信息存储列表
    # 一级筛选
    page = get_page(base_url+'/ershoufang') # 解析网页
    position={'ershoufang':'区域','ditiefang':'地铁线'} # 城市位置信息
    search_dict={}
    for poskey in position.keys():
        search_dict[position[poskey]]={}
        search_dict[position[poskey]] = get_search(base_url,page, poskey)
    # print(search_dict)
    # 获取城市位置及其网页,三重字典,第一层分为区域及地铁线,第二层为每个区域及地铁线名称,第三层为对应位置及其网页地址

    fin_info_list = get_info_list(search_dict, layer, tmp_list, search_list)
    # print(fin_info_list) # 三重标签网页合一输出
    # fin_info_list=[['区域', '鼓楼', '草场门大街', 'https://nj.lianjia.com/ershoufang/caochangmendajie/'],
    #                ['区域', '鼓楼', '定淮门大街', 'https://nj.lianjia.com/ershoufang/dinghuaimendajie/']]# for test
    fin_info_pn_list = get_info_pn_list(base_url,fin_info_list) # 获取每页链接
    fin_url_list = get_url_page(fin_info_pn_list) # 存放该区域名称及其所有二手房信息的URL网址
    # 保存数据
    HouseData=get_info(global_all_url)
    df = pd.DataFrame(HouseData).T # 转置,行表示每套房的基本信息,列表示每套房屋
    df.to_csv('123.csv',encoding='utf-8_sig') # 通过encoding解决保存中文乱码问题、

五、数据处理部分

参考文章:超详细:Python(wordcloud+jieba)生成中文词云图
以上四个章节获得了二手房信息,现在开始对数据进行处理,首先导入数据以及必要的库:

import jieba
from urllib import request,error
from fake_useragent import UserAgent
import requests
import socket
import time
import json
from wordcloud import WordCloud, STOPWORDS, ImageColorGenerator
import matplotlib.pyplot as plt
from skimage import io
import pandas as pd
from bs4 import BeautifulSoup as BS

# 导入数据
dfdata = pd.read_csv('HouseInfoData.csv')

5.1 经纪人评论情况云图制作

对于经纪人的评论情况并不能通过常规的方法获取评语,虽然在'div'标签中可以看到评语,但是通过find('div',class_='content').text的方法吧并不能提取评语,需要通过其他手段,估计这些评语是动态加载的,可以参考# 浅谈如何使用python抓取网页中的动态数据所描述的方法来提取评语

image.png

首先定义经纪人信息的函数:

# 获取经纪人评价信息
def BrokerInfo(url):
    soup=BS(get_page(url),'lxml')

    BrokerName=soup.find('div',class_='agent-name clear-fl').find('a').text
    BrokerIMG=soup.find('div',class_='pic_panel').find('img')['src']
    ucid=soup.find('a',class_='lianjiaim-createtalkAll fl')['data-ucid'] # 经纪人ID
    BrokerComment=''
    temp_url='https://dianpu.lianjia.com/shop/getcomment/{}?size=20&offset=0&is_empty=0'.format(ucid)
    temp = json.loads(requests.get(temp_url).text)['commentAll']
    for comment in temp:
        BrokerComment=BrokerComment+comment['content']
    return BrokerComment,BrokerIMG,BrokerName # 返回评语、图像URL地址、经纪人姓名

然后生成云图,并保存在本地

# 导入数据
dfdata = pd.read_csv('HouseInfoData.csv')
text, img, brokername = BrokerInfo(dfdata['经纪人评价URL'][0])
back_img = io.imread('1.jpg') # 导入云图背景图片
wc = WordCloud(background_color='white',  # 背景颜色
               max_words=1000,  # 最大词数
               mask=back_img,  # 以该参数值作图绘制词云,这个参数不为空时,width和height会被忽略
               max_font_size=100,  # 显示字体的最大值
               stopwords=STOPWORDS,  # 使用内置的屏蔽词,再添加'苟利国'
               font_path="C:/Windows/Fonts/STFANGSO.ttf",  # 解决显示口字型乱码问题,可进入C:/Windows/Fonts/目录更换字体
               random_state=42,  # 为每个词返回一个PIL颜色
               # width=1000,  # 图片的宽
               # height=860  #图片的长
               )
# WordCloud各含义参数请点击 wordcloud参数

# 添加自己的词库分词,比如添加'XXY'到jieba词库后,当你处理的文本中含有XXY这个词,
# 就会直接将'XXY'当作一个词,而不会得到'XX'或'XY'这样的词
# jieba.add_word('XXY')

# 该函数的作用就是把屏蔽词去掉,使用这个函数就不用在WordCloud参数中添加stopwords参数了
# 把你需要屏蔽的词全部放入一个stopwords文本文件里即可
# def stop_words(texts):
#     words_list = []
#     word_generator = jieba.cut(texts, cut_all=False)  # 返回的是一个迭代器
#     with open('stopwords.txt',encoding='utf-8') as f:
#         str_text = f.read()
#         f.close()  # stopwords文本中词的格式是'一词一行'
#     for word in word_generator:
#         if word.strip() not in str_text:
#             words_list.append(word)
#     return ' '.join(words_list)  # 注意是空格

# text = stop_words(text)

wc.generate(text)
# 基于彩色图像生成相应彩色
image_colors = ImageColorGenerator(back_img)
# 显示图片
plt.imshow(wc)
# 关闭坐标轴
plt.axis('off')
# 绘制词云
plt.figure()
plt.imshow(wc.recolor(color_func=image_colors))
plt.axis('off')
# 保存图片
wc.to_file('%s.png' % brokername)

后话

1.这里只提取了南京的二手房信息,如果想要提取其他城市就得修改base_url 的取值
base_url = r'https://nj.lianjia.com'
其他城市可能会有其他问题
2.此外,在运行过程中会遇到有时取不到值的现象:

>>>正在抓取['区域', '鼓楼', '小市']第3页的URL地址
Traceback (most recent call last):
  File "E:/PycharmProjects/untitled1/HousePrice.py", line 405, in <module>
    fin_url_list = get_url_page(fin_info_pn_list) # 存放该区域名称及其所有二手房信息的URL网址
  File "E:/PycharmProjects/untitled1/HousePrice.py", line 131, in get_url_page
    all_li = soup.find('ul', class_='sellListContent').find_all('li')
AttributeError: 'NoneType' object has no attribute 'find_all'

其实此处soup.find('ul', class_='sellListContent')是有值的,因此soup.find('ul', class_='sellListContent')的值并不是NoneType。暂时不知道原因,在此留待后查
3.因为这种方法效率过低,因此可以用python3 爬虫教学之爬取链家二手房(最下面源码) //以更新源码所介绍的方法提取信息。
更多内容请移步:
爬虫系列文章:
南京链家爬虫系列文章(一)——工具篇
南京链家爬虫系列文章(二)——scrapy篇
南京链家爬虫系列文章(三)——MongoDB数据读取
南京链家爬虫系列文章(四)——图表篇

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容

  • 爬取链家所有二手房信息,并存入python自带的sqlite3数据库中,借鉴他人程序进行编写 目的:1、继续熟悉s...
    bitmote阅读 668评论 0 0
  • 题外话:这几天用python做题,算是有头有尾地完成了。这两天会抽空把我的思路和方法,还有代码贴出来,供pytho...
    徐胥阅读 2,075评论 1 4
  • 梦想是什么? 现实又是什么? 现实很残忍 梦想很美好 每个人都有最美好的梦 最好的年华 我们总是用 最美好的年华追...
    写作星阅读 180评论 0 3
  • 试试简书📖
    ddxing阅读 212评论 0 0
  • 看天边的彩虹飞过 看落日的倒影成河 一刹那的烟花 崩裂破回忆世界的薄膜 记忆的流沙也是 飞舞的尘 只是局促在暗地 ...
    三水芒芒阅读 248评论 0 3