首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >房天下二手房交易数据爬取

房天下二手房交易数据爬取

作者头像
爱编程的小明
发布2022-09-06 14:03:16
发布2022-09-06 14:03:16
1.2K0
举报
文章被收录于专栏:小明的博客小明的博客

本次是爬取西安房天下上的二手房交易数据,主要面临的困难有:

  • 网页的重定向问题的识别
  • 不完全规则网页的匹配规则书写问题
  • 爬虫效率问题
  • 滑块验证问题
代码语言:javascript
复制
import requests
from scrapy.selector import Selector as se
import pandas as pd
import re
headers = {
    'Connection': 'keep-alive',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36 Edg/103.0.1264.37',
}
datas = []
fail_url = []
for page in range(73, 76):
    # https://xian.esf.fang.com/house/i32/
    response = requests.get('https://xian.esf.fang.com/house/i3'+str(page+25)+'/',   headers=headers,
                            verify=True)
    html = response.text
    next_page_url = 'https://xian.esf.fang.com/house/i3' + \
        str(page+1)+'?'+re.findall(r"t3='(.*?)'", html)[0]

    response = requests.get(next_page_url,   headers=headers,
                            verify=True)
    next_url = se(text=response.text).xpath(
        '//h4[@class="clearfix"]/a/@href').extract()
    for url in next_url:
        response1 = requests.get('https://xian.esf.fang.com'+url,   headers=headers,
                                 verify=True)
        html = response1.text
        page_url = 'https://xian.esf.fang.com' + \
            url+'?'+re.findall(r"t3='(.*?)'", html)[0]
        print(f"开始爬取{page_url}")
        response = requests.get(page_url,  headers=headers,
                                verify=True)
        data = response.text
        # print(data)
        # //*[@id="lpname"]/h1/span
        # title
        x1 = se(text=data).xpath(
            '//span[@class="tit_text"]/text()').extract_first().replace('\n', '').strip()
        # 总价
        x2 = se(text=data).xpath(
            '//div[@class="trl-item price_esf  sty1"]/i/text()').extract_first()
        # 户型
        x3 = se(text=data).xpath(
            '//div[text()="户型"]/../div/text()').extract_first()
        # '//div[text()="户型"]/../div/text()').extract_first().replace('\n', '').strip()
        # 建筑面积
        x4 = se(text=data).xpath(
            '//div[@class="trl-item1 w182"]/div/text()').extract_first()
        # 单价
        x5 = se(text=data).xpath(
            '//div[@class="trl-item1 w132"]/div/text()').extract_first()
        # 朝向
        x6 = se(text=data).xpath(
            '//div[text()="朝向"]/../div[@class="tt"]/text()').extract_first()  # .replace('\n', '').strip()
        # 楼层
        x7 = se(text=data).xpath(
            '//div[text()="朝向"]/../../div[@class="trl-item1 w182"]/div[@class="tt"]/a/text()').extract_first()
        # 总楼层
        x27 = se(text=data).xpath(
            '//div[text()="朝向"]/../../div[@class="trl-item1 w182"]/div[@class="font14"]/text()').extract_first()
        # 装修
        x8 = se(text=data).xpath(
            '//div[text()="装修"]/../div/a/text()').extract_first()
        # 小区
        x9 = se(text=data).xpath(
            '//div[@class="rcont"]/a/text()').extract_first()
        # 小区位置具体描述
        x10 = se(text=data).xpath(
            '//div[@class="rcont"]/span/text()').extract_first()
        # 区
        x11 = se(text=data).xpath(
            '//div[@id="address"]/a/text()').extract()[0].replace('\n', '').strip()
        # 区域
        x12 = se(text=data).xpath(
            '//div[@id="address"]/a/text()').extract()[1].replace('\n', '').strip()
        # 建筑年代
        x13 = se(text=data).xpath(
            '//span[text()="建筑年代"]/../span[@class="rcont "]/text()').extract_first()
        # 有无电梯
        x14 = se(text=data).xpath(
            '//span[text()="有无电梯"]/../span[@class="rcont"]/text()').extract_first()
        # 产权性质
        x15 = se(text=data).xpath(
            '//span[text()="产权性质"]/../span[@class="rcont"]/a/text()').extract_first()
        # 住宅类别
        x16 = se(text=data).xpath(
            '//span[text()="住宅类别"]/../span[@class="rcont"]/a/text()').extract_first()
        # 建筑结构
        x17 = se(text=data).xpath(
            '//span[text()="建筑结构"]/../span[@class="rcont"]/a/text()').extract_first()
        # 建筑类别
        x18 = se(text=data).xpath(
            '//span[text()="建筑类别"]/../span[@class="rcont"]/a/text()').extract_first()
        # 挂牌时间
        if se(text=data).xpath(
                '//span[text()="挂牌时间"]/../span[@class="rcont"]/text()').extract_first():

            x19 = se(text=data).xpath(
                '//span[text()="挂牌时间"]/../span[@class="rcont"]/text()').extract_first().replace('\n', '').strip()
        else:
            x19 = se(text=data).xpath(
                '//span[text()="挂牌时间"]/../span[@class="rcont"]/text()').extract_first()
        # 参考均价
        x20 = se(text=data).xpath(
            '//span[text()="参考均价"]/../span/i/text()').extract_first()
        # 物业类型
        x21 = se(text=data).xpath(
            '//span[text()="物业类型"]/../span[@class="rcont "]/text()').extract_first()
        # 绿化率
        x22 = se(text=data).xpath(
            '//span[text()="绿  化  率"]/../span[@class="rcont "]/text()').extract_first()
        # 容积率
        x23 = se(text=data).xpath(
            '//span[text()="容  积  率"]/../span[@class="rcont "]/text()').extract_first()
        # 建筑类型
        x24 = se(text=data).xpath(
            '//span[text()="建筑类型"]/../span[@class="rcont "]/text()').extract_first()
        # 人车分流
        x25 = se(text=data).xpath(
            '//span[text()="人车分流"]/../span[@class="rcont "]/text()').extract_first()
        # 总户数
        x26 = se(text=data).xpath(
            '//span[text()="总  户  数"]/../span[@class="rcont "]/text()').extract_first()
        x28 = se(text=data).xpath(
            '//span[text()="总楼栋数"]/../span[@class="rcont "]/text()').extract_first()
        # 总评分
        x29 = se(text=data).xpath(
            '//p[text()="房源评级|多维度分析房源"]/../div[@class="num_bg"]/h3/text()').extract_first()
        x30 = se(text=data).xpath(
            '//p[text()="交易价值"]/../h4/text()').extract_first()
        x31 = se(text=data).xpath(
            '//p[text()="居住品质"]/../h4/text()').extract_first()
        x32 = se(text=data).xpath(
            '//p[text()="便利指数"]/../h4/text()').extract_first()
        x33 = se(text=data).xpath(
            '//p[text()="物业服务"]/../h4/text()').extract_first()
        datas.append([x1, x2, x3, x4, x5, x6, x7, x27, x8, x9, x10, x11, x12, x13,
                     x14, x15, x16, x17, x18, x19, x20, x21, x22, x23, x24, x25, x26, x28, x30, x31, x32, x33, x29])
    print(f'第{page+1}页数据爬取完成')
