Python爬虫爬取链家网房源数据实例

作者:青山常在人不老   阅读 (1664)  |  收藏 (1)  |  点赞 (1)

摘要

关于使用Python来实现链家的数据爬取和分析


原文链接:Python爬虫爬取链家网房源数据实例

已经实现

 1.房屋数据爬取并下载

 2.房屋按区域分析

 3.房屋按经纪人分析

 4.前十经纪人

 5.经纪人最有可能的位置分析

 6.实现以地区划分房屋

 目前存在的问题:

 1.多线程下载的时候会出现个别文件不继续写入了(已经解决)

 2.未考虑经纪人重名问题

 3.查询中发现不是每次都能 get 到 url 的数据,具体原因可能跟header有关,或者网站反扒(已经解决,手机端的header有时候访问pc端会出现None的情况)

 4.守护线程那里应该出问题了,如果有文件储存完成,其他就不运行了(已经解决,多线程下还要有主程序运行,否则会出现问题)

 5.json.dumps(dict)方法取出的字符串类型,二进制的,decode不好用,怎么解决

  (已经解决json.dumps(content, ensure_ascii=False)保持原有的编码)

# -*- coding: utf-8 -*-
# @Time :2018/5/1   23:39
# @Author : ELEVEN
# @File : _链家_数据分析_修改.py
# @Software: PyCharm

import time
from lxml import etree
from urllib import request
import threading
import os
import json
import random

'''
已经实现
1.房屋数据爬取并下载
2.房屋按区域分析
3.房屋按经纪人分析
4.前十经纪人
5.经纪人最有可能的位置分析
6.实现以地区划分房屋
目前存在的问题:
1.多线程下载的时候会出现个别文件不继续写入了(已经解决)
2.未考虑经纪人重名问题
3.查询中发现不是每次都能 get 到 url 的数据,具体原因可能跟header有关,或者网站反扒(已经解决,手机端的header有时候访问pc端会出现None的情况)
4.守护线程那里应该出问题了,如果有文件储存完成,其他就不运行了(已经解决,多线程下还要有主程序运行,否则会出现问题)
5.json.dumps(dict)方法取出的字符串类型,二进制的,decode不好用,怎么解决
(已经解决json.dumps(content, ensure_ascii=False)保持原有的编码)


'''
# 获取能够 xpath 匹配的 HTML 匹配对象
def get_html(url):
    time.sleep(1)
    header = {
        'Referer':'https://bj.lianjia.com/zufang/',
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0'
    }
    req = request.Request(url, headers = header)
    # 请求
    response = request.urlopen(req)
    result = response.read().decode()
    # 构建 HTML 匹配对象
    html = etree.HTML(result)
    return html
# 主程序
def main(p, url):
    # 加锁,写入本条数据后指针才会进行跳转
    lock = threading.Lock()
    # 获取 get_html() 函数返回的 HTML 匹配对象
    html = get_html(url)
    # 进行 xpath 初步匹配
    house_list = html.xpath('//ul[@id="house-lst"]/li/div[@class = "info-panel"]')
    threading_list = []
    # 遍历得到的匹配列表
    for index, house in enumerate(house_list):
        content, house_address, house_dict ,broker_name = get_info(p,index, house)
        print('正在保存第 %d 页 第 %s 条数据......' % (p, index+1))
        lock.acquire()
        # save_info(p, str(index+1) + ' ' + content + '\n')
        get_class_data(house_address, house_dict, broker_name)
        t2 = threading.Thread(target=save_info, args=(p, str(index+1) + ' ' + content + '\n'))
        t3 = threading.Thread(target=get_class_data, args=(house_address, house_dict, broker_name))
        t2.setDaemon(True)  # 这个好像没有用,等老师帮助解答
        t2.start()
        t3.start()
        threading_list.append(t2)
        threading_list.append(t3)
        lock.release()
    for t in threading_list:
        t.join()
    # 这里必须的写, 这个错误得记住,必须要有主进程 ,所有线程才会都运行
    print('我是守护线程')
# 获取分类数据,方便数据分析
def get_class_data(house_address, house_dict, broker_name):
    # 按区域划分
    if house_address in address_class:
        address_class[house_address]['num'] += 1
        address_class[house_address]['house'].append(house_dict)
    else:
        address_class[house_address] =  {'num': 0, 'house': []}
        address_class[house_address]['num'] += 1
        address_class[house_address]['house'].append(house_dict)
    # 按经纪人划分
    if broker_name in broker_class:
        broker_class[broker_name]['num'] += 1
        broker_class[broker_name]['house'].append(house_dict)
    else:
        broker_class[broker_name] =  {'num': 0, 'house': []}
        broker_class[broker_name]['num'] += 1
        broker_class[broker_name]['house'].append(house_dict)
