我爱编程

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

2018-01-01  本文已影响0人  haffner2010

写在前面的话

本文参考的信息如下:

【房价网房价信息爬虫】整站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数据读取
南京链家爬虫系列文章(四)——图表篇

上一篇下一篇

猜你喜欢

热点阅读