final_data = pd.DataFrame(datas, columns=['title', '总价', '户型',
                                          '建筑面积', '单价', '朝向',
                                          '楼层', '总楼层', '装修',
                                          '小区', '小区位置具体描述',
                                          '所属区', '所属具体区域', '建筑年代',
                                          '有无电梯', '产权性质', '住宅类别',
                                          '建筑结构', '建筑类别', '挂牌时间',
                                          '小区参考均价', '物业类型', '绿化率',
                                          '容积率', '小区建筑类型', '人车分流',
                                          '总户数', '总楼栋数', '交易价值',
                                          '居住品质', '便利指数', '物业服务',
                                          '综合评分'])
# final_data=final_data.set_index('id')
final_data.to_excel('数据.xlsx')

上述代码是本次校赛仓促之间写出的代码,还有一些地方需要优化:

本次爬虫总的来看是一次不怎么成功的爬虫项目经历,首先时间限制加上对于Scrapy框架不够熟悉导致在实际爬取数据过程中并没有使用Scrapy框架,临时使用的requests库也存在一些需要优化的地方:

  1. 没有使用beautifulsoup进行网页文件解析而是用了Scrap框架中的解析库。
  2. 匹配规则需要进行优化,对房子网页的结构并没有分辨清楚便开始爬虫,用节点文本作为匹配规则导致部分’朝向’位置写成’进门朝向’的数据并没有被匹配到,这直接导致了爬得数据中朝向、楼价、房价一列出现大量缺失,同样的问题也发生在了建筑年代匹配列(属性值分为有空格和没有空格两种)。
  3. 没有正确分析所需数据需求直接进行盲目爬取,做了很多无用功(这个是老毛病了,遇到问题时冷静思考的能力就丧失了😥)
  4. 代码的逻辑性比较强,但是修改起来相对麻烦,看起来不够简洁。
  5. 爬虫的效率不够高,考虑通过多线程进行进一步优化。
  6. 数据存储的逻辑需要更改,爬虫过程中可能会出现各种报错,最理想的方法是在每次爬虫后保存获得的数据(善用try语句防止意外失误!)