# 获取房产信息
def get_info(p,index, house):
    house_url = house.xpath('h2/a/@href')[0]
    house_html = get_html(house_url)
    broker_name = house_html.xpath(
        '//div[@class="brokerInfo"]/div[@class="brokerInfoText"]/div[@class="brokerName"]/a/text()')
    broker_phone = house_html.xpath('//div[@class="phone"]/text()')
    if broker_name != []:
        broker_name = broker_name[0]
        broker_phone = str(broker_phone[0].strip()) + '转' + str(broker_phone[1].strip())
    else:
        broker_name = '暂无相关经纪人!'
        broker_phone = '请直接联系客服  10109666'
    house_name = house.xpath('h2/a/text()')[0]
    house_style = house.xpath('div[@class="col-1"]/div[@class="where"]/span[@class="zone"]/span/text()')[0]
    house_size = house.xpath('div[@class="col-1"]/div[@class="where"]/span[@class="meters"]/text()')[0].strip()
    house_address = house.xpath('div[@class="col-1"]/div[@class="other"]/div[@class="con"]/a/text()')[0]
    house_price = house.xpath('div[@class="col-3"]/div[@class="price"]/span/text()')[0]
    house_dict = {
        'house_name': house_name,
        'style': house_style.strip(),
        'size': house_size,
        'address': house_address,
        'price': house_price,
        'broker_name': broker_name,
        'broker_phone': broker_phone
    }
    content = "名字:%(house_name)s 样式:%(style)s  大小:%(size)s  地址:%(address)s  " \
              "价格:%(price)s 经纪人:%(broker_name)s 联系电话:%(broker_phone)s " % house_dict
    # 构建字典类型 {‘house_name’:{'num':13, 'house':[house_dict]}}
    print(p, index+1, content)
    return content,house_address,house_dict,broker_name
# 保存文件
def save_info(p, content):
    with open('%s/%s.txt' % ('链家房产信息', str(p)), 'a', encoding='utf-8') as f:
        f.write(content)