🤔经验总结

  1. 匹配的精准程度势必会导致匹配到数据变少的问题,最初写的匹配规则存在明显的’过拟合’问题,这也是导致爬取失败的主要原因。制定匹配规则前冷静的观察页面结构是有必要的。首先观察容易发现[‘户型’, ‘建筑面积’, ‘单价’, ‘朝向’, ‘楼层(共34层)’, ‘装修’]这些信息是不太可能出现缺失的(可能为空,但是不会直接没有这一项),因此在匹配这些信息时可以稍微‘粗犷’一些,这也避免了缺失值的出现。
  2. 爬虫数据爬取过程中要时时做好存储与爬取失败定位,不要因为一个页面的错误导致后边页面全部爬取失败,爬虫本身是一个十分浪费时间的事情,这会大大降低效率。
  3. 善用try语句

关于网页的重定向问题

在爬取房天下的网页过程中遇到的一个问题是输入网页链接后拿到的html文件中并不是实际我们想要拿到的html文件,主要原因是因为实际访问的页面中还存在一串自动编码:

即面临的网页重定向问题,这个问题的解决主要是依据想办法通过初次访问拿到的html文件中做匹配得到真实访问的链接,观察获取到的html文件可以发现,实际的链接在返回html的两个位置:

代码语言:javascript
复制
<div class="redirect" style="padding-top: 40px;">
    <p class="info" style="font-size: 18pt; margin-bottom: 8px;">自动跳转中<span class="second"></span>s...</p>
    <a class="btn-redir" style="font-size: 14pt;" href="https://xian.esf.fang.com/house/i31/?rfss=1-4906b68e40073d2ab0-41">点击跳转</a>
代码语言:javascript
复制
var t4='https://xian.esf.fang.com/house/i31/';
var t3='rfss=1-4906b68e40073d2ab0-41';


location.href=t4+'?'+t3;

这些都可以从html文件中直接获取得到,然后这个链接其实才是我们访问所需要的真正链接,第一种情况应该是更常见的一种重定向情况。

下次遇到网页的重定向可以先用正则匹配一下页面的链接来寻找真正可以拿到数据的url,另外就是需要注意的是这里拿到的url并非链接地址栏显示的url,据我观察链接地址栏的url会在一段时间后失效,具体的机理分析仍有待进一步的深入学习

😄终稿

经过一番重塑修改,对代码的逻辑进行进一步优化,最后写出了最终版本的房天下二手房交易数据爬取py代码:

代码语言:javascript
复制
import requests
import re
from scrapy.selector import Selector as se
import time
#import _thread
# 爬虫结果输出路径
out_dir = '爬虫结果.csv'
#列名
columns = ['title', '总价', '户型', '建筑面积',
           '朝向', '楼层', '总楼层', '装修',
           '所在小区', '区', '区域', '建筑年代',
           '有无电梯', '挂牌时间', '小区参考均价',
           '物业类型', '绿化率', '容积率', '建筑类型',
           '人车分流', '总户数', '总楼栋数', '交易价值',
           '居住品质', '便利指数', '物业服务', '总评分',
           '单价']