# 随机消息头, 这里修改后再用,应该是出问题了
# def random_agent():
#     header_str = '''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#Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50#Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)#Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1#Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1#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#Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Maxthon 2.0)#Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler 4.0)#Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)#Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SE 2.X MetaSr 1.0; SE 2.X MetaSr 1.0; .NET CLR 2.0.50727; SE 2.X MetaSr 1.0)#Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5#Mozilla/5.0 (iPod; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5#MQQBrowser/26 Mozilla/5.0 (Linux; U; Android 2.3.7; zh-cn; MB200 Build/GRJ22; CyanogenMod-7) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1
#     '''
#     header = header_str.split('#')
#     return header[random.randint(1, len(header)) - 1]
# 数据分析
def analyze_data():
    # 查询信息内房屋总套数
    address_sum = 0
    # 区域
    for key, value in address_class.items():
        print(key, '一共', value['num'], '套')
        save_info('房屋分类', key+ '一共'+ json.dumps(value['num'])+ '套'+'\n')
        # with open('%s/%s.txt' % ('链家房产信息', '房屋分类'), 'a', encoding='utf-8') as f:
        #     f.write(key+ '一共'+ value['num']+ '套')
        # 每遍历到一个区域 房屋总数量变量address_sum 就将这个区域的房屋数量加上
        address_sum += int(value['num'])
        # 遍历每个区域房屋信息
        for item in value['house']:
            print(item)
            save_info('房屋分类', json.dumps(item)+'\n')
            # with open('%s/%s.txt' % ('链家房产信息', '房屋分类'), 'a', encoding='utf-8') as f:
            #     f.write(item)
        print('----------------------------------')
    print('当前查询房屋一共', address_sum, '套')
    save_info('房屋分类', '当前查询房屋一共'+ str(address_sum)+ '套')
    # 创建字典:键为 经纪人 值为 经纪人所拥有房屋数量 num
    broker_dict = {}
    for key, value in broker_class.items():
        # 打印 经纪人 和 其所拥有的房屋数量
        print(key, '一共', value['num'], '套')
        save_info('经纪人分类', key+ '一共'+ json.dumps(value['num'])+ '套'+'\n')
        # 将经纪人 和 房屋数量信息添加到 字典中
        broker_dict[key] = value['num']
        # 分别打印该 经纪人 下面每套房屋的信息
        for item in value['house']:
            print(item)
            save_info('经纪人分类', json.dumps(item) + '\n')
        print('----------------------------------')
    # 如果存在 '暂无相关经纪人!'这种情况,那么统计的联系人数量需要减一
    if '暂无相关经纪人!' in list(broker_class.keys()):
        # broker_sum 为经纪人 数量总数
        broker_sum = int(len(broker_class)) - 1
        print(broker_sum)
        # broker_house 为 经纪人 拥有房屋总套数
        broker_house = address_sum - int(broker_class['暂无相关经纪人!']['num'])
        del broker_dict['暂无相关经纪人!']
    else:
        broker_sum = len(broker_class)
        broker_house = address_sum
    print(broker_dict)
    # 整理出不含有 '暂无相关经纪人!' 的字典,方便下面进行数据分析
    # if broker_dict['暂无相关经纪人!']:
    #     del broker_dict['暂无相关经纪人!']

    print('当前查询经纪人共有', broker_sum, '人')
    print('当前查询有经纪人的房屋', broker_house, '套')
    # 存储前十名的列表
    max_list = []
    print('排序得到前十的经纪人')
    for i in range(10):
        # 取出手里拥有房子最多的经纪人
        max_num = max(zip(broker_dict.values(), broker_dict.keys()))
        # 将房子最多的联系人添加到前十的列表中
        max_list.append(max_num)
        # 打印,第几名,是谁
        print('第 %d 名' % (i + 1), max_num)
        save_info('排名', '第 %d 名' % (i + 1)+'姓名: '+ max_num[1]+'   '+str(max_num[0])+'套'+'\n')
        # 已经被取出的经纪人,从列表中删除掉,以免影响下一次筛选
        del broker_dict[max_num[1]]
        # 创建存储经纪人位置的字典,
        broker_postion = {}
        # 对经纪人手中的房子按照区域划分,并且存入字典中,统计各区域拥有房屋数量
        # 房屋最多的区域就是 经纪人最有可能在的位置
        for dict in broker_class[max_num[1]]['house']:
            # 如果此区域存在字典中,那么相应的区域数量 num + 1
            # 如果不存在,那么将这个区域添加到字典中,数量 num + 1
            if dict['address'] in broker_postion:
                broker_postion[dict['address']]['num'] += 1
            else:
                broker_postion[dict['address']] = {'num': 0}
                broker_postion[dict['address']]['num'] += 1
        # 取出经纪人按找区域分类的字典中 ,数量num最大的那个区域元组
        postion = max(zip(broker_postion.values(), broker_postion.keys()))
        print('最可能在的位置', postion[1])
        save_info('排名', '最可能在的位置'+ postion[1]+'\n')
        # 此经纪人数据分析已经结束,字典清空释放掉,方便下次其他经纪人使用
        broker_postion.clear()
    # print('排序得到前十的经纪人')
    # print(max_list)


if __name__ == "__main__":
    # 以地址划分, 创建以 address 为键字典
    address_class = {}
    # 以经纪人划分, 创建以 broker 为键字典
    broker_class = {}
    # 加锁
    lock = threading.Lock()
    # 运行初始提示
    print('   -------------可供选择的区域--------------\n东城 西城 朝阳 海淀 丰台 石景山 通州 昌平 大兴 顺义\n亦庄开发区 房山 门头沟 平谷 怀柔 密云 延庆 燕郊 香河')
    option = input('请输入相应区域的拼音,默认视为选择全部:')
    page = int(input('请输入要获取的页数:'))
    # 创建文件夹
    if not os.path.exists('链家房产信息'):
        os.mkdir('链家房产信息')
    # 多线程列表
    thread_list = []
    for p in range(1, page+1):
        if p == 1:
            url = 'https://bj.lianjia.com/zufang/%s/' % option
        else:
            url = 'https://bj.lianjia.com/zufang/%s/pg%d/' % (option, p)
        print('----------开始打印第 %d 页信息----------' % p)
        lock.acquire()
        t1 = threading.Thread(target=main, args=(p,url))
        # 设置守护线程
        t1.setDaemon(True)
        t1.start()
        thread_list.append(t1)
        lock.release()
        print('----------打印第 %d 页信息结束----------' % p)
    for t1 in thread_list:
        t1.join()
    # 执行数据分析
    analyze_data()
分类   默认分组
字数   10703

博客标签    python爬取链家网数据   链家房源数据爬取  

评论