headers = {
    'Connection': 'keep-alive',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36 Edg/103.0.1264.37',
    #'Cookie': 'csrfToken=fxB64yKN6YmKp2x6IBImOond; global_cookie=4qsim60u3xw9srizbptt3nh3q1yl5377g9v; engine_source_cookie=bing; sf_source=bing; city=xian; lastscanpage=0; g_sourcepage=esf_fy%5Exq_pc; unique_cookie=U_4qsim60u3xw9srizbptt3nh3q1yl5377g9v*62',
}
# 记录匹配失败网页
fail_url=[]
def get_url(url):
    """
    获取html文件
    """
    response = requests.get(url,   headers=headers, verify=True)
    print(f"开始爬取{url}")
    time.sleep(0.1)
    return response


def process_url(response):
    """
    返回重定向后的url(解决网页的重定向问题)
    """
    html = response.text
    """ try:
        url_head = re.findall(r"t4='(.*?)'", html)[0]
        url_tail = re.findall(r"t3='(.*?)'", html)[0]
    except:
        print(f'{response.url}解析失败') """
    return se(text=html).xpath('//div[@class="redirect"]/a[1]/@href').extract_first()  # url_head+'?'+url_tail


def parse_page(response):
    """
    返回当前房源页面的所有房子的链接以及下一个房源页面的url
    """
    # 解析页面提取页面内的房源url
    html = response.text
    house_url_list = se(text=html).xpath(
        '//h4[@class="clearfix"]/a/@href').extract()
    temp_url = se(text=html).xpath(
        '//a[text()="下一页"]/@href').extract_first()
    if temp_url:
        return house_url_list, 'https://xian.esf.fang.com'+temp_url
    #elif temp_url[-3:-1]%25==0:
       # return house_url_list,None
    else:
        return house_url_list,temp_url
def save(data, out_dir,column_name):
    """
    保存爬取数据
    """
    import csv
    import os
    if not os.path.exists(out_dir):
        with open(out_dir, 'w', newline='', encoding='utf-8') as fp:
            csvout = csv.writer(fp)
            csvout.writerow(column_name)  # 初次爬取写入列名
            csvout.writerow(data)
    else:
        # 追加写入内容
        with open(out_dir, "a", newline='', encoding='utf-8') as fp:
            csvout = csv.writer(fp)
            csvout.writerow(data)
def parse_house_page(response):
    """
    解析房子网页获得需要的数据
    """
    data=response.text
    try:
        # title
        x1 = se(text=data).xpath(
            '//span[@class="tit_text"]/text()').extract_first()#.replace('\n', '').strip()
        # 总价
        x2 = se(text=data).xpath(
            '//div[@class="trl-item price_esf  sty1"]/i/text()').extract_first()
        # 户型
        x3 = se(text=data).xpath(
            '//div[@class="tt"]/text()').extract()[0].replace('\n', '').strip()
        # 建筑面积
        x4 = se(text=data).xpath(
            '//div[@class="tt"]/text()').extract()[1]
        # 朝向
        x5 = se(text=data).xpath(
            '//div[@class="tt"]/text()').extract()[3]
        # 楼层
        x6 = se(text=data).xpath(
            '//div[@class="tt"]/a/text()').extract()[0]
        # 总楼层
        x7 = se(text=data).xpath(
            '//div[@class="font14"]/text()').extract()[4]
        # 装修
        x8 = se(text=data).xpath(
            '//div[@class="tt"]/a/text()').extract()[1]
        # 所在小区
        x9 = se(text=data).xpath(
            '//div[@class="rcont"]/a/text()').extract_first()
        # 区
        x10 = se(text=data).xpath(
            '//div[@id="address"]/a/text()').extract()[0].replace('\n', '').strip()
        # 区域
        x11 = se(text=data).xpath(
            '//div[@id="address"]/a/text()').extract()[1].replace('\n', '').strip()
        # 建筑年代
        year = se(text=data).xpath(
            '//span[text()="建筑年代"]/../span/text()').extract()
        if year:
            x12 = year[1]
        else:
            x12 = None
        # 有无电梯
        x13 = se(text=data).xpath(
            '//span[text()="有无电梯"]/../span[@class="rcont"]/text()').extract_first()
        # 挂牌时间
        date = se(text=data).xpath(
            '//span[text()="挂牌时间"]/../span[@class="rcont"]/text()').extract_first()
        if date:
            x14 = date.replace('\n', '').strip()
        else:
            x14 = date.extract_first()
        # 小区参考均价
        x15 = se(text=data).xpath(
            '//span[text()="参考均价"]/../span/i/text()').extract_first()
        # 绿化率
        x16 = se(text=data).xpath(
            '//span[text()="绿  化  率"]/../span[@class="rcont "]/text()').extract_first()
        # 容积率
        x17 = se(text=data).xpath(
            '//span[text()="容  积  率"]/../span[@class="rcont "]/text()').extract_first()
        # 建筑类型
        x18 = se(text=data).xpath(
            '//span[text()="建筑类型"]/../span[@class="rcont "]/text()').extract_first()
        # 人车分流
        x19 = se(text=data).xpath(
            '//span[text()="人车分流"]/../span[@class="rcont "]/text()').extract_first()
        # 总户数
        x20 = se(text=data).xpath(
            '//span[text()="总  户  数"]/../span[@class="rcont "]/text()').extract_first()
        # 总楼栋数
        x21 = se(text=data).xpath(
            '//span[text()="总楼栋数"]/../span[@class="rcont "]/text()').extract_first()
        # 评分
        x22 = se(text=data).xpath(
            '//p[text()="交易价值"]/../h4/text()').extract_first()
        x23 = se(text=data).xpath(
            '//p[text()="居住品质"]/../h4/text()').extract_first()
        x24 = se(text=data).xpath(
            '//p[text()="便利指数"]/../h4/text()').extract_first()
        x25 = se(text=data).xpath(
            '//p[text()="物业服务"]/../h4/text()').extract_first()
        x26 = se(text=data).xpath(
            '//p[text()="房源评级|多维度分析房源"]/../div[@class="num_bg"]/h3/text()').extract_first()
        # 单价
        x27 = se(text=response.text).xpath(
            '//div[@class="tt"]/text()').extract()[2]
        data = [x1, x2, x3, x4, x5, x6,
                x7, x8, x9, x10, x11, x12,
                x13, x14, x15, x16, x17, x18,
                x19, x20, x21, x22, x23, x24,
                x25, x26, x27]
        save(data, out_dir,columns)
        print(f"房源{response.url}保存成功")
    except:
        fail_url.append(response.url)
        save([response.url], 'fail_url.csv',['链接地址'])  # 字符串写入时会默认拆开
        print(f"{response.url}解析失败,请稍后查看实际情况,当前解析失败网页总数为{len(fail_url)}")
def  main(url):
    response = get_url(url)
    true_url=process_url(response)
    response=get_url(true_url)
    house_urls, next_url = parse_page(response)
    while(next_url):
        for url in house_urls:
            response = get_url('https://xian.esf.fang.com'+url)
            true_url=process_url(response)
            response=get_url(true_url)
            parse_house_page(response)
        response=get_url(next_url)
        true_url = process_url(response)
        response = get_url(true_url)
        house_urls, next_url = parse_page(response)
        if not(next_url):#将最后一页数据进行解析
            for url in house_urls:
                response=get_url(url)
                true_url=process_url(response)
                response=get_url(true_url)
                parse_house_page(response)    
if __name__ == '__main__':
    start = time.time()
    main('https://xian.esf.fang.com/house/i31/')
    #多线程进行有一些问题,仍待进一步修改
    """ url_list = ['https://xian.esf.fang.com/house/i31/',
                'https://xian.esf.fang.com/house/i325/',
                'https://xian.esf.fang.com/house/i350/',
                'https://xian.esf.fang.com/house/i375/']
    for url in url_list:
        _thread.start_new_thread(main,(url,)) """
    end = time.time()
    print("运行程序花费了%s秒" % (end-start))

虽然会因为爬取频率过高导致网页请求出现拖动滑块的验证码,但是真的不想改了啊啊很棒了。

第二次完善之后还是有好多问题😭,多线程爬取数据存在明显问题,还有待进一步完善。不过记得下次可以把try语句写进匹配规则里,因为不知道会遇到什么问题,这是一种很稳妥的渐进爬虫。多线程属于一个相对比较复杂的问题,这块因为本身计算机理论的缺失可能还有待进一步补充,照目前的情况来看,写的爬虫的爬取效率还有待进一步提升,再者就是访问次数过多出现的滑块验证问题还没有解决。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-07-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 🤔经验总结
    • 关于网页的重定向问题
  • 😄终稿
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档